[MERGE]: Merge with lp:~openerp-dev/openobject-addons/trunk-dev-addons2

bzr revid: rpa@tinyerp.com-20100428104744-gqjjqnw5gusv9vbp
This commit is contained in:
rpa (Open ERP)
2010-04-28 16:17:44 +05:30
109 changed files with 4222 additions and 6219 deletions
+7 -5
View File
@@ -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-08-28 16:01+0000\n"
"PO-Revision-Date: 2010-03-29 15:32+0000\n"
"Last-Translator: Carlo Vettore <Unknown>\n"
"PO-Revision-Date: 2010-04-26 07:46+0000\n"
"Last-Translator: eLBati - albatos.com <lorenzo.battistini@albatos.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-04-17 04:06+0000\n"
"X-Launchpad-Export-Date: 2010-04-28 03:44+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: account
@@ -1192,7 +1192,7 @@ msgstr "Report tassa"
#: wizard_button:account.analytic.account.chart,init,open:0
#: wizard_button:account.chart,init,open:0
msgid "Open Charts"
msgstr "Conti aperti"
msgstr "Apri"
#. module: account
#: wizard_view:account.fiscalyear.close.state,init:0
@@ -2301,6 +2301,8 @@ msgstr ""
#: wizard_view:account.chart,init:0
msgid "(If you do not select Fiscal year it will take all open fiscal years)"
msgstr ""
"Se non si seleziona un anno fiscale, verranno visualizzati tutti gli anni "
"fiscali aperti"
#. module: account
#: help:account.invoice.tax,base_code_id:0
@@ -4429,7 +4431,7 @@ msgstr ""
#. module: account
#: wizard_view:account.wizard_paid_open,init:0
msgid "Open Invoice"
msgstr "Fattura aperta"
msgstr "Apri fattura"
#. module: account
#: model:process.node,note:account.process_node_draftstatement0
+3 -3
View File
@@ -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-08-28 16:01+0000\n"
"PO-Revision-Date: 2010-04-25 17:34+0000\n"
"PO-Revision-Date: 2010-04-27 19:28+0000\n"
"Last-Translator: Grzegorz Grzelak (Cirrus.pl) <Unknown>\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-04-26 03:42+0000\n"
"X-Launchpad-Export-Date: 2010-04-28 03:44+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: account
@@ -2087,7 +2087,7 @@ msgstr "Wydatki"
#. module: account
#: field:account.journal,invoice_sequence_id:0
msgid "Invoice Sequence"
msgstr "Numeracja faktury"
msgstr "Numeracja faktur"
#. module: account
#: wizard_view:account.automatic.reconcile,init:0
+4 -4
View File
@@ -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-08-28 16:01+0000\n"
"PO-Revision-Date: 2010-04-25 21:20+0000\n"
"PO-Revision-Date: 2010-04-26 09:10+0000\n"
"Last-Translator: Martin Volf <Unknown>\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-04-26 03:41+0000\n"
"X-Launchpad-Export-Date: 2010-04-28 03:44+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: account_tax_include
@@ -24,12 +24,12 @@ msgstr "Neplatný XML pro zobrazení architektury"
#. module: account_tax_include
#: field:account.invoice,price_type:0
msgid "Price method"
msgstr ""
msgstr "Metoda určování cen"
#. module: account_tax_include
#: model:ir.module.module,shortdesc:account_tax_include.module_meta_information
msgid "Invoices and prices with taxes included"
msgstr ""
msgstr "Faktury a ceny včetně daní"
#. module: account_tax_include
#: selection:account.invoice,price_type:0
+41 -41
View File
@@ -462,13 +462,13 @@ property or property parameter."),
body = html_invitation % body_vals
if mail_to and email_from:
tools.email_send(
email_from,
mail_to,
sub,
body,
subtype='html',
reply_to=email_from
)
email_from,
mail_to,
sub,
body,
subtype='html',
reply_to=email_from
)
return True
def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
@@ -660,41 +660,41 @@ class calendar_alarm(osv.osv):
__attribute__ = {}
_columns = {
'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
'name': fields.char('Summary', size=124, help="""Contains the text to be \
used as the message subject for email \
or contains the text to be used for display"""),
'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
required=True, help="Defines the action to be invoked when an alarm is triggered"),
'description': fields.text('Description', help='Provides a more complete \
description of the calendar component, than that \
provided by the "SUMMARY" property'),
'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
'alarm_id', 'attendee_id', 'Attendees', readonly=True),
'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
which is rendered when the alarm is triggered for audio,
* File which is intended to be sent as message attachments for email,
* Points to a procedure resource, which is invoked when\
the alarm is triggered for procedure."""),
'res_id': fields.integer('Resource ID'),
'model_id': fields.many2one('ir.model', 'Model'),
'user_id': fields.many2one('res.users', 'Owner'),
'event_date': fields.datetime('Event Date'),
'event_end_date': fields.datetime('Event End Date'),
'trigger_date': fields.datetime('Trigger Date', readonly="True"),
'state':fields.selection([('draft', 'Draft'),
('run', 'Run'),
('stop', 'Stop'),
('done', 'Done'),
], 'State', select=True, readonly=True),
}
'alarm_id': fields.many2one('res.alarm', 'Basic Alarm', ondelete='cascade'),
'name': fields.char('Summary', size=124, help="""Contains the text to be \
used as the message subject for email \
or contains the text to be used for display"""),
'action': fields.selection([('audio', 'Audio'), ('display', 'Display'), \
('procedure', 'Procedure'), ('email', 'Email') ], 'Action', \
required=True, help="Defines the action to be invoked when an alarm is triggered"),
'description': fields.text('Description', help='Provides a more complete \
description of the calendar component, than that \
provided by the "SUMMARY" property'),
'attendee_ids': fields.many2many('calendar.attendee', 'alarm_attendee_rel', \
'alarm_id', 'attendee_id', 'Attendees', readonly=True),
'attach': fields.binary('Attachment', help="""* Points to a sound resource,\
which is rendered when the alarm is triggered for audio,
* File which is intended to be sent as message attachments for email,
* Points to a procedure resource, which is invoked when\
the alarm is triggered for procedure."""),
'res_id': fields.integer('Resource ID'),
'model_id': fields.many2one('ir.model', 'Model'),
'user_id': fields.many2one('res.users', 'Owner'),
'event_date': fields.datetime('Event Date'),
'event_end_date': fields.datetime('Event End Date'),
'trigger_date': fields.datetime('Trigger Date', readonly="True"),
'state':fields.selection([
('draft', 'Draft'),
('run', 'Run'),
('stop', 'Stop'),
('done', 'Done'),
], 'State', select=True, readonly=True),
}
_defaults = {
'action': lambda *x: 'email',
'state': lambda *x: 'run',
}
'action': lambda *x: 'email',
'state': lambda *x: 'run',
}
def create(self, cr, uid, vals, context={}):
"""
create new record.
@@ -982,7 +982,7 @@ class calendar_event(osv.osv):
'Show as'),
'base_calendar_url': fields.char('Caldav URL', size=264),
'exdate': fields.text('Exception Date/Times', help="This property \
defines the list of date/time exceptions for arecurring calendar component."),
defines the list of date/time exceptions for arecurring calendar component."),
'exrule': fields.char('Exception Rule', size=352, help="defines a \
rule or repeating pattern for anexception to a recurrence set"),
'rrule': fields.function(_get_rulestring, type='char', size=124, method=True, \
+46 -25
View File
@@ -30,19 +30,26 @@ import tools
#objects_proxy = netsvc.ExportService.getService('object').__class__
class recording_objects_proxy(osv_pool):
def execute(self, *args, **argv):
if len(args) >= 6 and isinstance(args[5], dict):
if args[3] == 'create':
_old_args = args[4].copy()
elif args[3] == 'write':
_old_args = args[5].copy()
elif len(args) >= 5 and isinstance(args[4], dict):
_old_args = args[4].copy()
else:
_old_args = None
res = super(recording_objects_proxy, self).execute(*args, **argv)
pool = pooler.get_pool(args[0])
mod = pool.get('ir.module.record')
if mod and mod.recording:
if args[3] not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink'):
if _old_args is not None:
args[5].update(_old_args)
if args[5]:
mod.recording_data.append(('query', args, argv,res))
if args[3] == 'create':
args[4].update(_old_args)
elif args[3] == 'write':
args[5].update(_old_args)
mod.recording_data.append(('query', args, argv,res))
return res
def exec_workflow(self, *args, **argv):
@@ -125,7 +132,8 @@ class base_module_record(osv.osv):
name = filter(lambda x: x in string.letters, (data.get('name','') or '').lower())
except:
name=''
val = model.replace('.','_')+'_'+name+ str(i)
# name=data.get('name','') or ''.lower()
val = model.replace('.','_')+'_'+ name + str(i)
i+=1
if val not in self.ids.values():
break
@@ -146,7 +154,6 @@ class base_module_record(osv.osv):
def _create_record(self, cr, uid, doc, model, data, record_id, noupdate=False):
data_pool = self.pool.get('ir.model.data')
model_pool = self.pool.get(model)
record = doc.createElement('record')
@@ -196,12 +203,26 @@ class base_module_record(osv.osv):
fname = model_pool._columns[key]._fields_id
else:
fname = model_pool._inherit_fields[key][2]._fields_id
valitem[2][fname] = record_id
newid,update = self._get_id(cr, uid, fields[key]['relation'], valitem[1])
if not newid:
if valitem[0] == 0:
newid = self._create_id(cr, uid, fields[key]['relation'], valitem[2])
valitem[1]=newid
else:
newid,update = self._get_id(cr, uid, fields[key]['relation'], valitem[1])
if not newid:
newid = self._create_id(cr, uid, fields[key]['relation'], valitem[2])
valitem[1]=newid
self.ids[(fields[key]['relation'], valitem[1])] = newid
# if valitem[0] in (0,1):
# if key in model_pool._columns:
# fname = model_pool._columns[key]._fields_id
# else:
# fname = model_pool._inherit_fields[key][2]._fields_id
# valitem[2][fname] = record_id
# newid,update = self._get_id(cr, uid, fields[key]['relation'], valitem[1])
# if not newid:
# newid = self._create_id(cr, uid, fields[key]['relation'], valitem[2])
# self.ids[(fields[key]['relation'], valitem[1])] = newid
#
childrecord, update = self._create_record(cr, uid, doc, fields[key]['relation'],valitem[2], newid)
noupdate = noupdate or update
record_list += childrecord
@@ -245,7 +266,7 @@ class base_module_record(osv.osv):
for key,val in data.items():
if ((key in defaults[model]) and (val == defaults[model][key])) and not(fields[key].get('required',False)):
continue
if not (val or (fields[key]['type']=='boolean')):
if not (val or (fields[key]['type']=='function')):
continue
elif fields[key]['type'] in ('boolean',):
if not val:
@@ -267,12 +288,9 @@ class base_module_record(osv.osv):
fname = model_pool._columns[key]._fields_id
else:
fname = model_pool._inherit_fields[key][2]._fields_id
valitem[2][fname] = record_id
newid,update = self._get_id(cr, uid, fields[key]['relation'], valitem[1])
if not newid:
newid = self._create_id(cr, uid, fields[key]['relation'], valitem[2])
self.ids[(fields[key]['relation'], valitem[1])] = newid
childrecord = self._create_yaml_record(cr, uid, fields[key]['relation'],valitem[2], newid)
del valitem[2][fname] #delete parent_field from child's fields list
childrecord = self._create_yaml_record(cr, uid, fields[key]['relation'],valitem[2], None)
items[0].append(childrecord['attrs'])
attrs[key] = items
elif fields[key]['type'] in ('many2many',):
@@ -289,11 +307,11 @@ class base_module_record(osv.osv):
if m2m[0]:
attrs[key] = m2m
else:
val=val.replace('"','\'')
try:
attrs[key]=str(val)
except:
attrs[key]=tools.ustr(val)
attrs[key]=attrs[key].replace('"','\'')
record['attrs'] = attrs
return record
@@ -311,7 +329,7 @@ class base_module_record(osv.osv):
for f in filter(lambda a: isinstance(obj._columns[a], fields.function)\
and (not obj._columns[a].store),obj._columns):
del data[f]
for key,val in data.items():
if result.has_key(key):
continue
@@ -361,16 +379,16 @@ class base_module_record(osv.osv):
def _generate_object_xml(self, cr, uid, rec, recv, doc, result=None):
record_list = []
noupdate = False
if rec[4]=='write':
for id in rec[5]:
id,update = self._get_id(cr, uid, rec[3], id)
if rec[3]=='write':
for id in rec[4]:
id,update = self._get_id(cr, uid, rec[2], id)
noupdate = noupdate or update
if not id:
continue
record,update = self._create_record(cr, uid, doc, rec[3], rec[6], id)
record,update = self._create_record(cr, uid, doc, rec[2], rec[5], id)
noupdate = noupdate or update
record_list += record
elif rec[4] in ('menu_create',):
for id in rec[5]:
id,update = self._get_id(cr, uid, rec[3], id)
@@ -487,7 +505,10 @@ class base_module_record(osv.osv):
yaml_file += str(object) + '''\n\n'''
else:
record= self._generate_object_yaml(cr, uid, rec[1],rec[3])
yaml_file+="!comment Creating an %s record"%(record['model']) + '''\n'''
if self.mode == "create" or self.mode == "copy":
yaml_file+="!comment Creating a %s record"%(record['model']) + '''\n'''
else:
yaml_file+="!comment Modifying a %s record"%(record['model']) + '''\n'''
object= yaml.load(unicode('''\n !record %s \n'''%record,'iso-8859-1'))
yaml_file += str(object) + '''\n'''
attrs=yaml.dump(object.attrs, default_flow_style=False)
+1 -1
View File
@@ -28,7 +28,7 @@ If you don't think you need any of these right now, you can easily install them
<field name="account"/> <field name="purchase"/>
<field name="hr"/> <field name="point_of_sale"/>
<field name="marketing"/> <field name="misc_tools"/>
<field name="report_designer"/>
<field name="report_designer"/> <field name="thunderbird"/>
<separator string="Vertical Modules" colspan="4"/>
<field name="profile_association"/>
<field name="profile_training"/>
+1
View File
@@ -73,6 +73,7 @@ class base_setup_installer(osv.osv_memory):
'report_designer':fields.boolean('Advanced Reporting',
help="Lets you install various tools to simplify and enhance "
"OpenERP's report creation."),
'thunderbird' :fields.boolean('Thunderbird'),
# Vertical modules
'profile_association':fields.boolean('Associations',
help="Installs a preselected set of OpenERP "
-101
View File
@@ -1,101 +0,0 @@
#!/usr/bin/env python
"""
Buffering HTTP Server
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from utils import VERSION, AUTHOR
__version__ = VERSION
__author__ = AUTHOR
from BaseHTTPServer import BaseHTTPRequestHandler
import os
class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
"""
Buffering HTTP Request Handler
This class is an extension to the BaseHTTPRequestHandler
class which buffers the whole output and sends it at once
after the processing if the request is finished.
This makes it possible to work together with some clients
which otherwise would break (e.g. cadaver)
"""
def _init_buffer(self):
"""initialize the buffer.
If you override the handle() method remember to call
this (see below)
"""
self.__buffer = ""
self.__outfp = os.tmpfile()
def _append(self,s):
""" append a string to the buffer """
self.__buffer = self.__buffer+s
def _flush(self):
""" flush the buffer to wfile """
self.wfile.write(self.__buffer)
self.__outfp.write(self.__buffer)
self.__outfp.flush()
self.wfile.flush()
self.__buffer = ""
def handle(self):
""" Handle a HTTP request """
self._init_buffer()
BaseHTTPRequestHandler.handle(self)
self._flush()
def send_header(self, keyword, value):
"""Send a MIME header."""
if self.request_version != 'HTTP/0.9':
self._append("%s: %s\r\n" % (keyword, value))
def end_headers(self):
"""Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9':
self._append("\r\n")
def send_response(self, code, message=None):
self.log_request(code)
if message is None:
if self.responses.has_key(code):
message = self.responses[code][0]
else:
message = ''
if self.request_version != 'HTTP/0.9':
self._append("%s %s %s\r\n" %
(self.protocol_version, str(code), message))
self.send_header('Server', self.version_string())
self.send_header('Connection', 'close')
self.send_header('Date', self.date_time_string())
protocol_version="HTTP/1.1"
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-381
View File
@@ -1,381 +0,0 @@
#!/usr/bin/env python
"""
Python WebDAV Server.
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This module builds on AuthServer by implementing the standard DAV
methods.
Subclass this class and specify an IFACE_CLASS. See example.
"""
DEBUG = None
from utils import VERSION, AUTHOR
__version__ = VERSION
__author__ = AUTHOR
from propfind import PROPFIND
from delete import DELETE
from davcopy import COPY
from davmove import MOVE
from string import atoi, split
from status import STATUS_CODES
from errors import *
import os
import sys
import time
import socket
import string
import posixpath
import base64
import urlparse
import urllib
import BaseHTTPServer
class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""Simple DAV request handler with
- GET
- HEAD
- PUT
- OPTIONS
- PROPFIND
- PROPPATCH
- MKCOL
It uses the resource/collection classes for serving and
storing content.
"""
server_version = "DAV/" + __version__
protocol_version = 'HTTP/1.1'
### utility functions
def _log(self, message):
pass
def _append(self, s):
""" write the string to wfile """
self.wfile.write(s)
def send_body(self, DATA, code, msg, desc, ctype='application/octet-stream', headers=None):
""" send a body in one part """
if not headers:
headers = {}
self.send_response(code, message=msg)
self.send_header("Connection", "keep-alive")
self.send_header("Accept-Ranges", "bytes")
for a, v in headers.items():
self.send_header(a, v)
if DATA:
self.send_header("Content-Length", str(len(DATA)))
self.send_header("Content-Type", ctype)
else:
self.send_header("Content-Length", "0")
self.end_headers()
if DATA:
self._append(DATA)
def send_body_chunks(self, DATA, code, msg, desc, ctype='text/xml; encoding="utf-8"'):
""" send a body in chunks """
self.responses[207]=(msg, desc)
self.send_response(code, message=msg)
self.send_header("Content-type", ctype)
self.send_header("Connection", "keep-alive")
self.send_header("Transfer-Encoding", "chunked")
self.end_headers()
self._append(hex(len(DATA))[2:]+"\r\n")
self._append(DATA)
self._append("\r\n")
self._append("0\r\n")
self._append("\r\n")
### HTTP METHODS
def do_OPTIONS(self):
"""return the list of capabilities """
self.send_response(200)
self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
self.send_header("Content-Type", "text/plain")
self.send_header("Connection", "keep-alive")
self.send_header("DAV", "1")
self.end_headers()
def do_PROPFIND(self):
dc = self.IFACE_CLASS
# read the body
body = None
if self.headers.has_key("Content-Length"):
l = self.headers['Content-Length']
body = self.rfile.read(atoi(l))
alt_body = """<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><prop>
<getcontentlength xmlns="DAV:"/>
<getlastmodified xmlns="DAV:"/>
<getcreationdate xmlns="DAV:"/>
<checked-in xmlns="DAV:"/>
<executable xmlns="http://apache.org/dav/props/"/>
<displayname xmlns="DAV:"/>
<resourcetype xmlns="DAV:"/>
<checked-out xmlns="DAV:"/>
</prop></propfind>"""
#self.wfile.write(body)
# which Depth?
if self.headers.has_key('Depth'):
d = self.headers['Depth']
else:
d = "infinity"
uri = self.geturi()
pf = PROPFIND(uri, dc, d)
if body:
pf.read_propfind(body)
try:
DATA = pf.createResponse()
DATA = DATA+"\n"
# print "Data:", DATA
except DAV_NotFound, (ec, dd):
return self.send_notFound(dd, uri)
except DAV_Error, (ec, dd):
return self.send_error(ec, dd)
self.send_body_chunks(DATA, 207, "Multi-Status", "Multiple responses")
def geturi(self):
buri = self.IFACE_CLASS.baseuri
if buri[-1] == '/':
return urllib.unquote(buri[:-1]+self.path)
else:
return urllib.unquote(buri+self.path)
def do_GET(self):
"""Serve a GET request."""
dc = self.IFACE_CLASS
uri = self.geturi()
# get the last modified date
try:
lm = dc.get_prop(uri, "DAV:", "getlastmodified")
except:
lm = "Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
headers = {"Last-Modified":lm , "Connection": "keep-alive"}
# get the content type
try:
ct = dc.get_prop(uri, "DAV:", "getcontenttype")
except:
ct = "application/octet-stream"
# get the data
try:
data = dc.get_data(uri)
except DAV_Error, (ec, dd):
self.send_status(ec)
return
# send the data
self.send_body(data, 200, "OK", "OK", ct, headers)
def do_HEAD(self):
""" Send a HEAD response """
dc = self.IFACE_CLASS
uri = self.geturi()
# get the last modified date
try:
lm = dc.get_prop(uri, "DAV:", "getlastmodified")
except:
lm = "Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
headers = {"Last-Modified":lm, "Connection": "keep-alive"}
# get the content type
try:
ct = dc.get_prop(uri, "DAV:", "getcontenttype")
except:
ct = "application/octet-stream"
try:
data = dc.get_data(uri)
headers["Content-Length"] = str(len(data))
except DAV_NotFound:
self.send_body(None, 404, "Not Found", "")
return
self.send_body(None, 200, "OK", "OK", ct, headers)
def do_POST(self):
self.send_error(404, "File not found")
def do_MKCOL(self):
""" create a new collection """
dc = self.IFACE_CLASS
uri = self.geturi()
try:
res = dc.mkcol(uri)
if res:
self.send_body(None, 201, "Created", '')
else:
self.send_body(None, 415, "Cannot create", '')
#self.send_header("Connection", "keep-alive")
# Todo: some content, too
except DAV_Error, (ec, dd):
self.send_body(None, int(ec), dd, dd)
def do_DELETE(self):
""" delete an resource """
dc = self.IFACE_CLASS
uri = self.geturi()
dl = DELETE(uri, dc)
if dc.is_collection(uri):
res = dl.delcol()
else:
res = dl.delone()
if res:
self.send_status(207, body=res)
else:
self.send_status(204)
def do_PUT(self):
dc = self.IFACE_CLASS
# read the body
body = None
if self.headers.has_key("Content-Length"):
l = self.headers['Content-Length']
body = self.rfile.read(atoi(l))
uri = self.geturi()
ct = None
if self.headers.has_key("Content-Type"):
ct = self.headers['Content-Type']
try:
dc.put(uri, body, ct)
except DAV_Error, (ec, dd):
self.send_status(ec)
return
self.send_status(201)
def do_COPY(self):
""" copy one resource to another """
try:
self.copymove(COPY)
except DAV_Error, (ec, dd):
self.send_status(ec)
def do_MOVE(self):
""" move one resource to another """
try:
self.copymove(MOVE)
except DAV_Error, (ec, dd):
self.send_status(ec)
def copymove(self, CLASS):
""" common method for copying or moving objects """
dc = self.IFACE_CLASS
# get the source URI
source_uri = self.geturi()
# get the destination URI
dest_uri = self.headers['Destination']
dest_uri = urllib.unquote(dest_uri)
# Overwrite?
overwrite = 1
result_code = 204
if self.headers.has_key("Overwrite"):
if self.headers['Overwrite']=="F":
overwrite=None
result_code=201
# instanciate ACTION class
cp = CLASS(dc, source_uri, dest_uri, overwrite)
# Depth?
d = "infinity"
if self.headers.has_key("Depth"):
d = self.headers['Depth']
if d!="0" and d!="infinity":
self.send_status(400)
return
if d=="0":
res = cp.single_action()
self.send_status(res)
return
# now it only can be "infinity" but we nevertheless check for a collection
if dc.is_collection(source_uri):
try:
res = cp.tree_action()
except DAV_Error, (ec, dd):
self.send_status(ec)
return
else:
try:
res = cp.single_action()
except DAV_Error, (ec, dd):
self.send_status(ec)
return
if res:
self.send_body_chunks(res, 207, STATUS_CODES[207], STATUS_CODES[207],
ctype='text/xml; charset="utf-8"')
else:
self.send_status(result_code)
def get_userinfo(self, user, pw):
""" Dummy method which lets all users in """
return 1
def send_status(self, code=200, mediatype='text/xml; charset="utf-8"', \
msg=None, body=None):
if not msg: msg = STATUS_CODES[code]
self.send_body(body, code, STATUS_CODES[code], msg, mediatype)
def send_notFound(self, descr, uri):
body = """<?xml version="1.0" encoding="utf-8" ?>
<D:response xmlns:D="DAV:">
<D:href>%s</D:href>
<D:error/>
<D:responsedescription>%s</D:responsedescription>
</D:response>
"""
return self.send_status(404, descr, body=body % (uri, descr))
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-20
View File
@@ -1,20 +0,0 @@
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-18
View File
@@ -1,18 +0,0 @@
"""
constants definition
"""
# definition for resourcetype
COLLECTION=1
OBJECT=None
DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
# Request classes in propfind
RT_ALLPROP=1
RT_PROPNAME=2
RT_PROP=3
-219
View File
@@ -1,219 +0,0 @@
"""
davcmd.py
---------
containts commands like copy, move, delete for normal
resources and collections
"""
from string import split,replace,joinfields
import urlparse
from utils import create_treelist, is_prefix
from errors import *
def deltree(dc, uri, exclude={}):
""" delete a tree of resources
dc -- dataclass to use
uri -- root uri to delete
exclude -- an optional list of uri:error_code pairs which should not
be deleted.
returns dict of uri:error_code tuples from which
another method can create a multistatus xml element.
Also note that we only know Depth=infinity thus we don't have
to test for it.
"""
tlist = create_treelist(dc,uri)
result = {}
for i in range(len(tlist),0,-1):
problem_uris = result.keys()
element = tlist[i-1]
# test here, if an element is a prefix of an uri which
# generated an error before.
# note that we walk here from childs to parents, thus
# we cannot delete a parent if a child made a problem.
# (see example in 8.6.2.1)
ok = 1
for p in problem_uris:
if is_prefix(element,p):
ok = None
break
if not ok: continue
# here we test for the exclude list which is the other way round!
for p in exclude.keys():
if is_prefix(p,element):
ok = None
break
if not ok: continue
# now delete stuff
try:
delone(dc,element)
except DAV_Error, (ec,dd):
result[element] = ec
return result
def delone(dc, uri):
""" delete a single object """
if dc.is_collection(uri):
dc.rmcol(uri) # should be empty
else:
dc.rm(uri)
###
### COPY
###
# helper function
def copy(dc, src, dst):
""" only copy the element
This is just a helper method factored out from copy and
copytree. It will not handle the overwrite or depth header.
"""
# destination should have been deleted before
if dc.exists(dst): raise DAV_Error, 412
# source should exist also
if not dc.exists(src): raise DAV_NotFound
if dc.is_collection(src):
dc.copycol(src,dst) # an exception will be passed thru
else:
dc.copy(src,dst) # an exception will be passed thru
# the main functions
def copyone(dc, src, dst, overwrite=None):
""" copy one resource to a new destination """
if overwrite and dc.exists(dst):
delres = deltree(dc,dst)
else:
delres = {}
# if we cannot delete everything, then do not copy!
if delres: return delres
try:
copy(dc,src,dst) # pass thru exceptions
except DAV_Error, (ec,dd):
return ec
def copytree(dc, src, dst, overwrite=None):
""" copy a tree of resources to another location
dc -- dataclass to use
src -- src uri from where to copy
dst -- dst uri
overwrite -- if 1 then delete dst uri before
returns dict of uri:error_code tuples from which
another method can create a multistatus xml element.
"""
# first delete the destination resource
if overwrite and dc.exists(dst):
delres = deltree(dc,dst)
else:
delres = {}
# if we cannot delete everything, then do not copy!
if delres: return delres
# get the tree we have to copy
tlist = create_treelist(dc,src)
result = {}
# prepare destination URIs (get the prefix)
dpath = urlparse.urlparse(dst)[2]
for element in tlist:
problem_uris = result.keys()
# now URIs get longer and longer thus we have
# to test if we had a parent URI which we were not
# able to copy in problem_uris which is the prefix
# of the actual element. If it is, then we cannot
# copy this as well but do not generate another error.
ok = 1
for p in problem_uris:
if is_prefix(p,element):
ok = None
break
if not ok: continue
# now create the destination URI which corresponds to
# the actual source URI. -> actual_dst
# ("subtract" the base src from the URI and prepend the
# dst prefix to it.)
esrc = replace(element,src,"")
actual_dst = dpath+esrc
# now copy stuff
try:
copy(dc,element,actual_dst)
except DAV_Error, (ec,dd):
result[element] = ec
return result
###
### MOVE
###
def moveone(dc, src, dst, overwrite=None):
""" move a single resource
This is done by first copying it and then deleting
the original.
"""
# first copy it
copyone(dc,src,dst,overwrite)
# then delete it
dc.rm(src)
def movetree(dc, src, dst, overwrite=None):
""" move a collection
This is done by first copying it and then deleting
the original.
PROBLEM: if something did not copy then we have a problem
when deleting as the original might get deleted!
"""
# first copy it
res = copytree(dc,src,dst,overwrite)
# then delete it
res = deltree(dc,src,exclude=res)
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-134
View File
@@ -1,134 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from xml.dom import ext
from xml.dom.Document import Document
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
from utils import create_treelist, quote_uri, gen_estring
class COPY:
""" copy resources and eventually create multistatus responses
This module implements the COPY class which is responsible for
copying resources. Usually the normal copy work is done in the
interface class. This class only creates error messages if error
occur.
"""
def __init__(self, dataclass, src_uri, dst_uri, overwrite):
self.__dataclass = dataclass
self.__src = src_uri
self.__dst = dst_uri
self.__overwrite = overwrite
def single_action(self):
""" copy a normal resources.
We try to copy it and return the result code.
This is for Depth==0
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
return dc.copyone(self.__src,self.__dst,self.__overwrite)
#return copyone(dc,self.__src,self.__dst,self.__overwrite)
def tree_action(self):
""" copy a tree of resources (a collection)
Here we return a multistatus xml element.
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
result = dc.copytree(self.__src,self.__dst,self.__overwrite)
#result=copytree(dc,self.__src,self.__dst,self.__overwrite)
if not result: return None
###
### create the multistatus XML element
### (this is also the same as in delete.py.
### we might make a common method out of it)
###
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
for el,ec in result.items():
re = doc.createElement("D:response")
hr = doc.createElement("D:href")
st = doc.createElement("D:status")
huri = doc.createTextNode(quote_uri(el))
t = doc.createTextNode(gen_estring(ec))
st.appendChild(t)
hr.appendChild(huri)
re.appendChild(hr)
re.appendChild(st)
ms.appendChild(re)
sfile = StringIO()
ext.PrettyPrint(doc,stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-103
View File
@@ -1,103 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
from utils import create_treelist, quote_uri, gen_estring, make_xmlresponse
from davcmd import moveone, movetree
class MOVE:
""" move resources and eventually create multistatus responses
This module implements the MOVE class which is responsible for
moving resources.
MOVE is implemented by a COPY followed by a DELETE of the old
resource.
"""
def __init__(self, dataclass, src_uri, dst_uri, overwrite):
self.__dataclass = dataclass
self.__src = src_uri
self.__dst = dst_uri
self.__overwrite = overwrite
def single_action(self):
""" move a normal resources.
We try to move it and return the result code.
This is for Depth==0
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
return dc.moveone(self.__src,self.__dst,self.__overwrite)
def tree_action(self):
""" move a tree of resources (a collection)
Here we return a multistatus xml element.
"""
dc = self.__dataclass
base = self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps = urlparse.urlparse(self.__src)[2]
pd = urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
result = dc.movetree(self.__src,self.__dst,self.__overwrite)
if not result: return None
# create the multistatus XML element
return make_xmlresponse(result)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import os
import string
import urllib
from StringIO import StringIO
from status import STATUS_CODES
from utils import gen_estring, quote_uri, make_xmlresponse
from davcmd import deltree
class DELETE:
def __init__(self, uri, dataclass):
self.__dataclass = dataclass
self.__uri = uri
def delcol(self):
""" delete a collection """
dc = self.__dataclass
result = dc.deltree(self.__uri)
if not len(result.items()):
return None # everything ok
# create the result element
return make_xmlresponse(result)
def delone(self):
""" delete a resource """
dc = self.__dataclass
result = dc.delone(self.__uri)
if not result: return None
if not len(result.items()):
return None # everything ok
# create the result element
return make_xmlresponse(result)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env python
"""
Exceptions for the DAVserver implementation
"""
class DAV_Error(Exception):
""" in general we can have the following arguments:
1. the error code
2. the error result element, e.g. a <multistatus> element
"""
def __init__(self, *args):
if len(args)==1:
self.args = (args[0],"")
else:
self.args = args
class DAV_Secret(DAV_Error):
""" the user is not allowed to know anything about it
returning this for a property value means to exclude it
from the response xml element.
"""
def __init__(self):
DAV_Error.__init__(self,0)
pass
class DAV_NotFound(DAV_Error):
""" a requested property was not found for a resource """
def __init__(self, *args):
if len(args):
if isinstance(args[0], list):
stre = "Path %s not found!"%('/'.join(args[0]))
else:
stre = args[0]
DAV_Error.__init__(self,404,stre)
else:
DAV_Error.__init__(self,404)
pass
class DAV_Forbidden(DAV_Error):
""" a method on a resource is not allowed """
def __init__(self, *args):
if len(args):
DAV_Error.__init__(self,403,args[0])
else:
DAV_Error.__init__(self,403)
pass
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-264
View File
@@ -1,264 +0,0 @@
"""
basic interface class
use this for subclassing when writing your own interface
class.
"""
from errors import *
import time
from string import lower
class dav_interface:
""" interface class for implementing DAV servers """
### defined properties (modify this but let the DAV stuff there!)
### the format is namespace: [list of properties]
PROPS={"DAV:" : ('creationdate',
'displayname',
'getcontentlanguage',
'getcontentlength',
'getcontenttype',
'getetag',
'getlastmodified',
'lockdiscovery',
'resourcetype',
'source',
'supportedlock'),
"NS2" : ("p1","p2")
}
# here we define which methods handle which namespace
# the first item is the namespace URI and the second one
# the method prefix
# e.g. for DAV:getcontenttype we call dav_getcontenttype()
M_NS = {"DAV:" : "_get_dav",
"NS2" : "ns2" }
def get_propnames(self, uri):
""" return the property names allowed for the given URI
In this method we simply return the above defined properties
assuming that they are valid for any resource.
You can override this in order to return a different set
of property names for each resource.
"""
return self.PROPS
def get_prop2(self, uri, ns, pname):
""" return the value of a property
"""
if lower(ns)=="dav:": return self.get_dav(uri,pname)
raise DAV_NotFound
def get_prop(self, uri, ns, propname):
""" return the value of a given property
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
raise DAV_NotFound
mname = prefix+"_"+propname
if not hasattr(self,mname):
raise DAV_NotFound
try:
m = getattr(self,mname)
r = m(uri)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
raise DAV_NotFound
###
### DATA methods (for GET and PUT)
###
def get_data(self, uri):
""" return the content of an object
return data or raise an exception
"""
raise DAV_NotFound
def put(self, uri, data):
""" write an object to the repository
return a result code or raise an exception
"""
raise DAV_Forbidden
###
### Methods for DAV properties
###
def _get_dav_creationdate(self, uri):
""" return the creationdate of a resource """
d = self.get_creationdate(uri)
# format it
if isinstance(d, int) or isinstance(d, float):
d = time.localtimetime(d)
return time.strftime("%Y-%m-%dT%H:%M:%S%Z",d)
def _get_dav_getlastmodified(self, uri):
""" return the last modified date of a resource """
d = self.get_lastmodified(uri)
if isinstance(d, int) or isinstance(d, float):
d = time.localtime(d)
# format it
return time.asctime(d)
###
### OVERRIDE THESE!
###
def get_creationdate(self, uri):
""" return the creationdate of the resource """
return time.time()
def get_lastmodified(self, uri):
""" return the last modification date of the resource """
return time.time()
###
### COPY MOVE DELETE
###
### methods for deleting a resource
def rmcol(self, uri):
""" delete a collection
This should not delete any children! This is automatically done
before by the DELETE class in DAV/delete.py
return a success code or raise an exception
"""
raise DAV_NotFound
def rm(self, uri):
""" delete a single resource
return a success code or raise an exception
"""
raise DAV_NotFound
"""
COPY/MOVE HANDLER
These handler are called when a COPY or MOVE method is invoked by
a client. In the default implementation it works as follows:
- the davserver receives a COPY/MOVE method
- the davcopy or davmove module will be loaded and the corresponding
class will be initialized
- this class parses the query and decides which method of the interface class
to call:
copyone for a single resource to copy
copytree for a tree to copy (collection)
(the same goes for move of course).
- the interface class has now two options:
1. to handle the action directly (e.g. cp or mv on filesystems)
2. to let it handle via the copy/move methods in davcmd.
ad 1) The first approach can be used when we know that no error can
happen inside a tree or when the action can exactly tell which
element made which error. We have to collect these and return
it in a dict of the form {uri: error_code, ...}
ad 2) The copytree/movetree/... methods of davcmd.py will do the recursion
themselves and call for each resource the copy/move method of the
interface class. Thus method will then only act on a single resource.
(Thus a copycol on a normal unix filesystem actually only needs to do
an mkdir as the content will be copied by the davcmd.py function.
The davcmd.py method will also automatically collect all errors and
return the dictionary described above.
When you use 2) you also have to implement the copy() and copycol()
methods in your interface class. See the example for details.
To decide which approach is the best you have to decide if your application
is able to generate errors inside a tree. E.g. a function which completely
fails on a tree if one of the tree's childs fail is not what we need. Then
2) would be your way of doing it.
Actually usually 2) is the better solution and should only be replaced by
1) if you really need it.
The remaining question is if we should do the same for the DELETE method.
"""
### MOVE handlers
def moveone(self, src, dst, overwrite):
""" move one resource with Depth=0 """
return moveone(self, src, dst, overwrite)
def movetree(self, src, dst, overwrite):
""" move a collection with Depth=infinity """
return movetree(self, src, dst, overwrite)
### COPY handlers
def copyone(self, src, dst, overwrite):
""" copy one resource with Depth=0 """
return copyone(self, src, dst, overwrite)
def copytree(self, src, dst, overwrite):
""" copy a collection with Depth=infinity """
return copytree(self, src, dst, overwrite)
### low level copy methods (you only need these for method 2)
def copy(self, src, dst):
""" copy a resource with depth==0
You don't need to bother about overwrite or not.
This has been done already.
return a success code or raise an exception if something fails
"""
return 201
def copycol(self, src, dst):
""" copy a resource with depth==infinity
You don't need to bother about overwrite or not.
This has been done already.
return a success code or raise an exception if something fails
"""
return 201
### some utility functions you need to implement
def exists(self, uri):
""" return 1 or None depending on if a resource exists """
return None # no
def is_collection(self, uri):
""" return 1 or None depending on if a resource is a collection """
return None # no
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-373
View File
@@ -1,373 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from xml.dom import ext
from xml.dom.Document import Document
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
def utf8str(st):
if isinstance(st,unicode):
return st.encode('utf8')
else:
return str(st)
class PROPFIND:
""" parse a propfind xml element and extract props
It will set the following instance vars:
request_class: ALLPROP | PROPNAME | PROP
proplist: list of properties
nsmap: map of namespaces
The list of properties will contain tuples of the form
(element name, ns_prefix, ns_uri)
"""
def __init__(self, uri, dataclass, depth):
self.request_type = None
self.nsmap = {}
self.proplist = {}
self.default_ns = None
self.__dataclass = dataclass
self.__depth = str(depth)
self.__uri = uri
self.use_full_urls = True
self.__has_body = None # did we parse a body?
def read_propfind(self, xml_doc):
self.request_type,self.proplist,self.namespaces = utils.parse_propfind(xml_doc)
# a violation of the expected logic: client (korganizer) will ask for DAV:resourcetype
# but we also have to return the http://groupdav.org/:resourcetype property!
if self.proplist.has_key('DAV:') and 'resourcetype' in self.proplist['DAV:']:
if not self.proplist.has_key('http://groupdav.org/'):
self.proplist['http://groupdav.org/'] = []
self.proplist['http://groupdav.org/'].append('resourcetype')
if 'DAV:' in self.namespaces: #TMP
self.namespaces.append('http://groupdav.org/')
def createResponse(self):
""" create the multistatus response
This will be delegated to the specific method
depending on which request (allprop, propname, prop)
was found.
If we get a PROPNAME then we simply return the list with empty
values which we get from the interface class
If we get an ALLPROP we first get the list of properties and then
we do the same as with a PROP method.
If the uri doesn't exist, return an xml response with a 404 status
"""
if not self.__dataclass.exists(self.__uri):
raise DAV_NotFound("Path %s doesn't exist" % self.__uri)
if self.request_type==RT_ALLPROP:
return self.create_allprop()
if self.request_type==RT_PROPNAME:
return self.create_propname()
if self.request_type==RT_PROP:
return self.create_prop()
# no body means ALLPROP!
return self.create_allprop()
def create_propname(self):
""" create a multistatus response for the prop names """
dc = self.__dataclass
# create the document generator
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
if self.__depth=="0":
pnames = dc.get_propnames(self.__uri)
re = self.mk_propname_response(self.__uri,pnames,doc)
ms.appendChild(re)
elif self.__depth=="1":
pnames = dc.get_propnames(self.__uri)
re = self.mk_propname_response(self.__uri,pnames,doc)
ms.appendChild(re)
for newuri in dc.get_childs(self.__uri):
pnames = dc.get_propnames(newuri)
re = self.mk_propname_response(newuri,pnames,doc)
ms.appendChild(re)
# *** depth=="infinity"
sfile = StringIO()
ext.PrettyPrint(doc,stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
def create_allprop(self):
""" return a list of all properties """
self.proplist = {}
self.namespaces = []
for ns,plist in self.__dataclass.get_propnames(self.__uri).items():
self.proplist[ns] = plist
self.namespaces.append(ns)
return self.create_prop()
def create_prop(self):
""" handle a <prop> request
This will
1. set up the <multistatus>-Framework
2. read the property values for each URI
(which is dependant on the Depth header)
This is done by the get_propvalues() method.
3. For each URI call the append_result() method
to append the actual <result>-Tag to the result
document.
We differ between "good" properties, which have been
assigned a value by the interface class and "bad"
properties, which resulted in an error, either 404
(Not Found) or 403 (Forbidden).
"""
# create the document generator
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
if self.__depth=="0":
gp,bp = self.get_propvalues(self.__uri)
res = self.mk_prop_response(self.__uri,gp,bp,doc)
ms.appendChild(res)
elif self.__depth=="1":
gp,bp = self.get_propvalues(self.__uri)
res = self.mk_prop_response(self.__uri,gp,bp,doc)
ms.appendChild(res)
try:
for newuri in self.__dataclass.get_childs(self.__uri):
gp,bp = self.get_propvalues(newuri)
res = self.mk_prop_response(newuri,gp,bp,doc)
ms.appendChild(res)
except DAV_NotFound:
# If no children, never mind.
pass
sfile = StringIO()
ext.PrettyPrint(doc,stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
def mk_propname_response(self,uri,propnames,doc):
""" make a new <prop> result element for a PROPNAME request
This will simply format the propnames list.
propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
"""
re = doc.createElement("D:response")
# write href information
href = doc.createElement("D:href")
if self.use_full_urls:
huri = doc.createTextNode(uri)
else:
uparts = urlparse.urlparse(uri)
fileloc = uparts[2]
huri = doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
href.appendChild(huri)
re.appendChild(href)
ps = doc.createElement("D:propstat")
nsnum = 0
for ns,plist in propnames.items():
# write prop element
pr = doc.createElement("D:prop")
nsp = "ns"+str(nsnum)
pr.setAttribute("xmlns:"+nsp,ns)
nsnum = nsnum+1
# write propertynames
for p in plist:
pe = doc.createElement(nsp+":"+p)
pr.appendChild(pe)
ps.appendChild(pr)
re.appendChild(ps)
return re
def mk_prop_response(self,uri,good_props,bad_props,doc):
""" make a new <prop> result element
We differ between the good props and the bad ones for
each generating an extra <propstat>-Node (for each error
one, that means).
"""
re = doc.createElement("D:response")
# append namespaces to response
nsnum = 0
for nsname in self.namespaces:
re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum = nsnum+1
# write href information
href = doc.createElement("D:href")
if self.use_full_urls:
huri = doc.createTextNode(uri)
else:
uparts = urlparse.urlparse(uri)
fileloc = uparts[2]
huri = doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
href.appendChild(huri)
re.appendChild(href)
# write good properties
if good_props and len(good_props.items()):
ps = doc.createElement("D:propstat")
gp = doc.createElement("D:prop")
for ns in good_props.keys():
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
for p,v in good_props[ns].items():
pe = doc.createElement(ns_prefix+str(p))
if v == None:
pass
elif ns=='DAV:' and p=="resourcetype":
if v == 1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
elif isinstance(v,tuple) and v[1] == ns:
ve=doc.createElement(ns_prefix+v[0])
pe.appendChild(ve)
else:
ve = doc.createTextNode(utf8str(v))
pe.appendChild(ve)
gp.appendChild(pe)
if gp.hasChildNodes():
re.appendChild(ps)
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!
if len(bad_props.items()):
# write a propstat for each error code
for ecode in bad_props.keys():
ps = doc.createElement("D:propstat")
re.appendChild(ps)
bp = doc.createElement("D:prop")
ps.appendChild(bp)
for ns in bad_props[ecode].keys():
ns_prefix = "ns"+str(self.namespaces.index(ns))+":"
for p in bad_props[ecode][ns]:
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
return re
def get_propvalues(self,uri):
""" create lists of property values for an URI
We create two lists for an URI: the properties for
which we found a value and the ones for which we
only got an error, either because they haven't been
found or the user is not allowed to read them.
"""
good_props = {}
bad_props = {}
for (ns,plist) in self.proplist.items():
good_props[ns] = {}
bad_props = {}
ec = 0
for prop in plist:
try:
ec = 0
r = self.__dataclass.get_prop(uri,ns,prop)
good_props[ns][prop] = r
except DAV_Error, error_code:
ec = error_code[0]
# ignore props with error_code if 0 (invisible)
if ec==0: continue
if bad_props.has_key(ec):
if bad_props[ec].has_key(ns):
bad_props[ec][ns].append(prop)
else:
bad_props[ec][ns] = [prop]
else:
bad_props[ec] = {ns:[prop]}
return good_props, bad_props
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-31
View File
@@ -1,31 +0,0 @@
"""
status codes for DAV services
"""
STATUS_CODES={
102: "Processing",
200: "Ok",
201: "Created",
204: "No Content",
207: "Multi-Status",
201: "Created",
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
409: "Conflict",
412: "Precondition failed",
423: "Locked",
415: "Unsupported Media Type",
507: "Insufficient Storage",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
502: "Bad Gateway",
507: "Insufficient Storage",
999: "Some error in Create Method please check the data of create method"
}
-161
View File
@@ -1,161 +0,0 @@
#!/usr/bin/env python
"""
UTILITIES
- parse a propfind request body into a list of props
"""
from xml.dom import ext
from xml.dom.Document import Document
from xml.dom.ext.reader import PyExpat
from xml.dom import Node
from xml.dom import NodeIterator, NodeFilter
from string import lower, split, atoi, joinfields
import urlparse
from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from status import STATUS_CODES
VERSION = '0.6'
AUTHOR = 'Simon Pamies <s.pamies@banality.de>'
def gen_estring(ecode):
""" generate an error string from the given code """
ec = atoi(str(ecode))
if STATUS_CODES.has_key(ec):
return "HTTP/1.1 %s %s" %(ec,STATUS_CODES[ec])
else:
return "HTTP/1.1 %s" %(ec)
def parse_propfind(xml_doc):
""" parse an propfind xml file and return a list of props
returns:
request_type -- ALLPROP, PROPNAME, PROP
proplist -- list of properties found
namespaces -- list of namespaces found
"""
doc = PyExpat.Reader().fromString(xml_doc)
snit = doc.createNodeIterator(doc, NodeFilter.NodeFilter.SHOW_ELEMENT, None, None)
request_type = None
props = {}
namespaces = []
while 1:
curr_elem = snit.nextNode()
if not curr_elem: break
ename=fname = lower(curr_elem.nodeName)
if ":" in fname:
ename = split(fname,":")[1]
if ename=="prop": request_type = RT_PROP; continue
if ename=="propfind": continue
if ename=="allprop": request_type = RT_ALLPROP; continue
if ename=="propname": request_type = RT_PROPNAME; continue
# rest should be names of attributes
ns = curr_elem.namespaceURI
if props.has_key(ns):
props[ns].append(ename)
else:
props[ns] = [ename]
namespaces.append(ns)
return request_type,props,namespaces
def create_treelist(dataclass, uri):
""" create a list of resources out of a tree
This function is used for the COPY, MOVE and DELETE methods
uri - the root of the subtree to flatten
It will return the flattened tree as list
"""
queue = [uri]
list = [uri]
while len(queue):
element = queue[-1]
if dataclass.is_collection(element):
childs = dataclass.get_childs(element)
else:
childs = []
if len(childs):
list = list+childs
# update queue
del queue[-1]
if len(childs):
queue = queue+childs
return list
def is_prefix(uri1, uri2):
""" returns 1 of uri1 is a prefix of uri2 """
if uri2[:len(uri1)]==uri1:
return 1
else:
return None
def quote_uri(uri):
""" quote an URL but not the protocol part """
import urlparse
import urllib
up = urlparse.urlparse(uri)
np = urllib.quote(up[2])
return urlparse.urlunparse((up[0], up[1], np, up[3], up[4], up[5]))
def get_uriparentpath(uri):
""" extract the uri path and remove the last element """
up = urlparse.urlparse(uri)
return joinfields(split(up[2], "/")[:-1], "/")
def get_urifilename(uri):
""" extract the uri path and return the last element """
up = urlparse.urlparse(uri)
return split(up[2], "/")[-1]
def get_parenturi(uri):
""" return the parent of the given resource"""
up = urlparse.urlparse(uri)
np = joinfields(split(up[2], "/")[:-1], "/")
return urlparse.urlunparse((up[0], up[1], np, up[3], up[4], up[5]))
### XML utilities
def make_xmlresponse(result):
""" construct a response from a dict of uri:error_code elements """
doc = Document(None)
ms = doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D", "DAV:")
doc.appendChild(ms)
for el, ec in result.items():
re = doc.createElement("D:response")
hr = doc.createElement("D:href")
st = doc.createElement("D:status")
huri = doc.createTextNode(quote_uri(el))
t = doc.createTextNode(gen_estring(ec))
st.appendChild(t)
hr.appendChild(huri)
re.appendChild(hr)
re.appendChild(st)
ms.appendChild(re)
sfile = StringIO()
ext.PrettyPrint(doc, stream = sfile)
s = sfile.getvalue()
sfile.close()
return s
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+2 -4
View File
@@ -19,11 +19,9 @@
#
##############################################################################
import calendar
import calendar_collection
import caldav
import caldav_cache
import caldav_fs
import caldav_node
import webdav_server
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+2 -1
View File
@@ -25,6 +25,7 @@
"version" : "1.0",
"depends" : [
"base",
"document_webdav",
],
'description': """
This module Contains basic functionality for caldav system like:
@@ -34,7 +35,7 @@
- Provides iCal Import/Export functionality
To access OpenERP Calendar using caldav to remote site use the URL like:
http://HOSTNAME:PORT/calendar/DATABASE_NAME/CALENDAR_NAME.ics
http://HOSTNAME:PORT/webdav/DATABASE_NAME/Calendars/CALENDAR_NAME.ics
Where,
HOSTNAME: Host on which OpenERP server(With webdav) is running
+20 -1003
View File
File diff suppressed because it is too large Load Diff
-62
View File
@@ -1,62 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# 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 time
import heapq
def memoize(maxsize):
"""decorator to 'memoize' a function - caching its results"""
def decorating_function(f):
cache = {} # map from key to value
heap = [] # list of keys, in LRU heap
cursize = 0 # because len() is slow
def wrapper(*args):
key = repr(args)
# performance crap
_cache = cache
_heap = heap
_heappop = heapq.heappop
_heappush = heapq.heappush
_time = time.time
_cursize = cursize
_maxsize = maxsize
if not _cache.has_key(key):
if _cursize == _maxsize:
# pop oldest element
(_, oldkey) = _heappop(_heap)
_cache.pop(oldkey)
else:
_cursize += 1
# insert this element
_cache[key] = f(*args)
_heappush(_heap, (_time(), key))
wrapper.misses += 1
else:
wrapper.hits += 1
return cache[key]
wrapper.__doc__ = f.__doc__
wrapper.__name__ = f.__name__
wrapper.hits = wrapper.misses = 0
return wrapper
return decorating_function
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+380
View File
@@ -0,0 +1,380 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record model="document.directory" id="document.dir_calendars">
<field name="name">Calendars</field>
<field name="calendar_collection">True</field>
</record>
<!-- Event attributes-->
<record model="basic.calendar.attributes" id="field_event_comment">
<field name="name">comment</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_uid">
<field name="name">uid</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_seq">
<field name="name">seq</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_recurrence-id">
<field name="name">recurrence-id</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_transp">
<field name="name">transp</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_attendee">
<field name="name">attendee</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_related">
<field name="name">related</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_rrule">
<field name="name">rrule</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_dtend">
<field name="name">dtend</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_valarm">
<field name="name">valarm</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_vtimezone">
<field name="name">vtimezone</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_priority">
<field name="name">priority</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_location">
<field name="name">location</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_exrule">
<field name="name">exrule</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_resources">
<field name="name">resources</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_rstatus">
<field name="name">rstatus</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_status">
<field name="name">status</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_exdate">
<field name="name">exdate</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_dtstamp">
<field name="name">dtstamp</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_description">
<field name="name">description</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_rdate">
<field name="name">rdate</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_dtstart">
<field name="name">dtstart</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_class">
<field name="name">class</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_x-openobject-model">
<field name="name">x-openobject-model</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_created">
<field name="name">created</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_url">
<field name="name">url</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_summary">
<field name="name">summary</field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.attributes" id="field_event_contact">
<field name="name">contact</field>
<field name="type">vevent</field>
</record>
<!-- Todo attributes-->
<record model="basic.calendar.attributes" id="field_todo_status">
<field name="name">status</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_comment">
<field name="name">comment</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_attendee">
<field name="name">attendee</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_valarm">
<field name="name">valarm</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_description">
<field name="name">description</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_seq">
<field name="name">seq</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_vtimezone">
<field name="name">vtimezone</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_url">
<field name="name">url</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_completed">
<field name="name">completed</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_percent">
<field name="name">percent</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_due">
<field name="name">due</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_summary">
<field name="name">summary</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_priority">
<field name="name">priority</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_exdate">
<field name="name">exdate</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_location">
<field name="name">location</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_exrule">
<field name="name">exrule</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_duration">
<field name="name">duration</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_organizer">
<field name="name">organizer</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_dtstart">
<field name="name">dtstart</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_rrule">
<field name="name">rrule</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_class">
<field name="name">class</field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.attributes" id="field_todo_uid">
<field name="name">uid</field>
<field name="type">vtodo</field>
</record>
<!-- Attendee's attributes-->
<record model="basic.calendar.attributes" id="field_attendee_cn">
<field name="name">cn</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_sent-by">
<field name="name">sent-by</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_language">
<field name="name">language</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_delegated-from">
<field name="name">delegated-from</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_member">
<field name="name">member</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_cutype">
<field name="name">cutype</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_role">
<field name="name">role</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_partstat">
<field name="name">partstat</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_delegated-to">
<field name="name">delegated-to</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_dir">
<field name="name">dir</field>
<field name="type">attendee</field>
</record>
<record model="basic.calendar.attributes" id="field_attendee_rsvp">
<field name="name">rsvp</field>
<field name="type">attendee</field>
</record>
<!-- Alarm attributes-->
<record model="basic.calendar.attributes" id="field_alarm_attendee">
<field name="name">attendee</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_duration">
<field name="name">trigger_duration</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_description">
<field name="name">description</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_attach">
<field name="name">attach</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_occurs">
<field name="name">trigger_occurs</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_interval">
<field name="name">trigger_interval</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_summary">
<field name="name">summary</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_duration">
<field name="name">duration</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_repeat">
<field name="name">repeat</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_action">
<field name="name">action</field>
<field name="type">alarm</field>
</record>
<record model="basic.calendar.attributes" id="field_alarm_trigger_related">
<field name="name">trigger_related</field>
<field name="type">alarm</field>
</record>
</data>
</openerp>
-334
View File
@@ -1,334 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# 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 pooler
import base64
import sys
import os
import time
from string import joinfields, split, lower
from service import security
import netsvc
import urlparse
from DAV.constants import COLLECTION, OBJECT
from DAV.errors import *
from DAV.iface import *
import urllib
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
from caldav_cache import memoize
from tools import misc
CACHE_SIZE=20000
class tinydav_handler(dav_interface):
"""
This class models a Tiny ERP interface for the DAV server
"""
PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
M_NS={ "DAV:": dav_interface.M_NS['DAV:'], }
def __init__(self, parent, verbose=False):
self.db_name = False
self.parent = parent
self.baseuri = parent.baseuri
def get_propnames(self, uri):
props = self.PROPS
self.parent.log_message('get propnames: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return props
node = self.uri2object(cr, uid, pool, uri2)
if node:
props.update(node.get_dav_props(cr))
cr.close()
return props
def get_prop(self,uri,ns,propname):
""" return the value of a given property
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
return dav_interface.get_prop(self, uri, ns, propname)
if uri[-1]=='/':uri = uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
raise DAV_NotFound
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound
res = node.get_dav_eprop(cr, ns, propname)
cr.close()
return res
def urijoin(self,*ajoin):
""" Return the base URI of this request, or even join it with the
ajoin path elements
"""
return self.baseuri+ '/'.join(ajoin)
def uri2local(self, uri):
uparts = urlparse.urlparse(uri)
reluri = uparts[2]
if reluri and reluri[-1]=="/":
reluri = reluri[:-1]
return reluri
#
# pos: -1 to get the parent of the uri
#
def get_cr(self, uri):
pdb = self.parent.auth_proxy.last_auth
reluri = self.uri2local(uri)
try:
dbname = reluri.split('/')[2]
except:
dbname = False
if not dbname:
return None, None, None, False, None
if not pdb and dbname:
# if dbname was in our uri, we should have authenticated
# against that.
raise Exception("Programming error")
assert pdb == dbname, " %s != %s" %(pdb, dbname)
user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
db,pool = pooler.get_db_and_pool(dbname)
cr = db.cursor()
uri2 = reluri.split('/')[3:]
return cr, uid, pool, dbname, uri2
def uri2object(self, cr, uid, pool, uri):
if not uid:
return None
return pool.get('basic.calendar').get_calendar_object(cr, uid, uri)
def get_data(self, uri):
self.parent.log_message('GET: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
try:
datas = node.get_data(cr, uid)
except TypeError, e:
import traceback
self.parent.log_error("GET typeError: %s", str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Forbidden
except IndexError, e :
self.parent.log_error("GET IndexError: %s", str(e))
raise DAV_NotFound(uri2)
except Exception, e:
import traceback
self.parent.log_error("GET exception: %s", str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Error, 409
return datas
finally:
cr.close()
@memoize(CACHE_SIZE)
def _get_dav_resourcetype(self, uri):
""" return type of object """
self.parent.log_message('get RT: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return COLLECTION
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
return OBJECT
finally:
cr.close()
def _get_dav_displayname(self, uri):
self.parent.log_message('get DN: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return COLLECTION
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
cr.close()
return node.displayname
@memoize(CACHE_SIZE)
def _get_dav_getcontentlength(self, uri):
""" return the content length of an object """
self.parent.log_message('get length: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return '0'
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
result = node.content_length or 0
cr.close()
return str(result)
@memoize(CACHE_SIZE)
def _get_dav_getetag(self, uri):
""" return the ETag of an object """
self.parent.log_message('get etag: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return '0'
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound(uri2)
result = node.get_etag(cr)
cr.close()
return str(result)
@memoize(CACHE_SIZE)
def get_lastmodified(self, uri):
""" return the last modified date of the object """
if uri[-1]=='/':uri=uri[:-1]
today = time.time()
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return today
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
if node.write_date:
return time.mktime(time.strptime(node.write_date, '%Y-%m-%d %H:%M:%S'))
else:
return today
finally:
cr.close()
@memoize(CACHE_SIZE)
def get_creationdate(self, uri):
""" return the last modified date of the object """
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
if node.create_date:
result = time.strptime(node.create_date, '%Y-%m-%d %H:%M:%S')
else:
result = time.gmtime()
return result
finally:
cr.close()
@memoize(CACHE_SIZE)
def _get_dav_getcontenttype(self, uri):
self.parent.log_message('get contenttype: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return 'httpd/unix-directory'
node = self.uri2object(cr, uid, pool, uri2)
if not node:
raise DAV_NotFound(uri2)
result = node.mimetype
return result
#raise DAV_NotFound, 'Could not find %s' % path
finally:
cr.close()
def put(self, uri, data, content_type=None):
""" put the object into the filesystem """
self.parent.log_message('Putting %s (%d), %s'%(misc.ustr(uri), len(data), content_type))
parent='/'.join(uri.split('/')[:-1])
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
raise DAV_Forbidden
try:
node = self.uri2object(cr, uid, pool, uri2[:])
except:
node = False
if not node:
raise DAV_Forbidden
else:
try:
node.set_data(cr, uid, data)
except Exception, e:
import traceback
self.parent.log_error("Cannot save :%s", str(e))
self.parent.log_message("Exc: %s", traceback.format_exc())
raise DAV_Forbidden
cr.commit()
cr.close()
return 201
def exists(self, uri):
""" test if a resource exists """
result = False
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return True
try:
node = self.uri2object(cr, uid, pool, uri2)
if node:
result = True
except:
pass
cr.close()
return result
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+467 -33
View File
@@ -25,31 +25,104 @@ import pooler
import tools
import time
import base64
from document 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
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
class node_calendar_collection(nodes.node_dir):
PROPS = {
"http://calendarserver.org/ns/" : ('getctag'),
}
M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
}
class node_calendar(object):
def __init__(self, path, context, calendar):
self.path = path
self.context = context
self.calendar_id = calendar.id
self.mimetype = 'ics'
self.create_date = calendar.create_date
self.write_date = calendar.write_date or calendar.create_date
self.content_length = 0
self.displayname = calendar.name
def get_dav_props(self, cr):
return self.PROPS
def get_data(self, cr, uid):
calendar_obj = pooler.get_pool(cr.dbname).get('basic.calendar')
return calendar_obj.export_cal(cr, uid, [self.calendar_id])
def get_dav_eprop(self,cr, ns, propname):
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
return None
def get_data_len(self, cr):
return self.content_length
mname = prefix + "_" + propname
def set_data(self, cr, uid, data):
calendar_obj = pooler.get_pool(cr.dbname).get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
if not hasattr(self, mname):
return None
try:
m = getattr(self, mname)
r = m(cr)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
return None
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:
res = name.split('.ics')
if len(res) > 1:
name = res[0]
ext = '.ics'
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 = []
for calender in fil_obj.browse(cr, uid, ids, context=ctx):
if not ext:
res.append(node_calendar(calender.name, self, self.context, calender))
else:
res.append(res_node_calendar(name, self, self.context, calender))
return res
def _get_dav_owner(self, cr):
return False
def get_etag(self, cr):
""" Get a tag, unique per object + modification.
@@ -64,24 +137,385 @@ class node_calendar(object):
return str(wtime)
def _get_ttag(self, cr):
return 'calendar-%d' % self.calendar_id
return 'calendar collection-%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'
PROPS = {
"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',)}
M_NS = {
"DAV:" : '_get_dav',
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
def __init__(self,path, parent, context, calendar):
super(node_calendar,self).__init__(path, parent,context)
self.calendar_id = calendar.id
self.mimetype = 'application/x-directory'
self.create_date = calendar.create_date
self.write_date = calendar.write_date or calendar.create_date
self.content_length = 0
self.displayname = calendar.name
self.cal_type = calendar.type
def _get_dav_getctag(self, cr):
result = self._get_ttag(cr) + ':' + str(time.time())
return str(result)
def match_dav_eprop(self, cr, match, ns, prop):
if ns == "DAV:" and prop == "getetag":
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
tem, dav_time = tuple(match.split(':'))
model, res_id = tuple(tem.split('_'))
model_obj = dirobj.pool.get(model)
model = model_obj.browse(cr, uid, res_id, context=ctx)
write_time = model.write_date or model.create_date
wtime = time.mktime(time.strptime(write_time,'%Y-%m-%d %H:%M:%S'))
if float(dav_time) == float(wtime):
return True
return False
res = super(node_calendar, self).match_dav_eprop(cr, match, ns, prop)
return res
def get_domain(self, cr, filters):
res = []
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
calendar_obj = dirobj.pool.get('basic.calendar')
if not filters:
return res
if filters.localName == 'calendar-query':
res = []
for filter_child in filters.childNodes:
if filter_child.nodeType == filter_child.TEXT_NODE:
continue
if filter_child.localName == 'filter':
for vcalendar_filter in filter_child.childNodes:
if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
continue
if vcalendar_filter.localName == 'comp-filter':
if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
for vevent_filter in vcalendar_filter.childNodes:
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')]
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)
res = [('name','in',names)]
return res
return res
def children(self, cr, domain=None):
return self._child_get(cr, domain=domain)
def child(self,cr, name, domain=None):
res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
return None
class Calendar(osv.osv):
_inherit = 'basic.calendar'
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 = []
if name:
where.append(('id','=',int(name)))
if not domain:
domain = []
#for opr1, opt, opr2 in domain:
# if opr1 == 'type' and opr2 != self.cal_type:
# return []
def get_calendar_object(self, cr, uid, uri, context=None):
if not uri:
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)
return res
def get_dav_props(self, cr):
return self.PROPS
def get_dav_eprop(self,cr, ns, propname):
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
return None
if len(uri) > 1:
propname = propname.replace('-','_')
mname = prefix + "_" + propname
if not hasattr(self, mname):
return None
name, file_type = tuple(uri[0].split('.'))
res = self.name_search(cr, uid, name)
if not res:
return None
calendar_id, calendar_name = res[0]
calendar = self.browse(cr, uid, calendar_id)
return node_calendar(uri, context, calendar)
Calendar()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4
try:
m = getattr(self, mname)
r = m(cr)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
return None
def create_child(self,cr,path,data):
""" API function to create a child file object and node
Return the node_* created
"""
return self.set_data(cr, data)
def set_data(self, cr, data, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
def get_data_len(self, cr, fil_obj = None):
return self.content_length
def _get_ttag(self,cr):
return 'calendar-%d' % (self.calendar_id,)
def get_etag(self, cr):
""" 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)
def _get_wtag(self, cr):
""" Return the modification time as a unique, compact string """
if self.write_date:
wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
else: wtime = time.time()
return str(wtime)
def rmcol(self, cr):
return False
class res_node_calendar(nodes.node_class):
our_type = 'file'
PROPS = {
"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',)}
M_NS = {
"http://calendarserver.org/ns/" : '_get_dav',
"urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
def __init__(self,path, parent, context, res_obj, res_model=None, res_id=None):
super(res_node_calendar,self).__init__(path, parent, context)
self.mimetype = 'text/calendar'
self.create_date = parent.create_date
self.write_date = parent.write_date or parent.create_date
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
self.create_date = res_obj.create_date
self.write_date = res_obj.write_date or res_obj.create_date
self.displayname = res_obj.name
self.content_length = 0
self.model = res_model
self.res_id = res_id
def open(self, cr, mode=False):
uid = self.context.uid
if self.type in ('collection','database'):
return False
s = StringIO.StringIO(self.get_data(cr))
s.name = self
return s
def get_dav_props(self, cr):
return self.PROPS
def get_dav_eprop(self,cr, ns, propname):
if self.M_NS.has_key(ns):
prefix = self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
return None
propname = propname.replace('-','_')
mname = prefix + "_" + propname
if not hasattr(self, mname):
return None
try:
m = getattr(self, mname)
r = m(cr)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
return None
def get_data(self, cr, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
context = self.context.context.copy()
context.update({'model': self.model, 'res_id':self.res_id})
res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
return res
def get_data_len(self, cr, fil_obj = None):
return self.content_length
def set_data(self, cr, data, fil_obj = None):
uid = self.context.uid
calendar_obj = self.context._dirobj.pool.get('basic.calendar')
return calendar_obj.import_cal(cr, uid, base64.encodestring(data), self.calendar_id)
def _get_ttag(self,cr):
res = False
if self.model and self.res_id:
res = '%s_%d' % (self.model, self.res_id)
elif self.calendar_id:
res = '%d' % (self.calendar_id)
return res
def _get_caldav_calendar_data(self, cr):
return self.get_data(cr)
def _get_caldav_calendar_description(self, cr):
uid = self.context.uid
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
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
def _get_caldav_calendar_user_address_set(self, cr):
import xml.dom.minidom
dirobj = self.context._dirobj
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:' + user.email)
href.appendChild(huri)
return href
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.name, calendar.collection_id.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 rm(self, cr):
uid = self.context.uid
res = False
if self.type in ('collection','database'):
return False
if self.model and self.res_id:
document_obj = self.context._dirobj.pool.get(self.model)
if document_obj:
res = False
#res = document_obj.unlink(cr, uid, [self.res_id]) #TOFIX
return res
def _get_caldav_schedule_outbox_URL(self, cr):
return self._get_caldav_schedule_inbox_URL(cr)
def get_etag(self, cr):
""" 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)
def _get_wtag(self, cr):
""" Return the modification time as a unique, compact string """
if self.write_date:
wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
else: wtime = time.time()
return str(wtime)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4
+138 -48
View File
@@ -1,61 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data>
<record model="ir.ui.view" id="view_calendar_collection_form">
<field name="name">Calendar Collections : Form</field>
<field name="model">document.directory</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Calendar Collections">
<field name="name" select="1" colspan="4"/>
<field name="user_id"/>
<field name="parent_id"/>
<field name="calendar_ids" colspan="4" nolabel="1"/>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_calendar_collection_tree">
<field name="name">Calendar Collections : Tree</field>
<field name="model">document.directory</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Calendar Collections" toolbar="1">
<field name="name"/>
<field name="user_id"/>
<field name="create_date"/>
<field name="write_date"/>
</tree>
</field>
</record>
<record id="view_calendar_form" model="ir.ui.view">
<field name="name">Basic Calendar</field>
<record model="ir.actions.act_window" id="action_calendar_collection_form">
<field name="name">Calendar Collections</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">document.directory</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('calendar_collection','=',True)]</field>
</record>
<record model="ir.actions.act_window" id="document.action_document_directory_form">
<field name="domain">[('calendar_collection','=',False)]</field>
</record>
<record id="action_dir_view1" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_calendar_collection_tree"/>
<field name="act_window_id" ref="action_calendar_collection_form"/>
</record>
<record id="action_dir_view2" model="ir.actions.act_window.view">
<field eval="20" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_calendar_collection_form"/>
<field name="act_window_id" ref="action_calendar_collection_form"/>
</record>
<menuitem
id="menu_calendar"
name="Calendar"
parent="base.menu_document_configuration"/>
<menuitem
action="action_calendar_collection_form"
id="menu_calendar_collection"
parent="menu_calendar"/>
<record model="ir.ui.view" id="view_caldav_form">
<field name="name">Calendar : Form</field>
<field name="model">basic.calendar</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Basic Calendar">
<field name="name" required="1" select="1" />
<field name="active" select="1" />
<form string="Calendar">
<field name="name"/>
<field name="type"/>
<field name="user_id"/>
<field name="collection_id" required="1"/>
<field name="line_ids" mode="form,tree" colspan="4" nolabel="1">
<form string="Calendar Lines">
<field name="name" required="1" select="1" />
<field name="object_id" required="1" select="1" />
<field name="domain" select="1" />
<field name="mapping_ids" select="1" colspan="4" nolabel="1">
<tree string="Attributes Mapping" editable="bottom">
<field name="name" required="1" domain="[('type', '=', parent.name)]"/>
<field name="fn" select="1" />
<field name="field_id" select="1" required="1" domain="[('model_id', '=', parent.object_id)]" />
<field name="expr" />
</tree>
<form string="Attributes Mapping">
<field name="name" select="1" required="1" domain="[('type', '=', parent.name)]"/>
<field name="field_id" select="1" domain="[('model_id', '=', parent.object_id)]"
required="1" />
<field name="fn" select="1" required="1" />
<field name="expr" />
<separator string="Value Mapping" colspan="4" />
<field name="mapping" select="1" colspan="4" nolabel="1" />
</form>
</field>
</form>
<tree string="Attributes Mapping" editable="bottom">
<field name="name" select="1" />
<field name="object_id" select="1" />
</tree>
<form string="Calendar Lines">
<field name="name" required="1" select="1" />
<field name="object_id" required="1" select="1" />
<field name="domain" select="1" />
<field name="mapping_ids" select="1" colspan="4" nolabel="1">
<tree string="Attributes Mapping" editable="bottom">
<field name="name" required="1" domain="[('type', '=', parent.name)]"/>
<field name="fn" select="1" />
<field name="field_id" select="1" required="1" domain="[('model_id', '=', parent.object_id)]" />
<field name="expr" />
</tree>
<form string="Attributes Mapping">
<field name="name" select="1" required="1" domain="[('type', '=', parent.name)]"/>
<field name="field_id" select="1" domain="[('model_id', '=', parent.object_id)]"
required="1" />
<field name="fn" select="1" required="1" />
<field name="expr" />
<separator string="Value Mapping" colspan="4" />
<field name="mapping" select="1" colspan="4" nolabel="1" />
</form>
</field>
</form>
<tree string="Calendar Lines" editable="bottom">
<field name="name" select="1" />
<field name="object_id" select="1" />
</tree>
</field>
</form>
</field>
</record>
</form>
</field>
</record>
<record id="action_view_calendar" model="ir.actions.act_window">
<field name="name">Calendar</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">basic.calendar</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="base.menu_calendar_configuration" name="Calendar"
parent="base.menu_base_config" sequence="10" />
<record model="ir.ui.view" id="view_caldav_tree">
<field name="name">Calendar : Tree</field>
<field name="model">basic.calendar</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Calendars" toolbar="1">
<field name="name"/>
<field name="type"/>
<field name="user_id"/>
<field name="create_date"/>
<field name="write_date"/>
</tree>
</field>
</record>
<menuitem id="menu_calendar"
name="Calendar" parent="base.menu_calendar_configuration"
sequence="5" action="action_view_calendar" />
<record model="ir.actions.act_window" id="action_caldav_form">
<field name="name">Calendars</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">basic.calendar</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
</data>
<record id="action_caldav_view1" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_caldav_tree"/>
<field name="act_window_id" ref="action_caldav_form"/>
</record>
<record id="action_caldav_view2" model="ir.actions.act_window.view">
<field eval="20" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_caldav_form"/>
<field name="act_window_id" ref="action_caldav_form"/>
</record>
<menuitem
action="action_caldav_form"
id="menu_caldav_directories"
parent="menu_calendar"/>
</data>
</openerp>
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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/>.
#
##############################################################################
from osv import osv, fields
from tools.translate import _
import caldav_node
class calendar_collection(osv.osv):
_inherit = 'document.directory'
_columns = {
'calendar_collection' : fields.boolean('Calendar Collection'),
'calendar_ids': fields.one2many('basic.calendar', 'collection_id', 'Calendars'),
}
_default = {
'calendar_collection' : False,
}
def _get_root_calendar_directory(self, cr, uid, context=None):
objid = self.pool.get('ir.model.data')
try:
mid = objid._get_id(cr, uid, 'document', 'dir_calendars')
if not mid:
return False
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))
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_description(self, cr, uid, ids, context=None):
#TODO : return description of all calendars
if not context:
context = {}
return False
def get_schedule_inbox_URL(self, cr, uid, ids, context=None):
calendar_obj = self.pool.get('basic.calendar')
calendar_ids = calendar_obj.search(cr, uid, [
('user_id', '=', uid), ('collection_id', 'in', ids)
], limit=1, context=context)
root_cal_dir = self._get_root_calendar_directory(cr, uid, context=context)
if not calendar_ids:
return root_cal_dir
calendar_id = calendar_ids[0]
calendar = calendar_obj.browse(cr, uid, calendar_id,
context=context)
return '%s/%s' %(root_cal_dir, calendar.name)
def get_schedule_outbox_URL(self, cr, uid, ids, context=None):
return self.get_schedule_inbox_URL(cr, uid, ids, context=context)
calendar_collection()
@@ -1,5 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_basic_calendar_all","basic.calendar","model_basic_calendar","base.group_user",1,1,1,1
"access_basic_calendar_event_all","basic.calendar.event","model_basic_calendar_event","base.group_user",1,1,1,0
"access_basic_calendar_attendee_all","basic.calendar.attendee","model_basic_calendar_attendee","base.group_user",1,1,1,0
"access_calendar_todo_all","basic.calendar.todo","model_basic_calendar_todo","base.group_user",1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_basic_calendar_all basic.calendar model_basic_calendar base.group_user 1 1 1 1
2 access_basic_calendar_event_all basic.calendar.event model_basic_calendar_event base.group_user 1 1 1 0
3 access_basic_calendar_attendee_all basic.calendar.attendee model_basic_calendar_attendee base.group_user 1 1 1 0
4 access_calendar_todo_all basic.calendar.todo model_basic_calendar_todo base.group_user 1 1 1 0
-78
View File
@@ -1,78 +0,0 @@
# -*- encoding: utf-8 -*-
#
# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 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.
###############################################################################
import netsvc
from caldav_fs import tinydav_handler
from tools.config import config
from DAV.WebDAVServer import DAVRequestHandler
from service.websrv_lib import HTTPDir,FixSendError
class DAVHandler(FixSendError, DAVRequestHandler):
verbose = False
def get_userinfo(self, user, pw):
return False
def _log(self, message):
netsvc.Logger().notifyChannel("webdav", netsvc.LOG_DEBUG, message)
def handle(self):
pass
def finish(self):
pass
def setup(self):
davpath = '/calendar/'
self.baseuri = "http://%s:%d%s"% (self.server.server_name, self.server.server_port, davpath)
self.IFACE_CLASS = tinydav_handler(self)
pass
def log_message(self, format, *args):
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_DEBUG_RPC, format % args)
def log_error(self, format, *args):
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_WARNING, format % args)
try:
from service.http_server import reg_http_service, OpenERPAuthProvider
davpath = '/calendar/'
handler = DAVHandler
handler.verbose = config.get_misc('webdav', 'verbose', True)
handler.debug = config.get_misc('webdav', 'debug', True)
reg_http_service(HTTPDir(davpath, DAVHandler, OpenERPAuthProvider()))
netsvc.Logger().notifyChannel('webdav', netsvc.LOG_INFO, "WebDAV service registered at path: %s/ "% davpath)
except Exception, e:
logger = netsvc.Logger()
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4
@@ -46,7 +46,7 @@
<field name="target">new</field>
</record>
<act_window id="action_calendar_event_import_values"
<act_window id="action_calendar_event_import_values"
key2="client_action_multi" name="Import .ics File"
res_model="calendar.event.import" src_model="basic.calendar"
view_mode="form" target="new" view_type="form" />
@@ -52,4 +52,4 @@
view_mode="form" target="new" view_type="form" />
</data>
</openerp>
</openerp>
+8
View File
@@ -109,6 +109,7 @@ between mails and Open ERP.""",
'process/crm_configuration_process.xml',
],
'demo_xml': [
'test/test_crm_meeting.yml',
'crm_demo.xml',
'crm_claims_demo.xml',
'crm_fund_demo.xml',
@@ -118,6 +119,13 @@ between mails and Open ERP.""",
'crm_opportunity_demo.xml',
'crm_phonecall_demo.xml'
],
'test': ['test/test_crm_lead.yml',
'test/test_crm_opportunity.yml',
'test/test_crm_phonecall.yml',
'test/test_crm_fund.yml',
'test/test_crm_claim.yml',
'test/test_crm_helpdesk.yml',
],
'installable': True,
'active': False,
'certificate': '0079056041421',
+11 -8
View File
@@ -59,6 +59,7 @@ class crm_case_section(osv.osv):
true, it will allow you to hide the sales team without removing it."),
'allow_unlink': fields.boolean('Allow Delete', help="Allows to delete non draft cases"),
'user_id': fields.many2one('res.users', 'Responsible User'),
'member_ids':fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'),
'reply_to': fields.char('Reply-To', size=64, help="The email address put \
in the 'Reply-To' of all emails sent by Open ERP about cases in this sales team"),
'parent_id': fields.many2one('crm.case.section', 'Parent Team'),
@@ -66,6 +67,7 @@ class crm_case_section(osv.osv):
'resource_calendar_id': fields.many2one('resource.calendar', "Resource's Calendar"),
'server_id':fields.many2one('email.smtpclient', 'Server ID'),
'note': fields.text('Description'),
}
_defaults = {
@@ -78,15 +80,16 @@ class crm_case_section(osv.osv):
]
def _check_recursion(self, cr, uid, ids):
"""
Checks for recursion level for sections
Checks for recursion level for sales team
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of section ids
@param ids: List of Sales team ids
"""
level = 100
while len(ids):
cr.execute('select distinct parent_id from crm_case_section where id =ANY(%s)', (ids,))
ids = filter(None, map(lambda x: x[0], cr.fetchall()))
@@ -97,7 +100,7 @@ class crm_case_section(osv.osv):
return True
_constraints = [
(_check_recursion, 'Error ! You cannot create recursive sections.', ['parent_id'])
(_check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id'])
]
def name_get(self, cr, uid, ids, context=None):
@@ -105,7 +108,7 @@ class crm_case_section(osv.osv):
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param ids: List of section ids
@param ids: List of sales team ids
"""
if not context:
context = {}
@@ -136,8 +139,8 @@ class crm_case_categ(osv.osv):
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
'object_id': fields.many2one('ir.model', 'Object Name'),
}
def _find_object_id(self, cr, uid, context=None):
def _find_object_id(self, cr, uid, context=None):
"""Finds id for case object
@param self: The object pointer
@param cr: the current row, from the database cursor,
@@ -151,8 +154,8 @@ class crm_case_categ(osv.osv):
_defaults = {
'object_id' : _find_object_id
}
}
crm_case_categ()
@@ -282,6 +285,7 @@ class crm_case(osv.osv):
@param uid: the current user’s ID for security checks,
@param ids: List of Case IDs
@param context: A standard dictionary for contextual values
@return:Dictionary of History Ids
"""
if not context:
context = {}
@@ -524,7 +528,6 @@ class crm_case(osv.osv):
@param context: A standard dictionary for contextual values"""
if not context:
context = {}
model_obj = self.pool.get('ir.model')
obj = self.pool.get('crm.case.log')
for case in cases:
+2
View File
@@ -265,3 +265,5 @@ class base_action_rule(osv.osv):
base_action_rule()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+2
View File
@@ -73,3 +73,5 @@ class crm_fundraising(osv.osv):
}
crm_fundraising()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+2 -2
View File
@@ -85,8 +85,8 @@ class crm_lead(osv.osv):
resource_id = resource_ids[0]
duration = float(ans.days)
if lead.section_id.resource_calendar_id:
duration = float(ans.days) * 24
if lead.section_id and lead.section_id.resource_calendar_id:
duration = float(ans.days) * 24
new_dates = cal_obj.interval_get(cr,
uid,
lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
+13 -12
View File
@@ -65,7 +65,7 @@ class crm_meeting(osv.osv):
_defaults = {
'state': lambda *a: 'draft',
}
def open_meeting(self, cr, uid, ids, context=None):
"""
Open Crm Meeting Form for Crm Meeting.
@@ -75,13 +75,14 @@ class crm_meeting(osv.osv):
@param context: A standard dictionary for contextual values
@return: Dictionary value which open Crm Meeting form.
"""
if not context:
context = {}
data_obj = self.pool.get('ir.model.data')
value = {}
id2 = data_obj._get_id(cr, uid, 'crm', 'crm_case_form_view_meet')
id3 = data_obj._get_id(cr, uid, 'crm', 'crm_case_tree_view_meet')
id4 = data_obj._get_id(cr, uid, 'crm', 'crm_case_calendar_view_meet')
@@ -93,17 +94,17 @@ class crm_meeting(osv.osv):
id4 = data_obj.browse(cr, uid, id4, context=context).res_id
for id in ids:
value = {
'name': _('Meeting'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'crm.meeting',
'view_id': False,
'name': _('Meeting'),
'view_type': 'form',
'view_mode': 'form,tree',
'res_model': 'crm.meeting',
'view_id': False,
'views': [(id2, 'form'), (id3, 'tree'), (id4, 'calendar')],
'type': 'ir.actions.act_window',
'res_id': base_calendar.base_calendar_id2real_id(id),
'type': 'ir.actions.act_window',
'res_id': base_calendar.base_calendar_id2real_id(id),
'nodestroy': True
}
return value
crm_meeting()
+3 -4
View File
@@ -92,8 +92,8 @@ class crm_opportunity(osv.osv):
resource_id = resource_ids[0]
duration = float(ans.days)
if opportunity.section_id.resource_calendar_id:
duration = float(ans.days) * 24
if opportunity.section_id and opportunity.section_id.resource_calendar_id:
duration = float(ans.days) * 24
new_dates = cal_obj.interval_get(cr,
uid,
opportunity.section_id.resource_calendar_id and opportunity.section_id.resource_calendar_id.id or False,
@@ -231,7 +231,7 @@ class crm_opportunity(osv.osv):
}
value = {
'name': _('Meetings'),
'domain': "[('user_id','=',%s)]" % (uid),
'domain': "[('user_id','=',%s),('opportunity_id','=',%s)]" % (uid,opp.id),
'context': context,
'view_type': 'form',
'view_mode': 'calendar,form,tree',
@@ -242,7 +242,6 @@ class crm_opportunity(osv.osv):
'search_view_id': res['res_id'],
'nodestroy': True
}
return value
crm_opportunity()
+2 -2
View File
@@ -18,8 +18,8 @@
res_model="crm.phonecall"
src_model="crm.opportunity"
view_mode="calendar,tree,form"
context="{'default_duration': 1.0}"
domain="[('user_id','=',uid)]"
context="{'default_duration': 1.0 ,'default_opportunity_id': active_id}"
domain="[('user_id','=',uid),('opportunity_id', '=', active_id)]"
view_type="form"/>
<record model="ir.actions.act_window" id="crm_case_category_act_oppor11">
+10 -8
View File
@@ -11,7 +11,7 @@
<field name="model">crm.case.section</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Case Section">
<form string="Sales Team">
<group col="6" colspan="4">
<field name="name" select="1" colspan="4"/>
<field name="code" select="1"/>
@@ -19,22 +19,24 @@
<field name="resource_calendar_id" select="2"/>
</group>
<notebook colspan="4">
<page string="Case section">
<group col="2" colspan="2">
<page string="Sales Team">
<group col="2" colspan="1">
<separator string="Responsible" colspan="2"/>
<field name="parent_id" select="2" widget="selection"/>
<field name="user_id" select="2"/>
</group>
<group col="2" colspan="2">
<group col="2" colspan="1">
<separator string="Contact Information" colspan="2"/>
<field name="server_id" select="2"/>
<field name="reply_to" select="2"/>
</group>
<group col="2" colspan="2">
<separator string="Section Property" colspan="2"/>
<group col="2" colspan="1">
<separator string="Sales Team Property" colspan="2"/>
<field name="active" select="2"/>
<field name="allow_unlink" select="2"/>
</group>
<separator string="Members List" colspan="4"/>
<field name="member_ids" nolabel="1" colspan="4"/>
<separator string="Note" colspan="4"/>
<field name="note" select="1" colspan="4" nolabel="1"/>
</page>
@@ -51,7 +53,7 @@
<field name="type">tree</field>
<field name="field_parent">child_ids</field>
<field name="arch" type="xml">
<tree string="Case Section">
<tree string="Sales Team">
<field name="name" select="1"/>
<field name="code" select="1"/>
<field name="user_id" select="1"/>
@@ -62,7 +64,7 @@
<!-- Case Sections Action -->
<record id="crm_case_section_act" model="ir.actions.act_window">
<field name="name">Sections</field>
<field name="name">Sales Team</field>
<field name="res_model">crm.case.section</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm_case_section_view_tree"/>
+1 -1
View File
@@ -178,7 +178,7 @@
<act_window domain="[('section_id', '=', active_id)]"
id="act_crm_case_section_2_report_crm_case_categ"
name="Monthly cases by section" res_model="crm.case.report"
name="Monthly cases by Sales Team" res_model="crm.case.report"
src_model="crm.case.section" />
<!-- Closed & Open CRM Case view for Random Activities dashboard Tree View -->
+38
View File
@@ -0,0 +1,38 @@
- |
Now I check claims which contain customer claim information about document
related problem, product related problem.
- |
I start by creating new claims for Damaged product as Value Claims with priority High and specify
date of claim at which claim is created.
-
!record {model: crm.claim, id: crm_claim_damagedproduct0}:
categ_id: crm.categ_claim2
date: '2010-04-21 20:13:00'
email_from: info@balmerinc.be
name: 'Damaged product '
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
priority: '2'
section_id: crm.section_sales_department
- |
I check that the claims is in 'draft' state.
-
!assert {model: crm.claim, id: crm_claim_damagedproduct0}:
- state == 'draft'
- |
Now I make sure that claim is at "Accepted" stage.
-
!record {model: crm.claim, id: crm.crm_claim_damagedproduct0}:
stage_id: crm.stage_claim1
- |
I can change that stage by next button right on it
-
!python {model: crm.claim}: |
self.stage_next(cr, uid, [ref('crm_claim_damagedproduct0')])
- |
I make this claim as Open
-
!python {model: crm.claim}: |
self.case_open(cr, uid, [ref('crm_claim_damagedproduct0')])
+32
View File
@@ -0,0 +1,32 @@
- |
Now I start test Fund Raising which contain information about Donation or charity
given by user.
- |
I start by creating new Funds entry for donation for books to poor school children
with cost 500000.00 and category as "Learning And Education"
I make payment by Cheque so I make sure that the mode on fund is selected as cheque
-
!record {model: crm.fundraising, id: crm_fundraising_donationforbookstopoorschoolchildren0}:
categ_id: crm.categ_fund2
email_from: info@balmerinc.be
name: Donation for books to poor school children
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
planned_cost: 500000.0
section_id: crm.section_sales_department
type_id: crm.type_fund2
- |
I check that the Funds is in 'draft' state.
-
!assert {model: crm.fundraising, id: crm_fundraising_donationforbookstopoorschoolchildren0}:
- state == 'draft'
- |
Now I open Funds by click on "Open" button
-
!python {model: crm.fundraising}: |
self.case_open(cr, uid, [ref('crm_fundraising_donationforbookstopoorschoolchildren0')])
- |
I can close Funds by click on "Done" button.
-
!python {model: crm.fundraising}: |
self.case_close(cr, uid, [ref('crm_fundraising_donationforbookstopoorschoolchildren0')])
+38
View File
@@ -0,0 +1,38 @@
-
|
I start by creating New helpdesk request regarding some functional questions.
I select Date at which helpdesk request is created.
-
!record {model: crm.helpdesk, id: crm_helpdesk_somefunctionalquestion0}:
date: '2010-04-22 10:17:00'
email_from: info@balmerinc.be
name: Some functional question.
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
section_id: crm.section_sales_department
- |
I check that the Helpdesk request is in 'Draft' state.
-
!assert {model: crm.helpdesk, id: crm_helpdesk_somefunctionalquestion0}:
- state == 'draft'
- |
In order to make this helpdesk request to be considered I make it "Open"
-
!python {model: crm.helpdesk}: |
self.case_open(cr, uid, [ref('crm_helpdesk_somefunctionalquestion0')])
- |
I can see that "Send Reminder" button is visible, which is used to send reminder to partner from
responsible person or send reminder to responsible person from partner.
-
#this is not work for yaml.
-
# !python {model: crm.helpdesk}: |
# self.remind_user(cr, uid, [ref('crm_helpdesk_somefunctionalquestion0')], context={}, attach=False, destination=True)
- |
After a proper communication for the request via email I make sure that the request is fulfilled and
I close this HelpDesk Request by clicking on "Close" button.
-
!python {model: crm.helpdesk}: |
self.case_close(cr, uid, [ref('crm_helpdesk_somefunctionalquestion0')])
-
+113
View File
@@ -0,0 +1,113 @@
- |
In order to test the CRM in OpenERP,
I will do a customer qualification process that
starts with a fist contact with a customer (a lead), which will be converted to a
business opportunity and a partner.
- |
In order to test the flow, I create a two new users "user_crm" and
I assign the group "salesman".
-
!record {model: res.users, id: res_users_usercrm0}:
company_id: base.main_company
context_lang: en_US
context_section_id: crm.section_sales_department
groups_id:
- crm.group_crm_user
login: user_crm
name: user_crm
password: user_crm
- |
I start by creating a new lead "New Customer" and I provide an address to this
new customer, as well as an email "info@mycustomer.com".
-
!record {model: crm.lead, id: crm_lead_newcustomer0}:
email_from: info@mycustomer.com
name: New Customer
partner_name: Sandip zala
phone: (855) 924-4364
mobile: (333) 715-1450
section_id: crm.section_sales_department
referred: False
title: M.
- |
I check that the lead is in 'draft' state,
-
!assert {model: crm.lead, id: crm_lead_newcustomer0, string: Lead in Draft}:
- state == "draft"
-
I open lead by click on "Open" button,
-
!python {model: crm.lead}: |
self.case_open(cr, uid, [ref("crm_lead_newcustomer0")])
- |
As the lead seems to be a real business opportunity, I will convert it to a
partner
and a business opportunity by clicking on the "Convert" button.
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref('crm_lead_newcustomer0'))
if not lead.partner_id:
self.convert_opportunity(cr, uid, [ref("crm_lead_newcustomer0")], {'active_ids': [ref("crm_lead_newcustomer0")]})
-
|
Now, select "create a new partner" option in this wizard.
-
!record {model: crm.lead2opportunity.partner, id: crm_lead2opportunity_partner_create_0}:
action: 'create'
-
I click on "Continue" button of this wizard.
-
!python {model: crm.lead2opportunity.partner}: |
self.make_partner(cr, uid, [ref("crm_lead2opportunity_partner_create_0")], {'active_ids': [ref("crm_lead_newcustomer0")]})
-
Now, I give value to this wizard field.
-
!record {model: crm.lead2opportunity, id: crm_lead2opportunity_stonage_0}:
name: Sandip zala
planned_revenue: 0.00
probability: 0.00
-
Then, Click on "Create Opportunity" button of this wizard.
-
!python {model: crm.lead2opportunity}: |
self.action_apply(cr, uid, [ref('crm_lead2opportunity_stonage_0')], {'active_id': ref('crm_lead_newcustomer0')})
- |
I can check that a lead and a business opportunity is now assigned to this
lead.
-
# !python {model: crm.lead, id: crm_lead_newcustomer0}:
# - opportunity_id.id != False
- |
I check that the partner associated to this lead as the same country, phone number
and name than the opportunity.
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref("crm_lead_newcustomer0"))
print "lead", lead.partner_name
obj_opportunity = self.pool.get('crm.opportunity')
ids = obj_opportunity.search(cr, uid, [('name', '=', lead.partner_name)])
opportunity = obj_opportunity.browse(cr, uid, ids)[0]
assert lead.partner_name == opportunity.partner_id.name
assert lead.phone == opportunity.phone
-
|
# yaml is also not working smpt server and send new email.
- |
I configure with smtp server.
- |
And I communicate with lead through send New mail to Lead using it email address from user which are loged.
- |
I check that communication history generated when send email to lead.
- |
Then, I add a cc which receive copy of future communication between partner and users by mail.
- |
I Reply to last Email to lead with some document attached.and check that communication history generated or not.
+99
View File
@@ -0,0 +1,99 @@
- |
Now I will test Meetings which may be customer meeting or phonecall meeting or
internal Meeting.
- |
I start by creating a new Meeting.
-
!record {model: crm.meeting, id: crm_meeting_regardingpresentation0}:
categ_id: crm.categ_meet2
date: '2010-04-21 16:04:00'
date_deadline: '2010-04-22 00:04:00'
duration: 8.0
email_from: info@balmerinc.be
location: Ahmedabad
name: Regarding Presentation
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
rrule_type: weekly
section_id: crm.section_sales_department
- |
I check that the Meetings is in 'UnConfirmed' state.
-
!assert {model: crm.meeting, id: crm_meeting_regardingpresentation0}:
- state == "draft"
- |
I can set reminder on meeting if I put reminder "40 minutes before"
- |
For that, I first create alarm.
-
!record {model: res.alarm, id: res_alarm_minituesbefore0}:
name: 40 minutes before
trigger_duration: 40
trigger_interval: minutes
trigger_occurs: before
trigger_related: start
- |
Now I will assign this reminder.
- !python {model: crm.meeting}: |
self.write(cr, uid, [ref('crm_meeting_regardingpresentation0')], {'alarm_id': ref("res_alarm_minituesbefore0")})
- |
In order to check recurrence on meetings I will set Recurrency to Custom
and I set the fields so that the meeting will occure weekly on Monday and Friday 10 times
-
!python {model: crm.meeting}: |
self.write(cr, uid, [ref("crm_meeting_regardingpresentation0")], {'fr': 1, 'mo': 1, 'th': 1, 'tu': 1, 'we':1, 'count':10, 'interval': 1, 'freq': 'weekly', 'rrule_type': 'custom'})
- |
I can see from the calendar view that the meeting is scheduled on Monday and Friday
for 10 times,
-
!python {model: crm.meeting}: |
self.fields_view_get(cr, uid, False, 'calendar', context)
- |
I will search for one of the recurrent event and count the number of meeting.
-
!python {model: crm.meeting}: |
ids = self.search(cr, uid, [('date', '>=', '2010-05-21 00:00:00'), ('date', '<=', '2010-04-21 00:00:00')] )
assert len(ids) == 9
- |
Now If I want to edit meetings information for all occurence I click on "Edit All" button.
-
!python {model: crm.meeting}: |
self.open_meeting(cr, uid, [ref('crm_meeting_regardingpresentation0')])
- |
I can see that new meeting form is opened with same value
I change some data for meeting and save it
I can see from meeting's calendar view that all meeting occurences are changed accordingly
-
!record {model: crm.meeting, id: crm.crm_meeting_regardingpresentation0}:
alarm_id: base_calendar.alarm9
rrule_type: weekly
- |
In order to invite people for this meetings, I click on "Invite People" button
I can invite internal user.
-
!record {model: base_calendar.invite.attendee, id: base_calendar_invite_attendee_0}:
type: internal
partner_id: base.res_partner_9
user_ids:
- base.user_demo
- |
If I set Send mail boolean as True I can see that an email is send to
specified email address with proper meeting information.
-
#This is not working for send mail boolean as True.
-
I click on "Invite" button of "Invite attendee" wizard.
-
!python {model: base_calendar.invite.attendee}: |
self.do_invite(cr, uid, [ref('base_calendar_invite_attendee_0')], {'active_id': ref('crm_meeting_regardingpresentation0'), 'model' : 'crm.meeting', 'attendee_field':'attendee_ids'})
- |
After direct/indirect confirmation for meetings I can confirm meeting
-
!python {model: crm.meeting}: |
self.case_open(cr, uid, [ref('crm_meeting_regardingpresentation0')])
+81
View File
@@ -0,0 +1,81 @@
- |
I start by creating a new Opportunity. And I select partner for opportunity.
I can see that after selecting partner his contact and email is automatically filled.
-
!record {model: crm.opportunity, id: crm_opportunity_abcfuelcounits0}:
email_from: info@balmerinc.be
name: 'ABC FUEL CO 829264 - 10002 units '
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
probability: 1.0
stage_id: crm.stage_oppor1
categ_id: crm.categ_oppor2
section_id: crm.section_sales_department
- |
I check that the opportunity is in 'New' state.
-
!assert {model: crm.opportunity, id: crm_opportunity_abcfuelcounits0}:
- state == "draft"
- |
I open opportunity by click on "Open" button,
-
!python {model: crm.opportunity}: |
self.case_open(cr, uid, [ref("crm_opportunity_abcfuelcounits0")])
- |
I schedule Meeting on this current opportunity by clicking on "schedule
Meeting".
-
!python {model: crm.opportunity}: |
self.action_makeMeeting(cr, uid, [ref("crm_opportunity_abcfuelcounits0")])
- |
I can see that Meeting's calendar view is shown.
then I click on the date on which I want schedule meeting.
I fill proper data for that meeting and save it
-
!record {model: crm.meeting, id: crm_meeting_abcfuelcounits0}:
alarm_id: base_calendar.alarm3
date: '2010-04-16 00:00:00'
date_deadline: '2010-04-16 08:00:00'
duration: 8.0
email_from: info@balmerinc.be
name: 'ABC FUEL CO 829264 - 10002 units '
opportunity_id: 'crm_opportunity_abcfuelcounits0'
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
rrule_type: weekly
section_id: crm.section_sales_department
state: open
- |
In order to schedule a phonecall to the partner
I click on "schedule call" button and select planned date for the call.
-
!record {model: crm.opportunity2phonecall, id: crm_opportunity2phonecall_abcfuelcounits0}:
date: '2010-04-17 11:15:00'
name: 'ABC FUEL CO 829264 - 10002 units '
section_id: crm.section_sales_department
user_id: base.user_demo
- |
schedule phonecall by apply (action_apply)function of opportunity2phoncall wizard
-
!python {model: crm.opportunity2phonecall}: |
self.action_apply(cr, uid, [ref('crm_opportunity2phonecall_abcfuelcounits0')], {'active_ids': [ref("crm_opportunity_abcfuelcounits0")]})
- |
I check that phonecall record is created for that opportunity.
-
!python {model: crm.phonecall}: |
ids = self.search(cr, uid, [('name', '=', 'ABC FUEL CO 829264 - 10002 units')])
assert len(ids)
- |
I can see phonecall record after click on "Schedule call" wizard.
-
!record {model: crm.phonecall, id: crm_phonecall_abcfuelcounits0}:
date: '2010-04-17 11:15:00'
duration: 3.0
name: 'ABC FUEL CO 829264 - 10002 units'
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
section_id: crm.section_sales_department
+91
View File
@@ -0,0 +1,91 @@
- |
I start by creating a new phonecall.
-
!record {model: crm.phonecall, id: crm_phonecall_interviewcall0}:
date: '2010-04-21 18:59:00'
name: Interview call
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
partner_mobile: (+32)2 211 34 83
section_id: crm.section_sales_department
- |
As the success of phonecall seems to be a real business opportunity, I will convert
it to opportunity by clicking on the "Convert to Opportunity" button.
-
# !python {model: crm.phonecall2opportunity}: |
# self.action_apply(cr, uid, [1], {'active_id': ref('crm_phonecall_interviewcall0')})
-
# This is not working, find a way to do that in YAML
- |
I can see that a business opportunity is now assigned to this phonecall
- |
I check that the partner associated to this as the same country, phone number
and name than the phonecall.
- |
In order to avoid the duplication of similar partner, OpenERP should be able to
detect if the partner exists or if it should be created from the phonecall during the
conversion.
- |
In order to test this, I log as the user "user_crm2"
- |
Then, I create the same phonecall than the preceeding one but I change the name
of the phonecall. But this customer keeps the same email.
- |
This time OpenERP should detect that this customer already exists in the
partner base and I check that in the wizard, proposes me to link to this existing
partner instead of creating another one.
- |
I confirm the conversion wizard.
- |
And I check that the phonecall and the newly created business opportunity is linked
to the partner
- |
I schedule Meeting on this current phonecall by clicking on "schedule
Meeting"
-
!python {model: crm.phonecall}: |
self.action_make_meeting(cr, uid, [ref('crm_phonecall_interviewcall0')])
- |
I can see that Meeting's calendar view is shown.
then I click on the date on which I want schedule meeting.
I fill proper data for that meeting and save it
-
!record {model: crm.meeting, id: crm_meeting_interviewcall0}:
alarm_id: base_calendar.alarm3
date: '2010-04-20 00:00:00'
date_deadline: '2010-04-20 08:00:00'
duration: 8.0
email_from: info@balmerinc.be
name: Interview call
partner_address_id: base.res_partner_address_1
partner_id: base.res_partner_9
phonecall_id: 'crm_phonecall_interviewcall0'
rrule_type: weekly
state: open
-
#This is not working for yaml
- |
I can jump to this meeting by "Meetings" shortcut from my phonecall's form view
-
- |
In order to schedule other phonecall to the partner
I click on "schedule other call" button. and plan for other call
I can see that it will open other phonecall view with some data same as current
phonecall.
-
!record {model: crm.phonecall2phonecall, id: crm_phonecall2phonecall_interviewcall0}:
date: '2010-04-21 19:49:00'
name: Interview call
section_id: crm.section_sales_department
user_id: base.user_root
- |
I click on "Schedule" button of this wizard.
-
!python {model: crm.phonecall2phonecall}: |
self.action_apply(cr, uid, [ref('crm_phonecall2phonecall_interviewcall0')], {'active_id': ref('crm_phonecall_interviewcall0')})
@@ -29,10 +29,10 @@ class crm_opportunity2phonecall(osv.osv_memory):
_description = 'Opportunity to Phonecall'
_columns = {
'name' : fields.char('Call summary', size=64, required=True, select=1),
'user_id' : fields.many2one('res.users', "Assign To"),
'date': fields.datetime('Date' , required=True),
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
'name' : fields.char('Call summary', size=64, required=True, select=1),
'user_id' : fields.many2one('res.users', "Assign To"),
'date': fields.datetime('Date' , required=True),
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
}
def default_get(self, cr, uid, fields, context=None):
@@ -46,7 +46,6 @@ class crm_opportunity2phonecall(osv.osv_memory):
@return : default values of fields.
"""
opp_obj = self.pool.get('crm.opportunity')
record_ids = context and context.get('active_ids', []) or []
res = super(crm_opportunity2phonecall, self).default_get(cr, uid, fields, context=context)
@@ -93,7 +92,7 @@ class crm_opportunity2phonecall(osv.osv_memory):
data_obj = self.pool.get('ir.model.data')
categ_id = mod_obj._get_id(cr, uid, 'crm', 'categ_phone1')
categ_id = data_obj.browse(cr, uid, categ_id, context=context).res_id
# Select the view
id2 = data_obj._get_id(cr, uid, 'crm', 'crm_case_phone_tree_view')
id3 = data_obj._get_id(cr, uid, 'crm', 'crm_case_phone_form_view')
@@ -105,31 +104,32 @@ class crm_opportunity2phonecall(osv.osv_memory):
for this in self.browse(cr, uid, ids, context=context):
for opp in opp_obj.browse(cr, uid, record_ids, context=context):
new_case = phonecall_obj.create(cr, uid, {
'name' : opp.name,
'case_id' : opp.id ,
'user_id' : this.user_id and this.user_id.id or False,
'categ_id' : categ_id,
'description' : opp.description or False,
'date' : this.date,
'section_id' : opp.section_id and opp.section_id.id or False,
'partner_id': opp.partner_id and opp.partner_id.id or False,
'partner_address_id': opp.partner_address_id and opp.partner_address_id.id or False,
'partner_phone' : opp.phone or (opp.partner_address_id and opp.partner_address_id.phone or False),
'partner_mobile' : opp.partner_address_id and opp.partner_address_id.mobile or False,
'priority': opp.priority,
'name' : opp.name,
'case_id' : opp.id ,
'user_id' : this.user_id and this.user_id.id or False,
'categ_id' : categ_id,
'description' : opp.description or False,
'date' : this.date,
'section_id' : opp.section_id and opp.section_id.id or False,
'partner_id': opp.partner_id and opp.partner_id.id or False,
'partner_address_id': opp.partner_address_id and opp.partner_address_id.id or False,
'partner_phone' : opp.phone or (opp.partner_address_id and opp.partner_address_id.phone or False),
'partner_mobile' : opp.partner_address_id and opp.partner_address_id.mobile or False,
'priority': opp.priority,
'opp_id': opp.id
}, context=context)
phonecall_obj.case_open(cr, uid, [new_case])
value = {
'name': _('Phone Call'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'crm.phonecall',
'res_id' : new_case,
'views': [(id3, 'form'), (id2, 'tree'), (False, 'calendar'), (False, 'graph')],
'type': 'ir.actions.act_window',
'name': _('Phone Call'),
'domain': "[('user_id','=',%s),('opportunity_id','=',%s)]" % (uid,opp.id),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'crm.phonecall',
'res_id' : new_case,
'views': [(id3, 'form'), (id2, 'tree'), (False, 'calendar'), (False, 'graph')],
'type': 'ir.actions.act_window',
'search_view_id': res['res_id']
}
return value
-2
View File
@@ -21,8 +21,6 @@
from osv import fields, osv
from crm import crm
from caldav import caldav
from base_calendar import base_calendar
class crm_meeting(osv.osv):
_inherit = 'crm.meeting'
+2
View File
@@ -6,6 +6,8 @@
<record model="basic.calendar" id="caldav.basic_calendar1">
<field name="name">Meetings</field>
<field name="collection_id" ref="document.dir_calendars"></field>
<field name="type">vevent</field>
</record>
<record model="basic.calendar.lines" id="base_calendar.calendar_lines_event">
+3
View File
@@ -46,6 +46,9 @@
'report/document_report_view.xml'
],
'demo_xml': [ 'document_demo.xml',],
'test': [
'test/document_test.yml',
],
'installable': True,
'active': False,
'certificate': None,
+7 -45
View File
@@ -95,8 +95,7 @@ class document_directory(osv.osv):
'user_id': lambda self,cr,uid,ctx: uid,
'domain': lambda self,cr,uid,ctx: '[]',
'type': lambda *args: 'directory',
'ressource_id': lambda *a: 0,
'parent_id': _get_root_directory,
'ressource_id': lambda *a: 0,
'storage_id': _get_def_storage,
}
_sql_constraints = [
@@ -105,7 +104,11 @@ class document_directory(osv.osv):
]
def name_get(self, cr, uid, ids, context={}):
res = []
all_ids = self.search(cr,uid,[])
for d in self.browse(cr, uid, ids, context=context):
if d.id not in all_ids:
continue
s = ''
d2 = d
while d2 and d2.parent_id:
@@ -200,52 +203,11 @@ class document_directory(osv.osv):
raise
def _locate_child(self, cr,uid, root_id, uri,nparent, ncontext):
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)
"""
did = root_id
duri = uri
path = []
context = ncontext.context
while len(duri):
nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','directory')], context=context)
if not nid:
break
if len(nid)>1:
print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
path.append(duri[0])
duri = duri[1:]
did = nid[0]
root_node = did and self.browse(cr,uid,did, context) or False
return (nodes.node_dir(path, nparent,ncontext, root_node), duri)
nid = self.search(cr,uid,[('parent_id','=',did),('name','=',duri[0]),('type','=','ressource')], context=context)
if nid:
if len(nid)>1:
print "Duplicate dir? p= %d, n=\"%s\"" %(did,duri[0])
path.append(duri[0])
duri = duri[1:]
did = nid[0]
return nodes.node_res_dir(path, nparent,ncontext,self.browse(cr,uid,did, context))
# Here, we must find the appropriate non-dir child..
# Chech for files:
fil_obj = self.pool.get('ir.attachment')
nid = fil_obj.search(cr,uid,[('parent_id','=',did),('name','=',duri[0])],context=context)
if nid:
if len(duri)>1:
# cannot treat child as a dir
return None
if len(nid)>1:
print "Duplicate file?",did,duri[0]
path.append(duri[0])
return nodes.node_file(path,nparent,ncontext,fil_obj.browse(cr,uid,nid[0],context))
print "nothing found:",did, duri
#still, nothing found
return None
return (nodes.node_database(context=ncontext), uri)
def old_code():
if not uri:
+1 -2
View File
@@ -118,8 +118,7 @@
<record model="ir.ui.view" id="view_document_directory_tree">
<field name="name">document.directory</field>
<field name="model">document.directory</field>
<field name="type">tree</field>
<field name="field_parent">child_ids</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Directories" toolbar="1">
<field name="name"/>
+209 -238
View File
@@ -20,7 +20,7 @@
##############################################################################
import base64
import StringIO
from osv import osv, fields
from osv.orm import except_orm
import urlparse
@@ -69,14 +69,6 @@ class node_context(object):
return ndir
class node_database():
""" A node representing the database directory
Useless?
"""
def __init__(self,ncontext):
self.nctx = ncontext
class node_class(object):
@@ -117,11 +109,11 @@ class node_class(object):
s.append(self.path)
return s #map(lambda x: '/' +x, s)
def children(self, cr):
def children(self, cr, domain=None):
print "node_class.children()"
return [] #stub
def child(self,cr, name):
def child(self,cr, name, domain=None):
print "node_class.child()"
return None
@@ -160,10 +152,94 @@ class node_class(object):
its capabilities """
return {}
def match_dav_eprop(self, cr, match, ns, prop):
res = self.get_dav_eprop(cr, ns, prop)
if res == match:
return True
return False
def get_dav_eprop(self,cr,ns,prop):
return None
class node_dir(node_class):
def rm(self, cr):
raise RuntimeError("Not Implemented")
def rmcol(self, cr):
raise RuntimeError("Not Implemented")
def get_domain(self, cr, filters):
return []
class node_database(node_class):
""" A node representing the database directory
"""
our_type = 'database'
def __init__(self, path=[], parent=False, context=None):
super(node_database,self).__init__(path, parent, context)
def children(self, cr, domain=None):
res = self._child_get(cr, domain=domain) + self._file_get(cr)
return res
def child(self, cr, name, domain=None):
res = self._child_get(cr, name, domain=None)
if res:
return res[0]
res = self._file_get(cr,name)
if res:
return res[0]
return None
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 = [('parent_id','=',parent_id)]
if name:
where.append(('name','=',name))
if not domain:
domain = []
where2 = where + domain + [('type', '=', 'directory')]
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))
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))
return res
def _file_get(self,cr, nodename=False, directory_id=False):
res = []
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',directory_id) ]
ids = cntobj.search(cr, uid, where, context=ctx)
for content in cntobj.browse(cr, uid, ids, context=ctx):
res3 = cntobj._file_get(cr, self, nodename, content)
if res3:
res.extend(res3)
return res
def _get_ttag(self,cr):
return 'db-%s' % cr.dbname
class node_dir(node_database):
our_type = 'collection'
def __init__(self,path, parent, context, dirr, dctx=None):
super(node_dir,self).__init__(path, parent,context)
@@ -192,89 +268,57 @@ class node_dir(node_class):
print e
pass
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
def get_data(self,cr):
res = ''
for child in self.children(cr):
res += child.get_data(cr)
return res
def child(self,cr, name):
res = self._child_get(cr,name)
if res:
return res[0]
res = self._file_get(cr,name)
if res:
return res[0]
return None
def _file_get(self,cr, nodename=False):
res = []
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr, uid, where, context=ctx)
for content in cntobj.browse(cr, uid, ids, context=ctx):
res3 = cntobj._file_get(cr, self, nodename, content)
if res3:
res.extend(res3)
return super(node_dir,self)._file_get(cr, nodename, self.dir_id)
def _child_get(self, cr, name=None, domain=None):
return super(node_dir,self)._child_get(cr, name, self.dir_id, domain=domain)
def rmcol(self, cr):
uid = self.context.uid
directory = self.context._dirobj.browse(cr, uid, self.dir_id)
res = False
if not directory:
raise OSError(2, 'Not such file or directory.')
if directory._table_name=='document.directory':
if self.children(cr):
raise OSError(39, 'Directory not empty.')
res = self.context._dirobj.unlink(cr, uid, [directory.id])
else:
raise OSError(1, 'Operation not permited.')
return res
def get_dav_props(self, cr):
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
if content.extension == '.ics': # FIXME: call the content class!
res['http://groupdav.org/'] = ('resourcetype',)
break
return res
def get_dav_eprop(self,cr,ns,prop):
if ns != 'http://groupdav.org/' or prop != 'resourcetype':
print "Who asked for %s:%s?" % (ns,prop)
return None
res = {}
cntobj = self.context._dirobj.pool.get('document.directory.content')
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('directory_id','=',self.dir_id) ]
ids = cntobj.search(cr,uid,where,context=ctx)
for content in cntobj.browse(cr,uid,ids,context=ctx):
if content.extension == '.ics': # FIXME: call the content class!
return ('vevent-collection','http://groupdav.org/')
return None
def _child_get(self,cr,name = None):
def create_child_collection(self, cr, objname):
object2 = False
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
where = [('parent_id','=',self.dir_id) ]
if name:
where.append(('name','=',name))
obj = dirobj.browse(cr, uid, self.dir_id)
if obj and (obj.type == 'ressource') and not object2:
raise OSError(1, 'Operation not permited.')
ids = dirobj.search(cr, uid, where + [('ressource_parent_type_id','=',False)],context=ctx)
res = []
if ids:
for dirr in dirobj.browse(cr,uid,ids,context=ctx):
if dirr.type == 'directory':
res.append(node_dir(dirr.name,self,self.context,dirr))
elif dirr.type == 'ressource':
res.append(node_res_dir(dirr.name,self,self.context,dirr))
#objname = uri2[-1]
val = {
'name': objname,
'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
'ressource_id': object2 and object2.id or False,
'parent_id' : obj and obj.id or False
}
return dirobj.create(cr, uid, val)
fil_obj=dirobj.pool.get('ir.attachment')
#where2 = where # + [('res_model', '=', None)]
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))
return res
def create_child(self,cr,path,data):
""" API function to create a child file object and node
@@ -297,6 +341,19 @@ class node_dir(node_class):
fnode = node_file(path,self,self.context,fil)
fnode.set_data(cr,data,fil)
return fnode
def get_etag(self, cr):
""" 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)
def _get_wtag(self, cr):
""" Return the modification time as a unique, compact string """
if self.write_date:
wtime = time.mktime(time.strptime(self.write_date, '%Y-%m-%d %H:%M:%S'))
else: wtime = time.time()
return str(wtime)
def _get_ttag(self,cr):
return 'dir-%d' % self.dir_id
@@ -334,16 +391,16 @@ class node_res_dir(node_class):
for dfld in dirr.dctx_ids:
self.dctx_dict['dctx_' + dfld.field] = dfld.expr
def children(self,cr):
return self._child_get(cr)
def children(self, cr, domain=None):
return self._child_get(cr, domain=domain)
def child(self,cr, name):
res = self._child_get(cr,name)
def child(self,cr, name, domain=None):
res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
return None
def _child_get(self,cr,name = None):
def _child_get(self,cr, name = None, domain=None):
""" return virtual children of resource, based on the
foreign object.
@@ -430,11 +487,11 @@ class node_res_obj(node_class):
else:
self.res_id = res_id
def children(self,cr):
return self._child_get(cr) + self._file_get(cr)
def children(self, cr, domain=None):
return self._child_get(cr, domain=domain) + self._file_get(cr)
def child(self,cr, name):
res = self._child_get(cr,name)
def child(self,cr, name, domain=None):
res = self._child_get(cr, name, domain=domain)
if res:
return res[0]
res = self._file_get(cr,name)
@@ -489,7 +546,7 @@ class node_res_obj(node_class):
return ('vevent-collection','http://groupdav.org/')
return None
def _child_get(self,cr,name = None):
def _child_get(self,cr, name=None, domain=None):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
@@ -551,6 +608,31 @@ class node_res_obj(node_class):
if dirr.type == 'ressource':
res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
return res
def create_child_collection(self, cr, objname):
dirobj = self.context._dirobj
uid = self.context.uid
ctx = self.context.context.copy()
ctx.update(self.dctx)
res_obj = dirobj.pool.get(self.context.context['res_model'])
object2 = res_obj.browse(cr, uid, self.context.context['res_id']) or False
obj = dirobj.browse(cr, uid, self.dir_id)
if obj and (obj.type == 'ressource') and not object2:
raise OSError(1, 'Operation not permited.')
val = {
'name': objname,
'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
'ressource_id': object2 and object2.id or False,
'parent_id' : False
}
if (obj and (obj.type in ('directory'))) or not object2:
val['parent_id'] = obj and obj.id or False
return dirobj.create(cr, uid, val)
def create_child(self,cr,path,data):
""" API function to create a child file object and node
@@ -597,7 +679,30 @@ class node_file(node_class):
if fil.parent_id:
self.storage_id = fil.parent_id.storage_id.id
else:
self.storage_id = None
self.storage_id = None
def open(self, cr, mode=False):
uid = self.context.uid
if self.type in ('collection','database'):
return False
fobj = self.context._dirobj.pool.get('ir.attachment').browse(cr, uid, self.file_id, context=self.context.context)
if fobj.store_method and fobj.store_method== 'fs' :
s = StringIO.StringIO(self.get_data(cr, fobj))
else:
s = StringIO.StringIO(base64.decodestring(fobj.db_datas or ''))
s.name = self
return s
def rm(self, cr):
uid = self.context.uid
document_obj = self.context._dirobj.pool.get('ir.attachment')
if self.type in ('collection','database'):
return False
document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
res = False
if document and document._table_name == 'ir.attachment':
res = document_obj.unlink(cr, uid, [document.id])
return res
def fix_ppath(self, cr, fbro):
"""Sometimes we may init this w/o path, parent.
@@ -683,6 +788,16 @@ class node_content(node_class):
if dctx:
self.dctx.update(dctx)
self.act_id = act_id
def open(self, cr, mode=False):
uid = self.context.uid
if self.type in ('collection','database'):
return False
pool = self.context._dirobj.pool
res = getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, self)
res = StringIO.StringIO(res)
res.name = self
return res
def fill_fields(self,cr,dctx = None):
""" Try to read the object and fill missing fields, like mimetype,
@@ -720,147 +835,3 @@ class node_content(node_class):
def _get_ttag(self,cr):
return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
class old_class():
# the old code, remove..
def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
self.cr = cr
def _file_get(self, nodename=False):
if not self.object:
return []
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
res2 = []
where = []
if self.object2:
where.append( ('res_model','=',self.object2._name) )
where.append( ('res_id','=',self.object2.id) )
else:
where.append( ('parent_id','=',self.object.id) )
where.append( ('res_id','=',False) )
if nodename:
where.append( (fobj._rec_name,'=',nodename) )
for content in self.object.content_ids:
res3 = content._table._file_get(self,nodename,content)
if res3:
res2.extend(res3)
ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
if self.object and self.root and (self.object.type=='ressource'):
ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
res = fobj.browse(self.cr, self.uid, ids, context=self.context)
return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
def get_translation(self,value,lang):
# Must go, it works on arbitrary models and could be ambiguous.
result = value
pool = pooler.get_pool(self.cr.dbname)
translation_ids = pool.get('ir.translation').search(self.cr, self.uid, [('value','=',value),('lang','=',lang),('type','=','model')])
if len(translation_ids):
tran_id = translation_ids[0]
translation = pool.get('ir.translation').read(self.cr, self.uid, tran_id, ['res_id','name'])
res_model,field_name = tuple(translation['name'].split(','))
res_id = translation['res_id']
res = pool.get(res_model).read(self.cr, self.uid, res_id, [field_name])
if res:
result = res[field_name]
return result
def directory_list_for_child(self,nodename,parent=False):
pool = pooler.get_pool(self.cr.dbname)
where = []
if nodename:
nodename = self.get_translation(nodename, self.context['lang'])
where.append(('name','=',nodename))
if (self.object and self.object.type=='directory') or not self.object2:
where.append(('parent_id','=',self.object and self.object.id or False))
else:
where.append(('parent_id','=',False))
if self.object:
where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
else:
where.append(('ressource_parent_type_id','=',False))
ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
if self.object2:
ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])
res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
return res
def _child_get(self, nodename=False):
if self.type not in ('collection','database'):
return []
res = self.directory_list_for_child(nodename)
result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
if self.type=='database':
pool = pooler.get_pool(self.cr.dbname)
fobj = pool.get('ir.attachment')
vargs = [('parent_id','=',False),('res_id','=',False)]
if nodename:
vargs.append((fobj._rec_name,'=',nodename))
file_ids=fobj.search(self.cr,self.uid,vargs)
res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
if self.type=='collection' and self.object.type=="ressource":
where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
pool = pooler.get_pool(self.cr.dbname)
obj = pool.get(self.object.ressource_type_id.model)
_dirname_field = obj._rec_name
if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
_dirname_field = 'dirname'
name_for = obj._name.split('.')[-1]
if nodename and nodename.find(name_for) == 0 :
id = int(nodename.replace(name_for,''))
where.append(('id','=',id))
elif nodename:
if nodename.find('__') :
nodename=nodename.replace('__','/')
for invalid in INVALID_CHARS:
if nodename.find(INVALID_CHARS[invalid]) :
nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
nodename = self.get_translation(nodename, self.context['lang'])
where.append((_dirname_field,'=',nodename))
if self.object.ressource_tree:
if obj._parent_name in obj.fields_get(self.cr,self.uid):
where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
return result
else :
if self.object2:
return result
else:
if self.object2:
return result
ids = obj.search(self.cr, self.uid, where)
res = obj.browse(self.cr, self.uid, ids,self.context)
for r in res:
if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
r.name = eval('r.'+_dirname_field)
else:
r.name = False
if not r.name:
r.name = name_for + '%d'%r.id
for invalid in INVALID_CHARS:
if r.name.find(invalid) :
r.name = r.name.replace(invalid,INVALID_CHARS[invalid])
result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
if result2:
if self.object.ressource_tree:
result += result2
else:
result = result2
return result
def path_get(self):
path = self.path
if self.path[0]=='/':
path = self.path[1:]
return path
+52
View File
@@ -0,0 +1,52 @@
- |
In order to test the Document Management System In OpenERP.
-
In order to test behaviour of resource Directory I will make one resource Directory "Partner Labels" in OpenERP having type "Other Resources" and Directory mapped to object "Partner"
-
!record {model: 'document.directory', id: dir_partner_label}:
name : "Partner Labels"
storage_id : storage_default
type : ressource
content_ids:
- name: "Label"
report_id : base.res_partner_address_report
-
Assign "res.partner" object to ressource_type_id.
-
!python {model: document.directory}: |
ids = self.pool.get('ir.model').search(cr, uid, [('model','=','res.partner')])
id = self.write(cr, uid, [ref("dir_partner_label")], {'ressource_type_id' : ids[0]}, context)
-
In order to check static directory in OpenERP which is the real directory just like system's local folders,
First I create a directory in OpenERP named "Directory 1" with storage as "Default File storage" and type as "Static Directory"
-
!record {model: 'document.directory', id: directory_1}:
name : "Directory 1"
storage_id : storage_default
type : directory
-
In order to make a directory hierarchy in OpenERP I will make other static directory "Directory 2" and I put its Parent Item as "Directory 1",
So I can see the directory hierarchy in OpenERP's client like :- Directory1 -> Directory2
-
!record {model: 'document.directory', id: directory_2}:
name : "Directory 2"
storage_id : storage_default
type : directory
parent_id : directory_1
-
I am create a one Document Attachment name "My document" and select "Directory 2" as its Directory,
And in order to put a File content.
-
When I am creating the document then "Resource Title" is filled automatic with "My document".
-
!record {model: 'ir.attachment', id: document_1}:
name : "My document"
parent_id : directory_2
-
Attach a pdf file in "My document"
-
!python {model: ir.attachment}: |
import tools
import base64
file = open(tools.config['addons_path'] + '/document/test/partner.pdf')
id = self.write(cr, uid, [ref("document_1")], {'datas' : base64.encodestring(file.read())}, context)
+118
View File
@@ -0,0 +1,118 @@
%PDF-1.3
%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
% 'BasicFonts': class PDFDictionary
1 0 obj
% The standard fonts dictionary
<< /F1 2 0 R
/F2 3 0 R
/F3 4 0 R >>
endobj
% 'F1': class PDFType1Font
2 0 obj
% Font Helvetica
<< /BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font >>
endobj
% 'F2': class PDFType1Font
3 0 obj
% Font Helvetica-Bold
<< /BaseFont /Helvetica-Bold
/Encoding /WinAnsiEncoding
/Name /F2
/Subtype /Type1
/Type /Font >>
endobj
% 'F3': class PDFType1Font
4 0 obj
% Font Times-Roman
<< /BaseFont /Times-Roman
/Encoding /WinAnsiEncoding
/Name /F3
/Subtype /Type1
/Type /Font >>
endobj
% 'Page1': class PDFPage
5 0 obj
% Page dictionary
<< /Contents 9 0 R
/MediaBox [ 0
0
841.8898
595.2756 ]
/Parent 8 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
/ImageB
/ImageC
/ImageI ] >>
/Rotate 0
/Trans << >>
/Type /Page >>
endobj
% 'R6': class PDFCatalog
6 0 obj
% Document Root
<< /Outlines 10 0 R
/PageMode /UseNone
/Pages 8 0 R
/Type /Catalog >>
endobj
% 'R7': class PDFInfo
7 0 obj
<< /Author (Generated by OpenERP, Fabien Pinckaers)
/CreationDate (20100421145542)
/Keywords ()
/Producer (ReportLab http://www.reportlab.com)
/Subject (\(unspecified\))
/Title (Partner) >>
endobj
% 'R8': class PDFPages
8 0 obj
% page tree
<< /Count 1
/Kids [ 5 0 R ]
/Type /Pages >>
endobj
% 'R9': class PDFStream
9 0 obj
% page stream
<< /Filter [ /ASCII85Decode
/FlateDecode ]
/Length 524 >>
stream
Gat=(?#P<K'R_@f5MSc<`6.HPV2s@dQRlarfjrE>3i<Y4(hW<qY=/IHPo2nd;U2u=SpPc46=:icfCoF922oN\83q_*El(+<i)0C.\*qSa'orY`+p(cC^oTpn8t,B-+=I?_Q1#SGXQbKWJ5-M7"aXRSQCBTu'NHJF046Jno-.K&2kW+&Ddh9bXc1q%)-e?DSC(5(4%=lk+ql;XaX3m]F#7K:"]hph/QW?(0_J4kXQiP</;'9K=]Wd,jeGe.Ma6pqJ&iD]r%qkNclhVq5Q/gSmK+r&6mr`*Z[m8(n)Usu)MZo%TNta^h'I^g6m5Vp92+>e[>S,B2ME\)S7:@oo6@nF8`I+h##OY3a-n#Qm!>5jiD.9pQ@E1#Xj$=)27)F3RmD%fbqZh,T`gJd0/,?A3`0:_VVm`"l`>oOf9p&HGF7>WCGVJoD2&_+c#BDb)/&0>>rL5@]KY$9YOA<rrt#g)1a61mS([/:0_[`)!ctOhSA/p@bq]>qZHTJq8k]!o+?4a5\-KbB#NYJC'`~>endstream
endobj
% 'R10': class PDFOutlines
10 0 obj
<< /Count 0
/Type /Outlines >>
endobj
xref
0 11
0000000000 65535 f
0000000113 00000 n
0000000233 00000 n
0000000398 00000 n
0000000573 00000 n
0000000740 00000 n
0000001017 00000 n
0000001152 00000 n
0000001395 00000 n
0000001500 00000 n
0000002169 00000 n
trailer
<< /ID
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
[(\210\344\006bE\230CUUi\010[\)Qif) (\210\344\006bE\230CUUi\010[\)Qif)]
/Info 7 0 R
/Root 6 0 R
/Size 11 >>
startxref
2221
%%EOF
+3
View File
@@ -37,6 +37,9 @@
'security/ir.model.access.csv'
],
'demo_xml': [],
'test': [
'test/document_ftp_test.yml',
],
'installable': True,
'active': False,
'certificate': None,
+11 -24
View File
@@ -309,31 +309,18 @@ class abstracted_fs:
def open(self, node, mode):
if not node:
raise OSError(1, 'Operation not permited.')
# Reading operation
if node.type == 'file':
cr = pooler.get_db(node.context.dbname).cursor()
uid = node.context.uid
if not self.isfile(node):
raise OSError(1, 'Operation not permited.')
fobj = node.context._dirobj.pool.get('ir.attachment').browse(cr, uid, node.file_id, context=node.context.context)
if fobj.store_method and fobj.store_method== 'fs' :
s = StringIO.StringIO(node.get_data(cr, fobj))
else:
s = StringIO.StringIO(base64.decodestring(fobj.db_datas or ''))
s.name = node
cr.close()
return s
elif node.type == 'content':
uid = node.context.uid
cr = pooler.get_db(node.context.dbname).cursor()
pool = pooler.get_pool(node.context.dbname)
res = getattr(pool.get('document.directory.content'), 'process_read')(cr, uid, node)
res = StringIO.StringIO(res)
res.name = node
cr.close()
return res
else:
# Reading operation
cr = pooler.get_db(node.context.dbname).cursor()
res = False
#try:
if node.type not in ('collection','database'):
res = node.open(cr, mode)
#except:
# pass
cr.close()
if not res:
raise OSError(1, 'Operation not permited.')
return res
# ok, but need test more
@@ -0,0 +1,87 @@
- |
In order to test the document_ftp module in OpenERP, I will try different operation on the FTP interface, and check their impacts on OpenERP's documents and vice-versa.
-
In order to test behaviour of resource Directory I will make one resource Directory "Labels" in OpenERP having type "Other Resources" and Directory mapped to object "Partner"
-
!record {model: 'document.directory', id: dir_label}:
name : "Labels"
storage_id : document.storage_default
type : ressource
content_ids:
- name: "Label"
report_id : base.res_partner_address_report
-
Assign "res.partner" object to ressource_type_id.
-
!python {model: document.directory}: |
ids = self.pool.get('ir.model').search(cr, uid, [('model','=','res.partner')])
id = self.write(cr, uid, [ref("dir_label")], {'ressource_type_id' : ids[0]}, context)
-
In order to check static directory in OpenERP which is the real directory just like system's local folders,
First I create a directory in OpenERP named "Directory 1" with storage as "Default File storage" and type as "Static Directory"
-
!record {model: 'document.directory', id: directory_file}:
name : "File"
storage_id : document.storage_default
type : directory
-
I am create a one Document name "Document" and select "File" as its Directory,
-
When I am creating the record then "Resource Title" is filled automatic with "Document".
-
!record {model: 'ir.attachment', id: document_1}:
name : "Document"
parent_id : directory_file
-
In order to connect FTP server and set "File" path.
I create one directory "New" in "File" directory from FTP and check its effect in OpenERP
-
Also Rename the directory name "New" to "New Directory".
-
Remove directory "New Directory" and remove file "Document".
-
!python {model: ir.attachment}: |
from ftplib import FTP
from tools.misc import detect_ip_addr
from tools import config
ftp = FTP()
if detect_ip_addr:
host = config.get('ftp_server_host', detect_ip_addr())
else:
host = config.get('ftp_server_host', '127.0.0.1')
port = config.get('ftp_server_port','8021')
ftp.connect(host,port)
user = self.pool.get('res.users').read(cr, uid, uid, context)
ftp.login(user.get('login',''),user.get('password',''))
ftp.cwd("/" + cr.dbname+"/Documents/File/")
ftp.mkd("New")
ftp.rename('New','New Directory')
ftp.cwd("/" + cr.dbname+"/Documents/File/")
ftp.rmd('New Directory')
ftp.delete('Document')
ftp.quit()
-
In order to check directory created from FTP is working perfectly
-
Now I will test the same for Resource directory which is mapped with OpenERP object.
When you open this directory from FTP clients, it displays each records of mapped resource object as directory.
-
Now I test FTP client and Open the "Labels" Directory to check Resource Directory in FTP.
I can see that all Label of OpenERP are shown as children of "Labels" in FTP client as Directories
-
!python {model: ir.attachment}: |
from ftplib import FTP
from tools.misc import detect_ip_addr
from tools import config
ftp = FTP()
if detect_ip_addr:
host = config.get('ftp_server_host', detect_ip_addr())
else:
host = config.get('ftp_server_host', '127.0.0.1')
port = config.get('ftp_server_port','8021')
ftp.connect(host,port)
user = self.pool.get('res.users').read(cr, uid, uid, context)
ftp.login(user.get('login',''),user.get('password',''))
ftp.cwd("/" + cr.dbname+"/Documents/Labels/")
-
I make sure that I Open Labels Directory successfully.
+2 -2
View File
@@ -7,14 +7,14 @@
<field name="code">shcal</field>
</record>
<record model="document.directory" id="dir_calendars">
<record model="document.directory" id="document.dir_calendars">
<field name="name">Calendars</field>
</record>
<record model="document.directory.content" id="dir_content_calendar">
<field name="name">Calendars</field>
<field name="suffix">meetings</field>
<field name="directory_id" ref="dir_calendars"/>
<field name="directory_id" ref="document.dir_calendars"/>
<field name="extension">.ics</field>
<field name="include_name" eval="False"/>
<field name="object_id" ref="crm.model_crm_meeting"/>
+6 -28
View File
@@ -332,8 +332,8 @@ class document_directory_content(osv.osv):
return s
document_directory_content()
class crm_case(osv.osv):
_inherit = 'crm.case'
class crm_meeting(osv.osv):
_inherit = 'crm.meeting'
_columns = {
'code': fields.char('Calendar Code', size=64),
'date_deadline': fields.datetime('Deadline', help="Deadline Date is automatically\
@@ -341,7 +341,7 @@ class crm_case(osv.osv):
}
_defaults = {
'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'crm.case'),
'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'crm.meeting'),
}
def copy(self, cr, uid, id, default=None, context=None):
@@ -356,32 +356,10 @@ class crm_case(osv.osv):
if not default: default = {}
if not context: context = {}
default.update({'code': self.pool.get('ir.sequence').get(cr, uid, 'crm.case'), 'id': False})
return super(crm_case, self).copy(cr, uid, id, default, context)
default.update({'code': self.pool.get('ir.sequence').get(cr, uid, 'crm.meeting'), 'id': False})
return super(crm_meeting, self).copy(cr, uid, id, default, context)
def on_change_duration(self, cr, uid, id, date, duration):
""" Change Duration
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param id: crm case's ID,
@param date: Pass the Date,
@param duration: Pass the duration,
"""
if not date:
return {}
start_date = datetime.datetime.fromtimestamp(time.mktime(time.strptime(date, "%Y-%m-%d %H:%M:%S")))
if duration >= 0 :
end = start_date + datetime.timedelta(hours=duration)
if duration < 0:
raise osv.except_osv(_('Warning !'),
_('You can not set negative Duration.'))
res = {'value': {'date_deadline' : end.strftime('%Y-%m-%d %H:%M:%S')}}
return res
crm_case()
crm_meeting()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+4 -24
View File
@@ -39,36 +39,16 @@
</field>
</record>
<record model="ir.ui.view" id="view_case_inherit_form">
<field name="name">crm.case.code.form</field>
<field name="model">crm.case</field>
<record model="ir.ui.view" id="view_meeting_inherit_form">
<field name="name">crm.meeting.code.form</field>
<field name="model">crm.meeting</field>
<field name="type">form</field>
<field name="inherit_id" ref="crm.crm_case-view"/>
<field name="inherit_id" ref="crm.crm_case_form_view_meet"/>
<field name="arch" type="xml">
<field name="priority" position="after">
<field name="code"/>
</field>
</field>
</record>
<!-- CRM Case Form View -->
<record model="ir.ui.view" id="view_case_inherit_form1">
<field name="name">crm.case.inherit.form1</field>
<field name="model">crm.case</field>
<field name="type">form</field>
<field name="inherit_id" ref="crm.crm_case-view"/>
<field name="arch" type="xml">
<field name="date" position="replace">
<field name="date"
on_change="on_change_duration(date, duration)"
required="1" />
<field name="duration" string="Duration(In Hour)"
on_change="on_change_duration(date, duration)"
widget="float_time" />
</field>
</field>
</record>
</data>
</openerp>
@@ -1,99 +0,0 @@
#!/usr/bin/env python
"""
Buffering HTTP Server
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from utils import VERSION, AUTHOR
__version__ = VERSION
__author__ = AUTHOR
from BaseHTTPServer import BaseHTTPRequestHandler
import os
class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
"""
Buffering HTTP Request Handler
This class is an extension to the BaseHTTPRequestHandler
class which buffers the whole output and sends it at once
after the processing if the request is finished.
This makes it possible to work together with some clients
which otherwise would break (e.g. cadaver)
"""
def _init_buffer(self):
"""initialize the buffer.
If you override the handle() method remember to call
this (see below)
"""
self.__buffer=""
self.__outfp=os.tmpfile()
def _append(self,s):
""" append a string to the buffer """
self.__buffer=self.__buffer+s
def _flush(self):
""" flush the buffer to wfile """
self.wfile.write(self.__buffer)
self.__outfp.write(self.__buffer)
self.__outfp.flush()
self.wfile.flush()
self.__buffer=""
def handle(self):
""" Handle a HTTP request """
self._init_buffer()
BaseHTTPRequestHandler.handle(self)
self._flush()
def send_header(self, keyword, value):
"""Send a MIME header."""
if self.request_version != 'HTTP/0.9':
self._append("%s: %s\r\n" % (keyword, value))
def end_headers(self):
"""Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9':
self._append("\r\n")
def send_response(self, code, message=None):
self.log_request(code)
if message is None:
if self.responses.has_key(code):
message = self.responses[code][0]
else:
message = ''
if self.request_version != 'HTTP/0.9':
self._append("%s %s %s\r\n" %
(self.protocol_version, str(code), message))
self.send_header('Server', self.version_string())
self.send_header('Connection', 'close')
self.send_header('Date', self.date_time_string())
protocol_version="HTTP/1.1"
-379
View File
@@ -1,379 +0,0 @@
"""
Python WebDAV Server.
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
This module builds on AuthServer by implementing the standard DAV
methods.
Subclass this class and specify an IFACE_CLASS. See example.
"""
DEBUG=None
from utils import VERSION, AUTHOR
__version__ = VERSION
__author__ = AUTHOR
import os
import sys
import time
import socket
import string
import posixpath
import base64
import urlparse
import urllib
from propfind import PROPFIND
from delete import DELETE
from davcopy import COPY
from davmove import MOVE
from string import atoi,split
from status import STATUS_CODES
from errors import *
import BaseHTTPServer
class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""Simple DAV request handler with
- GET
- HEAD
- PUT
- OPTIONS
- PROPFIND
- PROPPATCH
- MKCOL
It uses the resource/collection classes for serving and
storing content.
"""
server_version = "DAV/" + __version__
protocol_version = 'HTTP/1.1'
### utility functions
def _log(self, message):
pass
def _append(self,s):
""" write the string to wfile """
self.wfile.write(s)
def send_body(self,DATA,code,msg,desc,ctype='application/octet-stream',headers=None):
""" send a body in one part """
if not headers:
headers = {}
self.send_response(code,message=msg)
self.send_header("Connection", "keep-alive")
self.send_header("Accept-Ranges", "bytes")
for a,v in headers.items():
self.send_header(a,v)
if DATA:
self.send_header("Content-Length", str(len(DATA)))
self.send_header("Content-Type", ctype)
else:
self.send_header("Content-Length", "0")
self.end_headers()
if DATA:
self._append(DATA)
def send_body_chunks(self,DATA,code,msg,desc,ctype='text/xml; encoding="utf-8"'):
""" send a body in chunks """
self.responses[207]=(msg,desc)
self.send_response(code,message=msg)
self.send_header("Content-type", ctype)
self.send_header("Connection", "keep-alive")
self.send_header("Transfer-Encoding", "chunked")
self.end_headers()
self._append(hex(len(DATA))[2:]+"\r\n")
self._append(DATA)
self._append("\r\n")
self._append("0\r\n")
self._append("\r\n")
### HTTP METHODS
def do_OPTIONS(self):
"""return the list of capabilities """
self.send_response(200)
self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
self.send_header("Content-Type", "text/plain")
self.send_header("Connection", "keep-alive")
self.send_header("DAV", "1")
self.end_headers()
def do_PROPFIND(self):
dc=self.IFACE_CLASS
# read the body
body=None
if self.headers.has_key("Content-Length"):
l=self.headers['Content-Length']
body=self.rfile.read(atoi(l))
alt_body = """<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:"><prop>
<getcontentlength xmlns="DAV:"/>
<getlastmodified xmlns="DAV:"/>
<getcreationdate xmlns="DAV:"/>
<checked-in xmlns="DAV:"/>
<executable xmlns="http://apache.org/dav/props/"/>
<displayname xmlns="DAV:"/>
<resourcetype xmlns="DAV:"/>
<checked-out xmlns="DAV:"/>
</prop></propfind>"""
#self.wfile.write(body)
# which Depth?
if self.headers.has_key('Depth'):
d=self.headers['Depth']
else:
d="infinity"
uri=self.geturi()
pf=PROPFIND(uri,dc,d)
if body:
pf.read_propfind(body)
try:
DATA=pf.createResponse()
DATA=DATA+"\n"
# print "Data:", DATA
except DAV_NotFound,(ec,dd):
return self.send_notFound(dd, uri)
except DAV_Error, (ec,dd):
return self.send_error(ec,dd)
self.send_body_chunks(DATA,207,"Multi-Status","Multiple responses")
def geturi(self):
buri = self.IFACE_CLASS.baseuri
if buri[-1] == '/':
return urllib.unquote(buri[:-1]+self.path)
else:
return urllib.unquote(buri+self.path)
def do_GET(self):
"""Serve a GET request."""
dc=self.IFACE_CLASS
uri=self.geturi()
# get the last modified date
try:
lm=dc.get_prop(uri,"DAV:","getlastmodified")
except:
lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
headers={"Last-Modified":lm , "Connection": "keep-alive"}
# get the content type
try:
ct=dc.get_prop(uri,"DAV:","getcontenttype")
except:
ct="application/octet-stream"
# get the data
try:
data=dc.get_data(uri)
except DAV_Error, (ec,dd):
self.send_status(ec)
return
# send the data
self.send_body(data,200,"OK","OK",ct,headers)
def do_HEAD(self):
""" Send a HEAD response """
dc=self.IFACE_CLASS
uri=self.geturi()
# get the last modified date
try:
lm=dc.get_prop(uri,"DAV:","getlastmodified")
except:
lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
headers={"Last-Modified":lm, "Connection": "keep-alive"}
# get the content type
try:
ct=dc.get_prop(uri,"DAV:","getcontenttype")
except:
ct="application/octet-stream"
try:
data=dc.get_data(uri)
headers["Content-Length"]=str(len(data))
except DAV_NotFound:
self.send_body(None,404,"Not Found","")
return
self.send_body(None,200,"OK","OK",ct,headers)
def do_POST(self):
self.send_error(404,"File not found")
def do_MKCOL(self):
""" create a new collection """
dc=self.IFACE_CLASS
uri=self.geturi()
try:
res = dc.mkcol(uri)
if res:
self.send_body(None,201,"Created",'')
else:
self.send_body(None,415,"Cannot create",'')
#self.send_header("Connection", "keep-alive")
# Todo: some content, too
except DAV_Error, (ec,dd):
self.send_body(None,int(ec),dd,dd)
def do_DELETE(self):
""" delete an resource """
dc=self.IFACE_CLASS
uri=self.geturi()
dl=DELETE(uri,dc)
if dc.is_collection(uri):
res=dl.delcol()
else:
res=dl.delone()
if res:
self.send_status(207,body=res)
else:
self.send_status(204)
def do_PUT(self):
dc=self.IFACE_CLASS
# read the body
body=None
if self.headers.has_key("Content-Length"):
l=self.headers['Content-Length']
body=self.rfile.read(atoi(l))
uri=self.geturi()
ct=None
if self.headers.has_key("Content-Type"):
ct=self.headers['Content-Type']
try:
dc.put(uri,body,ct)
except DAV_Error, (ec,dd):
self.send_status(ec)
return
self.send_status(201)
def do_COPY(self):
""" copy one resource to another """
try:
self.copymove(COPY)
except DAV_Error, (ec,dd):
self.send_status(ec)
def do_MOVE(self):
""" move one resource to another """
try:
self.copymove(MOVE)
except DAV_Error, (ec,dd):
self.send_status(ec)
def copymove(self,CLASS):
""" common method for copying or moving objects """
dc=self.IFACE_CLASS
# get the source URI
source_uri=self.geturi()
# get the destination URI
dest_uri=self.headers['Destination']
dest_uri=urllib.unquote(dest_uri)
# Overwrite?
overwrite=1
result_code=204
if self.headers.has_key("Overwrite"):
if self.headers['Overwrite']=="F":
overwrite=None
result_code=201
# instanciate ACTION class
cp=CLASS(dc,source_uri,dest_uri,overwrite)
# Depth?
d="infinity"
if self.headers.has_key("Depth"):
d=self.headers['Depth']
if d!="0" and d!="infinity":
self.send_status(400)
return
if d=="0":
res=cp.single_action()
self.send_status(res)
return
# now it only can be "infinity" but we nevertheless check for a collection
if dc.is_collection(source_uri):
try:
res=cp.tree_action()
except DAV_Error, (ec,dd):
self.send_status(ec)
return
else:
try:
res=cp.single_action()
except DAV_Error, (ec,dd):
self.send_status(ec)
return
if res:
self.send_body_chunks(res,207,STATUS_CODES[207],STATUS_CODES[207],
ctype='text/xml; charset="utf-8"')
else:
self.send_status(result_code)
def get_userinfo(self,user,pw):
""" Dummy method which lets all users in """
return 1
def send_status(self,code=200,mediatype='text/xml; charset="utf-8"', \
msg=None,body=None):
if not msg: msg=STATUS_CODES[code]
self.send_body(body,code,STATUS_CODES[code],msg,mediatype)
def send_notFound(self,descr,uri):
body = """<?xml version="1.0" encoding="utf-8" ?>
<D:response xmlns:D="DAV:">
<D:href>%s</D:href>
<D:error/>
<D:responsedescription>%s</D:responsedescription>
</D:response>
"""
return self.send_status(404,descr, body=body % (uri,descr))
-20
View File
@@ -1,20 +0,0 @@
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-18
View File
@@ -1,18 +0,0 @@
"""
constants definition
"""
# definition for resourcetype
COLLECTION=1
OBJECT=None
DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
# Request classes in propfind
RT_ALLPROP=1
RT_PROPNAME=2
RT_PROP=3
-218
View File
@@ -1,218 +0,0 @@
"""
davcmd.py
---------
containts commands like copy, move, delete for normal
resources and collections
"""
from string import split,replace,joinfields
import urlparse
from utils import create_treelist, is_prefix
from errors import *
def deltree(dc,uri,exclude={}):
""" delete a tree of resources
dc -- dataclass to use
uri -- root uri to delete
exclude -- an optional list of uri:error_code pairs which should not
be deleted.
returns dict of uri:error_code tuples from which
another method can create a multistatus xml element.
Also note that we only know Depth=infinity thus we don't have
to test for it.
"""
tlist=create_treelist(dc,uri)
result={}
for i in range(len(tlist),0,-1):
problem_uris=result.keys()
element=tlist[i-1]
# test here, if an element is a prefix of an uri which
# generated an error before.
# note that we walk here from childs to parents, thus
# we cannot delete a parent if a child made a problem.
# (see example in 8.6.2.1)
ok=1
for p in problem_uris:
if is_prefix(element,p):
ok=None
break
if not ok: continue
# here we test for the exclude list which is the other way round!
for p in exclude.keys():
if is_prefix(p,element):
ok=None
break
if not ok: continue
# now delete stuff
try:
delone(dc,element)
except DAV_Error, (ec,dd):
result[element]=ec
return result
def delone(dc,uri):
""" delete a single object """
if dc.is_collection(uri):
dc.rmcol(uri) # should be empty
else:
dc.rm(uri)
###
### COPY
###
# helper function
def copy(dc,src,dst):
""" only copy the element
This is just a helper method factored out from copy and
copytree. It will not handle the overwrite or depth header.
"""
# destination should have been deleted before
if dc.exists(dst): raise DAV_Error, 412
# source should exist also
if not dc.exists(src): raise DAV_NotFound
if dc.is_collection(src):
dc.copycol(src,dst) # an exception will be passed thru
else:
dc.copy(src,dst) # an exception will be passed thru
# the main functions
def copyone(dc,src,dst,overwrite=None):
""" copy one resource to a new destination """
if overwrite and dc.exists(dst):
delres=deltree(dc,dst)
else:
delres={}
# if we cannot delete everything, then do not copy!
if delres: return delres
try:
copy(dc,src,dst) # pass thru exceptions
except DAV_Error, (ec,dd):
return ec
def copytree(dc,src,dst,overwrite=None):
""" copy a tree of resources to another location
dc -- dataclass to use
src -- src uri from where to copy
dst -- dst uri
overwrite -- if 1 then delete dst uri before
returns dict of uri:error_code tuples from which
another method can create a multistatus xml element.
"""
# first delete the destination resource
if overwrite and dc.exists(dst):
delres=deltree(dc,dst)
else:
delres={}
# if we cannot delete everything, then do not copy!
if delres: return delres
# get the tree we have to copy
tlist=create_treelist(dc,src)
result={}
# prepare destination URIs (get the prefix)
dpath=urlparse.urlparse(dst)[2]
for element in tlist:
problem_uris=result.keys()
# now URIs get longer and longer thus we have
# to test if we had a parent URI which we were not
# able to copy in problem_uris which is the prefix
# of the actual element. If it is, then we cannot
# copy this as well but do not generate another error.
ok=1
for p in problem_uris:
if is_prefix(p,element):
ok=None
break
if not ok: continue
# now create the destination URI which corresponds to
# the actual source URI. -> actual_dst
# ("subtract" the base src from the URI and prepend the
# dst prefix to it.)
esrc=replace(element,src,"")
actual_dst=dpath+esrc
# now copy stuff
try:
copy(dc,element,actual_dst)
except DAV_Error, (ec,dd):
result[element]=ec
return result
###
### MOVE
###
def moveone(dc,src,dst,overwrite=None):
""" move a single resource
This is done by first copying it and then deleting
the original.
"""
# first copy it
copyone(dc,src,dst,overwrite)
# then delete it
dc.rm(src)
def movetree(dc,src,dst,overwrite=None):
""" move a collection
This is done by first copying it and then deleting
the original.
PROBLEM: if something did not copy then we have a problem
when deleting as the original might get deleted!
"""
# first copy it
res=copytree(dc,src,dst,overwrite)
# then delete it
res=deltree(dc,src,exclude=res)
return res
-133
View File
@@ -1,133 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from xml.dom import ext
from xml.dom.Document import Document
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
from utils import create_treelist, quote_uri, gen_estring
class COPY:
""" copy resources and eventually create multistatus responses
This module implements the COPY class which is responsible for
copying resources. Usually the normal copy work is done in the
interface class. This class only creates error messages if error
occur.
"""
def __init__(self,dataclass,src_uri,dst_uri,overwrite):
self.__dataclass=dataclass
self.__src=src_uri
self.__dst=dst_uri
self.__overwrite=overwrite
def single_action(self):
""" copy a normal resources.
We try to copy it and return the result code.
This is for Depth==0
"""
dc=self.__dataclass
base=self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps=urlparse.urlparse(self.__src)[2]
pd=urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
return dc.copyone(self.__src,self.__dst,self.__overwrite)
#return copyone(dc,self.__src,self.__dst,self.__overwrite)
def tree_action(self):
""" copy a tree of resources (a collection)
Here we return a multistatus xml element.
"""
dc=self.__dataclass
base=self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps=urlparse.urlparse(self.__src)[2]
pd=urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
result=dc.copytree(self.__src,self.__dst,self.__overwrite)
#result=copytree(dc,self.__src,self.__dst,self.__overwrite)
if not result: return None
###
### create the multistatus XML element
### (this is also the same as in delete.py.
### we might make a common method out of it)
###
doc = Document(None)
ms=doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
for el,ec in result.items():
re=doc.createElement("D:response")
hr=doc.createElement("D:href")
st=doc.createElement("D:status")
huri=doc.createTextNode(quote_uri(el))
t=doc.createTextNode(gen_estring(ec))
st.appendChild(t)
hr.appendChild(huri)
re.appendChild(hr)
re.appendChild(st)
ms.appendChild(re)
sfile=StringIO()
ext.PrettyPrint(doc,stream=sfile)
s=sfile.getvalue()
sfile.close()
return s
-102
View File
@@ -1,102 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
from utils import create_treelist, quote_uri, gen_estring, make_xmlresponse
from davcmd import moveone, movetree
class MOVE:
""" move resources and eventually create multistatus responses
This module implements the MOVE class which is responsible for
moving resources.
MOVE is implemented by a COPY followed by a DELETE of the old
resource.
"""
def __init__(self,dataclass,src_uri,dst_uri,overwrite):
self.__dataclass=dataclass
self.__src=src_uri
self.__dst=dst_uri
self.__overwrite=overwrite
def single_action(self):
""" move a normal resources.
We try to move it and return the result code.
This is for Depth==0
"""
dc=self.__dataclass
base=self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps=urlparse.urlparse(self.__src)[2]
pd=urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
return dc.moveone(self.__src,self.__dst,self.__overwrite)
def tree_action(self):
""" move a tree of resources (a collection)
Here we return a multistatus xml element.
"""
dc=self.__dataclass
base=self.__src
### some basic tests
# test if dest exists and overwrite is false
if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
# test if src and dst are the same
# (we assume that both uris are on the same server!)
ps=urlparse.urlparse(self.__src)[2]
pd=urlparse.urlparse(self.__dst)[2]
if ps==pd: raise DAV_Error, 403
result=dc.movetree(self.__src,self.__dst,self.__overwrite)
if not result: return None
# create the multistatus XML element
return make_xmlresponse(result)
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import os
import string
import urllib
from StringIO import StringIO
from status import STATUS_CODES
from utils import gen_estring, quote_uri, make_xmlresponse
from davcmd import deltree
class DELETE:
def __init__(self,uri,dataclass):
self.__dataclass=dataclass
self.__uri=uri
def delcol(self):
""" delete a collection """
dc=self.__dataclass
result=dc.deltree(self.__uri)
if not len(result.items()):
return None # everything ok
# create the result element
return make_xmlresponse(result)
def delone(self):
""" delete a resource """
dc=self.__dataclass
result=dc.delone(self.__uri)
if not result: return None
if not len(result.items()):
return None # everything ok
# create the result element
return make_xmlresponse(result)
-56
View File
@@ -1,56 +0,0 @@
#!/usr/bin/env python
"""
Exceptions for the DAVserver implementation
"""
class DAV_Error(Exception):
""" in general we can have the following arguments:
1. the error code
2. the error result element, e.g. a <multistatus> element
"""
def __init__(self,*args):
if len(args)==1:
self.args=(args[0],"")
else:
self.args=args
class DAV_Secret(DAV_Error):
""" the user is not allowed to know anything about it
returning this for a property value means to exclude it
from the response xml element.
"""
def __init__(self):
DAV_Error.__init__(self,0)
pass
class DAV_NotFound(DAV_Error):
""" a requested property was not found for a resource """
def __init__(self,*args):
if len(args):
if isinstance(args[0],list):
stre = "Path %s not found!"%('/'.join(args[0]))
else:
stre = args[0]
DAV_Error.__init__(self,404,stre)
else:
DAV_Error.__init__(self,404)
pass
class DAV_Forbidden(DAV_Error):
""" a method on a resource is not allowed """
def __init__(self,*args):
if len(args):
DAV_Error.__init__(self,403,args[0])
else:
DAV_Error.__init__(self,403)
pass
-263
View File
@@ -1,263 +0,0 @@
"""
basic interface class
use this for subclassing when writing your own interface
class.
"""
from errors import *
import time
from string import lower
class dav_interface:
""" interface class for implementing DAV servers """
### defined properties (modify this but let the DAV stuff there!)
### the format is namespace: [list of properties]
PROPS={"DAV:" : ('creationdate',
'displayname',
'getcontentlanguage',
'getcontentlength',
'getcontenttype',
'getetag',
'getlastmodified',
'lockdiscovery',
'resourcetype',
'source',
'supportedlock'),
"NS2" : ("p1","p2")
}
# here we define which methods handle which namespace
# the first item is the namespace URI and the second one
# the method prefix
# e.g. for DAV:getcontenttype we call dav_getcontenttype()
M_NS={"DAV:" : "_get_dav",
"NS2" : "ns2" }
def get_propnames(self,uri):
""" return the property names allowed for the given URI
In this method we simply return the above defined properties
assuming that they are valid for any resource.
You can override this in order to return a different set
of property names for each resource.
"""
return self.PROPS
def get_prop2(self,uri,ns,pname):
""" return the value of a property
"""
if lower(ns)=="dav:": return self.get_dav(uri,pname)
raise DAV_NotFound
def get_prop(self,uri,ns,propname):
""" return the value of a given property
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
prefix=self.M_NS[ns]
else:
print "No namespace:",ns, "( for prop:", propname,")"
raise DAV_NotFound
mname=prefix+"_"+propname
if not hasattr(self,mname):
raise DAV_NotFound
try:
m=getattr(self,mname)
r=m(uri)
return r
except AttributeError, e:
print 'Property %s not supported' % propname
print "Exception:", e
raise DAV_NotFound
###
### DATA methods (for GET and PUT)
###
def get_data(self,uri):
""" return the content of an object
return data or raise an exception
"""
raise DAV_NotFound
def put(self,uri,data):
""" write an object to the repository
return a result code or raise an exception
"""
raise DAV_Forbidden
###
### Methods for DAV properties
###
def _get_dav_creationdate(self,uri):
""" return the creationdate of a resource """
d=self.get_creationdate(uri)
# format it
if isinstance(d, int) or isinstance(d, float):
d = time.localtimetime(d)
return time.strftime("%Y-%m-%dT%H:%M:%S%Z",d)
def _get_dav_getlastmodified(self,uri):
""" return the last modified date of a resource """
d=self.get_lastmodified(uri)
if isinstance(d, int) or isinstance(d, float):
d = time.localtime(d)
# format it
return time.asctime(d)
###
### OVERRIDE THESE!
###
def get_creationdate(self,uri):
""" return the creationdate of the resource """
return time.time()
def get_lastmodified(self,uri):
""" return the last modification date of the resource """
return time.time()
###
### COPY MOVE DELETE
###
### methods for deleting a resource
def rmcol(self,uri):
""" delete a collection
This should not delete any children! This is automatically done
before by the DELETE class in DAV/delete.py
return a success code or raise an exception
"""
raise DAV_NotFound
def rm(self,uri):
""" delete a single resource
return a success code or raise an exception
"""
raise DAV_NotFound
"""
COPY/MOVE HANDLER
These handler are called when a COPY or MOVE method is invoked by
a client. In the default implementation it works as follows:
- the davserver receives a COPY/MOVE method
- the davcopy or davmove module will be loaded and the corresponding
class will be initialized
- this class parses the query and decides which method of the interface class
to call:
copyone for a single resource to copy
copytree for a tree to copy (collection)
(the same goes for move of course).
- the interface class has now two options:
1. to handle the action directly (e.g. cp or mv on filesystems)
2. to let it handle via the copy/move methods in davcmd.
ad 1) The first approach can be used when we know that no error can
happen inside a tree or when the action can exactly tell which
element made which error. We have to collect these and return
it in a dict of the form {uri: error_code, ...}
ad 2) The copytree/movetree/... methods of davcmd.py will do the recursion
themselves and call for each resource the copy/move method of the
interface class. Thus method will then only act on a single resource.
(Thus a copycol on a normal unix filesystem actually only needs to do
an mkdir as the content will be copied by the davcmd.py function.
The davcmd.py method will also automatically collect all errors and
return the dictionary described above.
When you use 2) you also have to implement the copy() and copycol()
methods in your interface class. See the example for details.
To decide which approach is the best you have to decide if your application
is able to generate errors inside a tree. E.g. a function which completely
fails on a tree if one of the tree's childs fail is not what we need. Then
2) would be your way of doing it.
Actually usually 2) is the better solution and should only be replaced by
1) if you really need it.
The remaining question is if we should do the same for the DELETE method.
"""
### MOVE handlers
def moveone(self,src,dst,overwrite):
""" move one resource with Depth=0 """
return moveone(self,src,dst,overwrite)
def movetree(self,src,dst,overwrite):
""" move a collection with Depth=infinity """
return movetree(self,src,dst,overwrite)
### COPY handlers
def copyone(self,src,dst,overwrite):
""" copy one resource with Depth=0 """
return copyone(self,src,dst,overwrite)
def copytree(self,src,dst,overwrite):
""" copy a collection with Depth=infinity """
return copytree(self,src,dst,overwrite)
### low level copy methods (you only need these for method 2)
def copy(self,src,dst):
""" copy a resource with depth==0
You don't need to bother about overwrite or not.
This has been done already.
return a success code or raise an exception if something fails
"""
return 201
def copycol(self,src,dst):
""" copy a resource with depth==infinity
You don't need to bother about overwrite or not.
This has been done already.
return a success code or raise an exception if something fails
"""
return 201
### some utility functions you need to implement
def exists(self,uri):
""" return 1 or None depending on if a resource exists """
return None # no
def is_collection(self,uri):
""" return 1 or None depending on if a resource is a collection """
return None # no
-372
View File
@@ -1,372 +0,0 @@
#!/usr/bin/env python
"""
python davserver
Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from xml.dom import ext
from xml.dom.Document import Document
import sys
import string
import urlparse
import urllib
from StringIO import StringIO
import utils
from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
from errors import *
def utf8str(st):
if isinstance(st,unicode):
return st.encode('utf8')
else:
return str(st)
class PROPFIND:
""" parse a propfind xml element and extract props
It will set the following instance vars:
request_class : ALLPROP | PROPNAME | PROP
proplist : list of properties
nsmap : map of namespaces
The list of properties will contain tuples of the form
(element name, ns_prefix, ns_uri)
"""
def __init__(self,uri,dataclass,depth):
self.request_type=None
self.nsmap={}
self.proplist={}
self.default_ns=None
self.__dataclass=dataclass
self.__depth=str(depth)
self.__uri=uri
self.use_full_urls=True
self.__has_body=None # did we parse a body?
def read_propfind(self,xml_doc):
self.request_type,self.proplist,self.namespaces=utils.parse_propfind(xml_doc)
# a violation of the expected logic: client (korganizer) will ask for DAV:resourcetype
# but we also have to return the http://groupdav.org/:resourcetype property!
if self.proplist.has_key('DAV:') and 'resourcetype' in self.proplist['DAV:']:
if not self.proplist.has_key('http://groupdav.org/'):
self.proplist['http://groupdav.org/'] = []
self.proplist['http://groupdav.org/'].append('resourcetype')
if 'DAV:' in self.namespaces: #TMP
self.namespaces.append('http://groupdav.org/')
def createResponse(self):
""" create the multistatus response
This will be delegated to the specific method
depending on which request (allprop, propname, prop)
was found.
If we get a PROPNAME then we simply return the list with empty
values which we get from the interface class
If we get an ALLPROP we first get the list of properties and then
we do the same as with a PROP method.
If the uri doesn't exist, return an xml response with a 404 status
"""
if not self.__dataclass.exists(self.__uri):
raise DAV_NotFound("Path %s doesn't exist" % self.__uri)
if self.request_type==RT_ALLPROP:
return self.create_allprop()
if self.request_type==RT_PROPNAME:
return self.create_propname()
if self.request_type==RT_PROP:
return self.create_prop()
# no body means ALLPROP!
return self.create_allprop()
def create_propname(self):
""" create a multistatus response for the prop names """
dc=self.__dataclass
# create the document generator
doc = Document(None)
ms=doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
if self.__depth=="0":
pnames=dc.get_propnames(self.__uri)
re=self.mk_propname_response(self.__uri,pnames,doc)
ms.appendChild(re)
elif self.__depth=="1":
pnames=dc.get_propnames(self.__uri)
re=self.mk_propname_response(self.__uri,pnames,doc)
ms.appendChild(re)
for newuri in dc.get_childs(self.__uri):
pnames=dc.get_propnames(newuri)
re=self.mk_propname_response(newuri,pnames,doc)
ms.appendChild(re)
# *** depth=="infinity"
sfile=StringIO()
ext.PrettyPrint(doc,stream=sfile)
s=sfile.getvalue()
sfile.close()
return s
def create_allprop(self):
""" return a list of all properties """
self.proplist={}
self.namespaces=[]
for ns,plist in self.__dataclass.get_propnames(self.__uri).items():
self.proplist[ns]=plist
self.namespaces.append(ns)
return self.create_prop()
def create_prop(self):
""" handle a <prop> request
This will
1. set up the <multistatus>-Framework
2. read the property values for each URI
(which is dependant on the Depth header)
This is done by the get_propvalues() method.
3. For each URI call the append_result() method
to append the actual <result>-Tag to the result
document.
We differ between "good" properties, which have been
assigned a value by the interface class and "bad"
properties, which resulted in an error, either 404
(Not Found) or 403 (Forbidden).
"""
# create the document generator
doc = Document(None)
ms=doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
if self.__depth=="0":
gp,bp=self.get_propvalues(self.__uri)
res=self.mk_prop_response(self.__uri,gp,bp,doc)
ms.appendChild(res)
elif self.__depth=="1":
gp,bp=self.get_propvalues(self.__uri)
res=self.mk_prop_response(self.__uri,gp,bp,doc)
ms.appendChild(res)
try:
for newuri in self.__dataclass.get_childs(self.__uri):
gp,bp=self.get_propvalues(newuri)
res=self.mk_prop_response(newuri,gp,bp,doc)
ms.appendChild(res)
except DAV_NotFound:
# If no children, never mind.
pass
sfile=StringIO()
ext.PrettyPrint(doc,stream=sfile)
s=sfile.getvalue()
sfile.close()
return s
def mk_propname_response(self,uri,propnames,doc):
""" make a new <prop> result element for a PROPNAME request
This will simply format the propnames list.
propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
"""
re=doc.createElement("D:response")
# write href information
href=doc.createElement("D:href")
if self.use_full_urls:
huri=doc.createTextNode(uri)
else:
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
huri=doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
href.appendChild(huri)
re.appendChild(href)
ps=doc.createElement("D:propstat")
nsnum=0
for ns,plist in propnames.items():
# write prop element
pr=doc.createElement("D:prop")
nsp="ns"+str(nsnum)
pr.setAttribute("xmlns:"+nsp,ns)
nsnum=nsnum+1
# write propertynames
for p in plist:
pe=doc.createElement(nsp+":"+p)
pr.appendChild(pe)
ps.appendChild(pr)
re.appendChild(ps)
return re
def mk_prop_response(self,uri,good_props,bad_props,doc):
""" make a new <prop> result element
We differ between the good props and the bad ones for
each generating an extra <propstat>-Node (for each error
one, that means).
"""
re=doc.createElement("D:response")
# append namespaces to response
nsnum=0
for nsname in self.namespaces:
re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum=nsnum+1
# write href information
href=doc.createElement("D:href")
if self.use_full_urls:
huri=doc.createTextNode(uri)
else:
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
huri=doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
href.appendChild(huri)
re.appendChild(href)
# write good properties
if good_props and len(good_props.items()):
ps=doc.createElement("D:propstat")
gp=doc.createElement("D:prop")
for ns in good_props.keys():
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
for p,v in good_props[ns].items():
pe=doc.createElement(ns_prefix+str(p))
if v == None:
pass
elif ns=='DAV:' and p=="resourcetype":
if v == 1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
elif isinstance(v,tuple) and v[1] == ns:
ve=doc.createElement(ns_prefix+v[0])
pe.appendChild(ve)
else:
ve=doc.createTextNode(utf8str(v))
pe.appendChild(ve)
gp.appendChild(pe)
if gp.hasChildNodes():
re.appendChild(ps)
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!
if len(bad_props.items()):
# write a propstat for each error code
for ecode in bad_props.keys():
ps=doc.createElement("D:propstat")
re.appendChild(ps)
bp=doc.createElement("D:prop")
ps.appendChild(bp)
for ns in bad_props[ecode].keys():
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
for p in bad_props[ecode][ns]:
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
return re
def get_propvalues(self,uri):
""" create lists of property values for an URI
We create two lists for an URI: the properties for
which we found a value and the ones for which we
only got an error, either because they haven't been
found or the user is not allowed to read them.
"""
good_props={}
bad_props={}
for (ns,plist) in self.proplist.items():
good_props[ns]={}
bad_props={}
ec = 0
for prop in plist:
try:
ec = 0
r=self.__dataclass.get_prop(uri,ns,prop)
good_props[ns][prop]=r
except DAV_Error, error_code:
ec=error_code[0]
# ignore props with error_code if 0 (invisible)
if ec==0: continue
if bad_props.has_key(ec):
if bad_props[ec].has_key(ns):
bad_props[ec][ns].append(prop)
else:
bad_props[ec][ns]=[prop]
else:
bad_props[ec]={ns:[prop]}
return good_props, bad_props
-31
View File
@@ -1,31 +0,0 @@
"""
status codes for DAV services
"""
STATUS_CODES={
102: "Processing",
200: "Ok",
201: "Created",
204: "No Content",
207: "Multi-Status",
201: "Created",
400: "Bad Request",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
409: "Conflict",
412: "Precondition failed",
423: "Locked",
415: "Unsupported Media Type",
507: "Insufficient Storage",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
502: "Bad Gateway",
507: "Insufficient Storage",
999: "Some error in Create Method please check the data of create method"
}
-160
View File
@@ -1,160 +0,0 @@
#!/usr/bin/env python
"""
UTILITIES
- parse a propfind request body into a list of props
"""
from xml.dom import ext
from xml.dom.Document import Document
from xml.dom.ext.reader import PyExpat
from xml.dom import Node
from xml.dom import NodeIterator, NodeFilter
from string import lower, split, atoi, joinfields
import urlparse
from StringIO import StringIO
from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
from status import STATUS_CODES
VERSION = '0.6'
AUTHOR = 'Simon Pamies <s.pamies@banality.de>'
def gen_estring(ecode):
""" generate an error string from the given code """
ec=atoi(str(ecode))
if STATUS_CODES.has_key(ec):
return "HTTP/1.1 %s %s" %(ec,STATUS_CODES[ec])
else:
return "HTTP/1.1 %s" %(ec)
def parse_propfind(xml_doc):
""" parse an propfind xml file and return a list of props
returns:
request_type -- ALLPROP, PROPNAME, PROP
proplist -- list of properties found
namespaces -- list of namespaces found
"""
doc = PyExpat.Reader().fromString(xml_doc)
snit = doc.createNodeIterator(doc, NodeFilter.NodeFilter.SHOW_ELEMENT, None, None)
request_type=None
props={}
namespaces=[]
while 1:
curr_elem = snit.nextNode()
if not curr_elem: break
ename=fname=lower(curr_elem.nodeName)
if ":" in fname:
ename=split(fname,":")[1]
if ename=="prop": request_type=RT_PROP; continue
if ename=="propfind": continue
if ename=="allprop": request_type=RT_ALLPROP; continue
if ename=="propname": request_type=RT_PROPNAME; continue
# rest should be names of attributes
ns = curr_elem.namespaceURI
if props.has_key(ns):
props[ns].append(ename)
else:
props[ns]=[ename]
namespaces.append(ns)
return request_type,props,namespaces
def create_treelist(dataclass,uri):
""" create a list of resources out of a tree
This function is used for the COPY, MOVE and DELETE methods
uri - the root of the subtree to flatten
It will return the flattened tree as list
"""
queue=[uri]
list=[uri]
while len(queue):
element=queue[-1]
if dataclass.is_collection(element):
childs=dataclass.get_childs(element)
else:
childs=[]
if len(childs):
list=list+childs
# update queue
del queue[-1]
if len(childs):
queue=queue+childs
return list
def is_prefix(uri1,uri2):
""" returns 1 of uri1 is a prefix of uri2 """
if uri2[:len(uri1)]==uri1:
return 1
else:
return None
def quote_uri(uri):
""" quote an URL but not the protocol part """
import urlparse
import urllib
up=urlparse.urlparse(uri)
np=urllib.quote(up[2])
return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5]))
def get_uriparentpath(uri):
""" extract the uri path and remove the last element """
up=urlparse.urlparse(uri)
return joinfields(split(up[2],"/")[:-1],"/")
def get_urifilename(uri):
""" extract the uri path and return the last element """
up=urlparse.urlparse(uri)
return split(up[2],"/")[-1]
def get_parenturi(uri):
""" return the parent of the given resource"""
up=urlparse.urlparse(uri)
np=joinfields(split(up[2],"/")[:-1],"/")
return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5]))
### XML utilities
def make_xmlresponse(result):
""" construct a response from a dict of uri:error_code elements """
doc = Document(None)
ms=doc.createElement("D:multistatus")
ms.setAttribute("xmlns:D","DAV:")
doc.appendChild(ms)
for el,ec in result.items():
re=doc.createElement("D:response")
hr=doc.createElement("D:href")
st=doc.createElement("D:status")
huri=doc.createTextNode(quote_uri(el))
t=doc.createTextNode(gen_estring(ec))
st.appendChild(t)
hr.appendChild(huri)
re.appendChild(hr)
re.appendChild(st)
ms.appendChild(re)
sfile=StringIO()
ext.PrettyPrint(doc,stream=sfile)
s=sfile.getvalue()
sfile.close()
return s
+14 -20
View File
@@ -1,29 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
# Fabien Pinckaers <fp@tiny.Be>
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
# 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 Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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.
#
# 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 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.
# 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 webdav
import webdav_server
+148 -168
View File
@@ -1,32 +1,23 @@
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
# Fabien Pinckaers <fp@tiny.Be>
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
# 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 Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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.
#
# 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 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.
# 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 pooler
import base64
@@ -55,71 +46,87 @@ CACHE_SIZE=20000
urlparse.uses_netloc.append('webdav')
urlparse.uses_netloc.append('webdavs')
class tinydav_handler(dav_interface):
class openerp_dav_handler(dav_interface):
"""
This class models a Tiny ERP interface for the DAV server
This class models a OpenERP interface for the DAV server
"""
PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
PROPS={'DAV:': dav_interface.PROPS['DAV:'],}
M_NS={ "DAV:" : dav_interface.M_NS['DAV:'], }
M_NS={ "DAV:" : dav_interface.M_NS['DAV:'],}
def __init__(self, parent, verbose=False):
def __init__(self, parent, verbose=False):
self.db_name = False
self.directory_id=False
self.db_name_list=[]
self.parent = parent
self.baseuri = parent.baseuri
self.verbose = verbose
def get_propnames(self,uri):
props = self.PROPS
self.parent.log_message('get propnames: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return props
node = self.uri2object(cr,uid,pool, uri2)
if node:
props.update(node.get_dav_props(cr))
cr.close()
def get_propnames(self,uri):
props = self.PROPS
self.parent.log_message('get propnames: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
return props
node = self.uri2object(cr, uid, pool, uri2)
if node:
props.update(node.get_dav_props(cr))
cr.close()
return props
def get_prop(self,uri,ns,propname):
""" return the value of a given property
def _get_dav_lockdiscovery(self, uri):
raise DAV_NotFound
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
return dav_interface.get_prop(self,uri,ns,propname)
def _get_dav_supportedlock(self, uri):
raise DAV_NotFound
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
raise DAV_NotFound
node = self.uri2object(cr,uid,pool, uri2)
if not node:
cr.close()
raise DAV_NotFound
res = node.get_dav_eprop(cr,ns,propname)
def match_prop(self, uri, match, ns, propname):
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
raise DAV_NotFound
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
return res
raise DAV_NotFound
res = node.match_dav_eprop(cr, match, ns, propname)
cr.close()
return res
#
# def get_db(self,uri):
# names=self.uri2local(uri).split('/')
# self.db_name=False
# if len(names) > 1:
# self.db_name=self.uri2local(uri).split('/')[1]
# if self.db_name=='':
# raise Exception,'Plese specify Database name in folder'
# return self.db_name
#
def get_prop(self, uri, ns, propname):
""" return the value of a given property
def later_get_db_from_path(self,path):
return "aaa"
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
if self.M_NS.has_key(ns):
return dav_interface.get_prop(self,uri,ns,propname)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
raise DAV_NotFound
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
raise DAV_NotFound
res = node.get_dav_eprop(cr,ns,propname)
cr.close()
return res
def get_db(self,uri):
names=self.uri2local(uri).split('/')
self.db_name=False
if len(names) > 1:
self.db_name=self.uri2local(uri).split('/')[1]
if self.db_name=='':
raise Exception,'Plese specify Database name in folder'
return self.db_name
def urijoin(self,*ajoin):
""" Return the base URI of this request, or even join it with the
@@ -130,7 +137,7 @@ class tinydav_handler(dav_interface):
@memoize(4)
def db_list(self):
s = netsvc.ExportService.getService('db')
result = s.list()
result = s.exp_list()
self.db_name_list=[]
for db_name in result:
db = pooler.get_db_only(db_name)
@@ -142,20 +149,21 @@ class tinydav_handler(dav_interface):
cr.close()
return self.db_name_list
def get_childs(self,uri):
""" return the child objects as self.baseuris for the given URI """
def get_childs(self,uri, filters=None):
""" return the child objects as self.baseuris for the given URI """
self.parent.log_message('get childs: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
s = netsvc.ExportService.getService('db')
cr.close()
return map(lambda x: self.urijoin(x), self.db_list())
if not dbname:
if cr: cr.close()
res = map(lambda x: self.urijoin(x), self.db_list())
return res
result = []
node = self.uri2object(cr,uid,pool, uri2[:])
if not node:
cr.close()
if cr: cr.close()
raise DAV_NotFound(uri2)
else:
fp = node.full_path()
@@ -164,13 +172,16 @@ class tinydav_handler(dav_interface):
fp = '/'.join(fp)
else:
fp = None
for d in node.children(cr):
self.parent.log_message('child: %s' % d.path)
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) )
cr.close()
if cr: cr.close()
return result
def uri2local(self, uri):
@@ -183,24 +194,28 @@ class tinydav_handler(dav_interface):
#
# pos: -1 to get the parent of the uri
#
def get_cr(self, uri):
def get_cr(self, uri):
pdb = self.parent.auth_proxy.last_auth
reluri = self.uri2local(uri)
try:
dbname = reluri.split('/')[2]
dbname = reluri.split('/')[1]
except:
dbname = False
dbname = False
if not dbname:
return None, None, None, False, None
if not pdb and dbname:
# if dbname was in our uri, we should have authenticated
# against that.
raise Exception("Programming error")
assert pdb == dbname, " %s != %s" %(pdb, dbname)
user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
#assert pdb == dbname, " %s != %s" %(pdb, dbname)
res = self.parent.auth_proxy.auth_creds.get(dbname, False)
if not res:
self.parent.auth_proxy.checkRequest(self.parent, uri, dbname)
res = self.parent.auth_proxy.auth_creds[dbname]
user, passwd, dbn2, uid = res
db,pool = pooler.get_db_and_pool(dbname)
cr = db.cursor()
uri2 = reluri.split('/')[3:]
uri2 = reluri.split('/')[2:]
return cr, uid, pool, dbname, uri2
def uri2object(self, cr,uid, pool,uri):
@@ -208,20 +223,20 @@ class tinydav_handler(dav_interface):
return None
return pool.get('document.directory').get_object(cr, uid, uri)
def get_data(self,uri):
def get_data(self,uri):
self.parent.log_message('GET: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2)
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
try:
datas = node.get_data(cr)
except TypeError,e:
import traceback
import traceback
self.parent.log_error("GET typeError: %s", str(e))
self.parent.log_message("Exc: %s",traceback.format_exc())
raise DAV_Forbidden
@@ -235,11 +250,11 @@ class tinydav_handler(dav_interface):
raise DAV_Error, 409
return datas
finally:
cr.close()
if cr: cr.close()
@memoize(CACHE_SIZE)
def _get_dav_resourcetype(self,uri):
""" return type of object """
""" return type of object """
self.parent.log_message('get RT: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
@@ -253,7 +268,7 @@ class tinydav_handler(dav_interface):
return COLLECTION
return OBJECT
finally:
cr.close()
if cr: cr.close()
def _get_dav_displayname(self,uri):
self.parent.log_message('get DN: %s' % uri)
@@ -271,14 +286,14 @@ class tinydav_handler(dav_interface):
@memoize(CACHE_SIZE)
def _get_dav_getcontentlength(self,uri):
""" return the content length of an object """
""" return the content length of an object """
self.parent.log_message('get length: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
return '0'
if cr: cr.close()
return str(result)
node = self.uri2object(cr, uid, pool, uri2)
if not node:
cr.close()
@@ -311,9 +326,9 @@ class tinydav_handler(dav_interface):
if uri[-1]=='/':uri=uri[:-1]
today = time.time()
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return today
if not dbname:
return today
try:
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
@@ -322,36 +337,35 @@ class tinydav_handler(dav_interface):
else:
return today
finally:
cr.close()
if cr: cr.close()
@memoize(CACHE_SIZE)
def get_creationdate(self,uri):
""" return the last modified date of the object """
""" return the last modified date of the object """
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
raise DAV_Error, 409
if not dbname:
raise DAV_Error, 409
try:
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
raise DAV_NotFound(uri2)
if node.create_date:
result = time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S')
result = time.mktime(time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S'))
else:
result = time.gmtime()
result = time.time()
return result
finally:
cr.close()
if cr: cr.close()
@memoize(CACHE_SIZE)
def _get_dav_getcontenttype(self,uri):
self.parent.log_message('get contenttype: %s' % uri)
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
try:
if not dbname:
return 'httpd/unix-directory'
if not dbname:
return 'httpd/unix-directory'
try:
node = self.uri2object(cr,uid,pool, uri2)
if not node:
raise DAV_NotFound(uri2)
@@ -359,49 +373,29 @@ class tinydav_handler(dav_interface):
return result
#raise DAV_NotFound, 'Could not find %s' % path
finally:
cr.close()
if cr: cr.close()
def mkcol(self,uri):
""" create a new collection """
""" create a new collection """
self.parent.log_message('MKCOL: %s' % uri)
uri = self.uri2local(uri)[1:]
if uri[-1]=='/':uri=uri[:-1]
parent='/'.join(uri.split('/')[:-1])
if not parent.startswith(self.baseuri):
parent=self.baseuri + ''.join(parent[1:])
if not uri.startswith(self.baseuri):
uri=self.baseuri + ''.join(uri[1:])
parent = '/'.join(uri.split('/')[:-1])
parent = self.baseuri + parent
uri = self.baseuri + uri
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2[:-1])
object2 = False
if isinstance(node, node_res_obj):
object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False
obj = node.context._dirobj.browse(cr, uid, node.dir_id)
if obj and (obj.type == 'ressource') and not object2:
raise OSError(1, 'Operation not permited.')
objname = uri2[-1]
val = {
'name': objname,
'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
'ressource_id': object2 and object2.id or False,
'parent_id' : False
}
if (obj and (obj.type in ('directory'))) or not object2:
val['parent_id'] = obj and obj.id or False
pool.get('document.directory').create(cr, uid, val)
cr.commit()
if node:
node.create_child_collection(cr, uri2[-1])
cr.commit()
cr.close()
return True
def put(self,uri,data,content_type=None):
""" put the object into the filesystem """
self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), len(data), content_type))
self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), data and len(data) or 0, content_type))
parent='/'.join(uri.split('/')[:-1])
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
@@ -410,6 +404,7 @@ class tinydav_handler(dav_interface):
node = self.uri2object(cr,uid,pool, uri2[:])
except:
node = False
objname = uri2[-1]
ext = objname.find('.') >0 and objname.split('.')[1] or False
@@ -438,6 +433,7 @@ class tinydav_handler(dav_interface):
return 201
def rmcol(self,uri):
print ' REMOVE COLLL :::::', uri
""" delete a collection """
if uri[-1]=='/':uri=uri[:-1]
@@ -445,39 +441,23 @@ class tinydav_handler(dav_interface):
if not dbname: # *-*
raise DAV_Error, 409
node = self.uri2object(cr, uid, pool, uri2)
object = node.context._dirobj.browse(cr, uid, node.dir_id)
if not object:
raise OSError(2, 'Not such file or directory.')
if object._table_name=='document.directory':
if node.children(cr):
raise OSError(39, 'Directory not empty.')
res = pool.get('document.directory').unlink(cr, uid, [object.id])
else:
raise OSError(1, 'Operation not permited.')
node.rmcol(cr)
cr.commit()
cr.close()
return 204
def rm(self,uri):
if uri[-1]=='/':uri=uri[:-1]
object=False
print ' REMOVE EVENT :::::', uri
if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
raise DAV_Error, 409
node = self.uri2object(cr,uid,pool, uri2)
object = pool.get('ir.attachment').browse(cr, uid, node.file_id)
self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
if not object:
raise OSError(2, 'Not such file or directory.')
if object._table_name == 'ir.attachment':
res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
else:
raise OSError(1, 'Operation not permited.')
res = node.rm(cr)
if not res:
raise OSError(1, 'Operation not permited.')
cr.commit()
cr.close()
return 204
@@ -633,7 +613,7 @@ class tinydav_handler(dav_interface):
result = False
cr, uid, pool,dbname, uri2 = self.get_cr(uri)
if not dbname:
cr.close()
if cr: cr.close()
return True
try:
node = self.uri2object(cr,uid,pool, uri2)
+115
View File
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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 xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
import urlparse
import urllib
from DAV import utils
from DAV.propfind import PROPFIND
import tools
super_mk_prop_response = PROPFIND.mk_prop_response
def mk_prop_response(self,uri,good_props,bad_props,doc):
""" make a new <prop> result element
We differ between the good props and the bad ones for
each generating an extra <propstat>-Node (for each error
one, that means).
"""
re=doc.createElement("D:response")
# append namespaces to response
nsnum=0
for nsname in self.namespaces:
re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum=nsnum+1
# write href information
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
href=doc.createElement("D:href")
huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc))
href.appendChild(huri)
re.appendChild(href)
# write good properties
ps=doc.createElement("D:propstat")
if good_props:
re.appendChild(ps)
gp=doc.createElement("D:prop")
for ns in good_props.keys():
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
for p,v in good_props[ns].items():
if not v:
pass
pe=doc.createElement(ns_prefix+str(p))
if hasattr(v, '__class__') and v.__class__.__name__ == 'Element':
pe.appendChild(v)
else:
if p=="resourcetype":
if v==1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
else:
ve=doc.createTextNode(tools.ustr(v))
pe.appendChild(ve)
gp.appendChild(pe)
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!
if len(bad_props.items()):
# write a propstat for each error code
for ecode in bad_props.keys():
ps=doc.createElement("D:propstat")
re.appendChild(ps)
bp=doc.createElement("D:prop")
ps.appendChild(bp)
for ns in bad_props[ecode].keys():
ns_prefix="ns"+str(self.namespaces.index(ns))+":"
for p in bad_props[ecode][ns]:
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
return re
PROPFIND.mk_prop_response = mk_prop_response
+121 -11
View File
@@ -28,10 +28,26 @@
import netsvc
from dav_fs import tinydav_handler
import tools
from dav_fs import openerp_dav_handler
from tools.config import config
from DAV.WebDAVServer import DAVRequestHandler
from service.websrv_lib import HTTPDir,FixSendError
import urlparse
import urllib
from string import atoi,split
from DAV.errors import *
def OpenDAVConfig(**kw):
class OpenDAV:
def __init__(self, **kw):
self.__dict__.update(**kw)
class Config:
DAV = OpenDAV(**kw)
return Config()
class DAVHandler(FixSendError,DAVRequestHandler):
verbose = False
@@ -43,16 +59,24 @@ class DAVHandler(FixSendError,DAVRequestHandler):
netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
def handle(self):
pass
self._init_buffer()
def finish(self):
pass
def get_db_from_path(self, uri):
if uri or uri == '/':
dbs = self.IFACE_CLASS.db_list()
res = len(dbs) and dbs[0] or False
else:
res = self.IFACE_CLASS.get_db(uri)
return res
def setup(self):
davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
self.baseuri = "http://%s:%d%s"% (self.server.server_name,self.server.server_port,davpath)
self.IFACE_CLASS = tinydav_handler(self)
pass
davpath = '/'+config.get_misc('webdav','vdir','webdav')
self.baseuri = "http://%s:%d/"% (self.server.server_name,self.server.server_port)
self.IFACE_CLASS = openerp_dav_handler(self, self.verbose)
def log_message(self, format, *args):
netsvc.Logger().notifyChannel('webdav',netsvc.LOG_DEBUG_RPC,format % args)
@@ -60,16 +84,102 @@ class DAVHandler(FixSendError,DAVRequestHandler):
def log_error(self, format, *args):
netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_WARNING,format % args)
def do_PUT(self):
dc=self.IFACE_CLASS
uri=urlparse.urljoin(self.get_baseuri(dc), self.path)
uri=urllib.unquote(uri)
# Handle If-Match
if self.headers.has_key('If-Match'):
test = False
etag = None
for match in self.headers['If-Match'].split(','):
if match == '*':
if dc.exists(uri):
test = True
break
else:
if dc.match_prop(uri, match, "DAV:", "getetag"):
test = True
break
if not test:
self.send_status(412)
return
# Handle If-None-Match
if self.headers.has_key('If-None-Match'):
test = True
etag = None
for match in self.headers['If-None-Match'].split(','):
if match == '*':
if dc.exists(uri):
test = False
break
else:
if dc.match_prop(uri, match, "DAV:", "getetag"):
test = False
break
if not test:
self.send_status(412)
return
# Handle expect
expect = self.headers.get('Expect', '')
if (expect.lower() == '100-continue' and
self.protocol_version >= 'HTTP/1.1' and
self.request_version >= 'HTTP/1.1'):
self.send_status(100)
self._flush()
# read the body
body=None
if self.headers.has_key("Content-Length"):
l=self.headers['Content-Length']
body=self.rfile.read(atoi(l))
# locked resources are not allowed to be overwritten
if self._l_isLocked(uri):
return self.send_body(None, '423', 'Locked', 'Locked')
ct=None
if self.headers.has_key("Content-Type"):
ct=self.headers['Content-Type']
try:
location = dc.put(uri,body,ct)
except DAV_Error, (ec,dd):
return self.send_status(ec)
headers = {}
if location:
headers['Location'] = location
try:
etag = dc.get_prop(location or uri, "DAV:", "getetag")
headers['ETag'] = etag
except:
pass
self.send_body(None, '201', 'Created', '', headers=headers)
try:
from service.http_server import reg_http_service,OpenERPAuthProvider
from service.http_server import reg_http_service,OpenERPAuthProvider
if (config.get_misc('webdav','enable',True)):
davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
directory = '/'+config.get_misc('webdav','vdir','webdav')
handler = DAVHandler
handler.verbose = config.get_misc('webdav','verbose',True)
verbose = config.get_misc('webdav','verbose',True)
handler.debug = config.get_misc('webdav','debug',True)
reg_http_service(HTTPDir(davpath,DAVHandler,OpenERPAuthProvider()))
netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% davpath)
_dc = { 'verbose' : verbose,
'directory' : directory,
'lockemulation' : False,
}
conf = OpenDAVConfig(**_dc)
handler._config = conf
reg_http_service(HTTPDir(directory,DAVHandler,OpenERPAuthProvider()))
netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% directory)
except Exception, e:
logger = netsvc.Logger()
logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
+23 -16
View File
@@ -137,7 +137,8 @@ class email_server(osv.osv):
('imap','IMAP Server'),
],'State', select=True, readonly=False),
'is_ssl':fields.boolean('SSL ?', required=False),
'date': fields.date('Date'),
'attach':fields.boolean('Add Attachments ?', required=False),
'date': fields.date('Date', readonly=True, states={'draft':[('readonly',False)]}),
'user' : fields.char('User Name', size=256, required=True, readonly=True, states={'draft':[('readonly',False)]}),
'password' : fields.char('Password', size=1024, invisible=True, required=True, readonly=True, states={'draft':[('readonly',False)]}),
'note': fields.text('Description'),
@@ -292,17 +293,18 @@ class email_server(osv.osv):
else:
logger.notifyChannel('imap', netsvc.LOG_WARNING, 'method def message_new is not define in model %s' % (model_pool._name))
return False
# for attactment in attachents or []:
# data_attach = {
# 'name': attactment,
# 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
# 'datas_fname': attactment,
# 'description': 'Mail attachment',
# 'res_model': server.object_id.model,
# 'res_id': res_id,
# }
# self.pool.get('ir.attachment').create(cr, uid, data_attach)
if server.attach:
for attactment in attachents or []:
data_attach = {
'name': attactment,
'datas':binascii.b2a_base64(str(attachents.get(attactment))),
'datas_fname': attactment,
'description': 'Mail attachment',
'res_model': server.object_id.model,
'res_id': res_id,
}
self.pool.get('ir.attachment').create(cr, uid, data_attach)
if server.action_id:
action_pool = self.pool.get('ir.actions.server')
@@ -319,10 +321,15 @@ class email_server(osv.osv):
his_id = history_pool.create(cr, uid, res)
return res_id
def __fetch_mail(self, cr, uid, ids, context={}):
sendmail_thread = threading.Thread(target=self.fetch_mail, args=(cr, uid, ids))
sendmail_thread.start()
def set_draft(self, cr, uid, ids, context={}):
self.write(cr, uid, ids , {'state':'draft'})
return True
def button_fetch_mail(self, cr, uid, ids, context={}):
self.fetch_mail(cr, uid, ids)
# sendmail_thread = threading.Thread(target=self.fetch_mail, args=(cr, uid, ids))
# sendmail_thread.start()
return True
def _fetch_mails(self, cr, uid, ids=False, context={}):
+4 -3
View File
@@ -26,15 +26,15 @@
<group col="6" colspan="4">
<field name="name" select="1" colspan="4"/>
<field name="type" select="1" on_change="onchange_server_type(type, is_ssl)"/>
<field name="date" select="1"/>
<field name="is_ssl" select="1" on_change="onchange_server_type(type, is_ssl)"/>
<field name="attach"/>
<field name="active" select="1"/>
</group>
<notebook colspan="4">
<page string="Server &amp; Login">
<group col="2" colspan="2">
<separator string="Server Information" colspan="2"/>
<field name="is_ssl" select="1" on_change="onchange_server_type(type, is_ssl)"/>
<field name="server" />
<field name="port" />
</group>
@@ -58,7 +58,8 @@
</notebook>
<group col="6" colspan="4">
<field name="state" select="1"/>
<button string="Fetch Emails" type="object" name="__fetch_mail"/>
<button string="Fetch Emails" type="object" name="button_fetch_mail"/>
<button string="Set to Draft" type="object" name="set_draft"/>
</group>
</form>
</field>
+1
View File
@@ -43,6 +43,7 @@
'security/ir.model.access.csv',
],
'demo_xml': [],
'test':['test/test_idea.yml'],
'installable': True,
'certificate': '0071515601309',
}
+103
View File
@@ -0,0 +1,103 @@
- |
In order to test the Idea module in OpenERP,
we will create a full flow, from the idea definition up to the votes and discussions on the idea.
- |
Given that I have an Idea category "Technical".
-
!record {model: idea.category, id: idea_category_technical0}:
name: Technical
- |
I create a New Idea of "Technical presentation for 1 hours in every day" and specify category "Technical".
-
!record {model: idea.idea, id: idea_idea_0}:
category_id: idea_category_technical0
description: I want that Technical presentation are arranged for 1 hours in every
day.\nso, on that presentation, we can know all things what improvement and development
are done in our company.\n\n\n\n\n
title: Technical presentation for 1 hours in every day
user_id: base.user_root
- |
I check that idea on draft state.
-
!assert {model: idea.idea, id: idea_idea_0}:
- state == 'draft'
- |
I open this idea by click on "Open" button.
-
!workflow {model: idea.idea, action: idea_open, ref: idea_idea_0}
- |
In order to check voting statistics in real systems I will create 2 new Users "user1".
-
!record {model: res.users, id: res_users_user0}:
company_id: base.main_company
context_lang: en_US
login: user1
name: user1
password: user1
- |
And I will also create user2.
-
!record {model: res.users, id: res_users_user1}:
company_id: base.main_company
context_lang: en_US
login: user2
name: user2
password: user2
- |
In order to post vote I connect as user1 and open the idea page
I click on "Vote" wizard and vote the idea as "Normal"
-
!record {model: idea.post.vote, id: idea_post_vote_0}:
vote: 50
- |
Now I click on "Post Vote" button of wizard.
-
!python {model: idea.post.vote}: |
uid = ref('res_users_user0')
self.do_vote(cr, uid, [ref("idea_post_vote_0")], {'active_id': ref('idea_idea_0')})
- |
To add other vote I connect as user2 and open the idea page
I click on "Vote" wizard and vote the idea as "Very Good"
-
!record {model: idea.post.vote, id: idea_post_vote_1}:
vote: 100
- |
I click on "Post Vote" button of this wizard.
-
!python {model: idea.post.vote}: |
uid = ref('res_users_user1')
self.do_vote(cr, uid, [ref("idea_post_vote_1")], {'active_id': ref('idea_idea_0')})
- |
I can see that the Average score changed in "Average score" field with value 75
-
!record {model: idea.idea, id: idea_idea_0}:
vote_avg: 75
- |
I put one comment "We can learn many things from technical presentation" for the idea.
-
!record {model: idea.idea, id: idea.idea_idea_0}:
comment_ids:
- content: We can learn many things from technical presentation
idea_id: idea.idea_idea_0
user_id: res_users_user1
- |
I connect as Manager and close this idea by click on "Close" button.
-
!workflow {model: idea.idea, action: idea_close, ref: idea_idea_0}
- |
I check that the idea's state is now Accepted
-
!assert {model: idea.idea, id: idea_idea_0}:
- state == 'close'
+4 -1
View File
@@ -47,7 +47,10 @@
'lunch_report.xml',
#'process/lunch_process.xml'
],
"demo_xml": ['lunch_demo.xml'],
"demo_xml": ['lunch_demo.xml',
],
"test": ['test/test_lunch.yml'
],
"installable": True,
}
+1 -13
View File
@@ -42,21 +42,9 @@ class lunch_product(osv.osv):
_name = 'lunch.product'
_description = "Lunch Product"
def _category_name_get(self, cr, uid, context={}):
""" Get category name
@param cr: the current row, from the database cursor,
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values"""
obj = self.pool.get('lunch.category')
cat_ids = obj.search(cr, uid, [])
res = obj.read(cr, uid, cat_ids, ['name', 'category'])
return [(str(r['id']), r['name']) for r in res]+ [('0','')]
_columns = {
'name': fields.char('Name', size=50, required=True),
'category_id': fields.selection(_category_name_get, 'Category', size=32),
'category_id': fields.many2one('lunch.category', 'Category'),
'description': fields.char('Description', size=128, required=False),
'price': fields.float('Price', digits=(16,2)),
'active': fields.boolean('Active'),
+1 -1
View File
@@ -195,7 +195,7 @@
<field name="arch" type="xml">
<form string="Products">
<field name="name" select="1"/>
<field name="category_id" select="1"/>
<field name="category_id" widget='selection' select="1"/>
<field name="description" select="1"/>
<field name="price" select="1"/>
<field name="active" select="1"/>
+130
View File
@@ -0,0 +1,130 @@
- |
In order to test the Lunch module in OpenERP,
I will create one lunch order and then check the effect on cashboxes and cashmoves
- |
Given that I have a category of lunch products "Burger".
-
!record {model: lunch.category, id: lunch_category_burger0}:
name: Burger
- |
Given that I have a product "Club1" in this category with a price of "2.75".
-
!record {model: lunch.product, id: lunch_product_club1}:
category_id: lunch_category_burger0
name: Club1
price: 2.75
- |
Given that I have a cashbox "Employee Cashbox"
-
!record {model: lunch.cashbox, id: lunch_cashbox_cashbox0}:
manager: base.user_root
name: Employee Cashbox
- |
I create a lunch order "LU001" for the "Club1" product
- |
When I select the product "club1", the price of 2.75 is automatically proposed
-
!record {model: lunch.order, id: lunch_order_0}:
date: '2010-04-19'
product: 'lunch_product_club1'
price: 2.75
user_id: base.user_root
- |
I check that lunch order is on draft state after having created it.
-
!assert {model: lunch.order, id: lunch_order_0}:
- state == 'draft'
- |
I confirm the order "LU001" using the "Confirm Order" wizard.
-
!record {model: lunch.order.confirm, id: lunch_order_confirm_0}:
confirm_cashbox: 'lunch_cashbox_cashbox0'
-
I click on "Confirm Order" button of this wizard.
-
!python {model: lunch.order.confirm}: |
self.confirm(cr, uid, [ref('lunch_order_confirm_0')], {'active_ids': [ref('lunch_order_0')]})
- |
I check that the Cash Moves have been generated with the right box
name
-
!assert {model: lunch.order, id: lunch_order_0}:
- cashmove.id != False
- |
I check that the Total on the "Employee Cashbox" is -2.75
-
!assert {model: lunch.cashbox, id: lunch_cashbox_cashbox0}:
- sum_remain == -2.75
- |
I create a new lunch order "LU002" for the "Club1" product, at another date.
-
!record {model: lunch.order, id: lunch_order_1}:
date: '2010-04-22'
product: 'lunch_product_club1'
price: 2.75
user_id: base.user_root
- |
I confirm this order.open wizard and select "Employee Cashbox".
-
!record {model: lunch.order.confirm, id: lunch_order_confirm_1}:
confirm_cashbox: 'lunch_cashbox_cashbox0'
- |
Now I click on "Confirm Order" button of this wizard.
-
!python {model: lunch.order.confirm}: |
self.confirm(cr, uid, [ref('lunch_order_confirm_1')], {'active_ids': [ref('lunch_order_1')]})
- |
I check that the Total on the "Employee Cashbox" is -5.50
-
!assert {model: lunch.cashbox, id: lunch_cashbox_cashbox0}:
- sum_remain == -5.50
- |
I reset the "Employee Cashbox" to zero using the "Set CashBox to Zero" wizard.
-
!record {model: lunch.cashbox.clean, id: lunch_cashbox_clean_0}:
{}
-
Now click on "Set to Zero" button of this wizard.
-
!python {model: lunch.cashbox.clean, id: lunch_cashbox_clean_0}: |
self.set_to_zero(cr, uid, [ref('lunch_cashbox_clean_0')], {'active_ids': [ref('lunch_cashbox_cashbox0')]})
- |
I check that the Total on the "Employee Cashbox" is 0.0
-
!assert {model: lunch.cashbox, id: lunch_cashbox_cashbox0}:
- sum_remain == 0.00
- |
I cancel the order "LU002"
-
!record {model: lunch.order.cancel, id: lunch_order_cancel_0}:
{}
- |
I click on "Yes" button of this wizard for cancel order.
-
!python {model: lunch.order.cancel}: |
self.cancel(cr, uid, [ref('lunch_order_cancel_0')], {'active_ids': [ref('lunch_order_1')]})
- |
I test that the Cash Moves revord have been removed for the order LU002.
-
!assert {model: lunch.order, id: lunch_order_1}:
- cashmove.id == False
- |
I check that the Total on the "Employee Cashbox" is -2.75
-
!assert {model: lunch.cashbox, id: lunch_cashbox_cashbox0}:
- sum_remain == -2.75
-1
View File
@@ -2058,7 +2058,6 @@ class olap_warehouse_wizard(osv.osv_memory):
@param uid: the current user’s ID for security checks,
@param context: A standard dictionary for contextual values
"""
query_obj = self.pool.get('olap.query.logs')
qry_ids = query_obj.search(cr, uid, [('user_id', '=', uid), ('count', '>=', 3)])
+3 -3
View File
@@ -8,13 +8,13 @@ msgstr ""
"Project-Id-Version: pt_BR\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2009-08-28 16:01+0000\n"
"PO-Revision-Date: 2010-02-22 19:37+0000\n"
"PO-Revision-Date: 2010-04-27 11:00+0000\n"
"Last-Translator: Luiz Fernando M.França <Unknown>\n"
"Language-Team: <pt@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2010-04-17 03:55+0000\n"
"X-Launchpad-Export-Date: 2010-04-28 03:44+0000\n"
"X-Generator: Launchpad (build Unknown)\n"
#. module: project
@@ -461,7 +461,7 @@ msgstr "Tempo Planejado"
#: view:project.task:0
#: view:project.task.work:0
msgid "Task Work"
msgstr "Trabalho na Tarefa"
msgstr "Tarefas realizadas"
#. module: project
#: selection:res.company,project_time_mode:0
+4 -2
View File
@@ -20,12 +20,14 @@
##############################################################################
from osv import fields, osv
from caldav import caldav
from caldav import calendar
class project_task(osv.osv):
_name = "project.task"
_inherit = ["calendar.todo", "project.task"]
_columns = {
'write_date' : fields.datetime('Write Date'),
'create_date' : fields.datetime('Create Date'),
'attendee_ids': fields.many2many('calendar.attendee', \
'task_attendee_rel', 'task_id', 'attendee_id', 'Attendees'),
}
@@ -53,7 +55,7 @@ class project_task(osv.osv):
hours = (val['planned_hours'].seconds / float(3600)) + \
(val['planned_hours'].days * 24)
val['planned_hours'] = hours
exists, r_id = caldav.uid2openobjectid(cr, val['id'], self._name, val.get('recurrent_id'))
exists, r_id = calendar.uid2openobjectid(cr, val['id'], self._name, val.get('recurrent_id'))
val.pop('id')
if exists:
self.write(cr, uid, [exists], val)
@@ -1,9 +1,11 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<data noupdate="1">
<record model="basic.calendar" id="caldav.basic_calendar2">
<field name="name">Todo</field>
<field name="collection_id" ref="document.dir_calendars"></field>
<field name="type">vtodo</field>
</record>
<record model="basic.calendar.lines" id="caldav.calendar_lines_todo">
+3 -2
View File
@@ -84,10 +84,11 @@ class project_issue(osv.osv):
resource_id = False
if issue.user_id:
resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)])
resource_id = len(resource_ids) and resource_ids[0] or False
if resource_ids and len(resource_ids):
resource_id = resource_ids[0]
duration = float(ans.days)
if issue.section_id.resource_calendar_id:
if issue.section_id and issue.section_id.resource_calendar_id:
duration = float(ans.days) * 24
new_dates = cal_obj.interval_get(cr,
uid,
+20 -9
View File
@@ -68,7 +68,9 @@ class smtpclient(osv.osv):
_columns = {
'name' : fields.char('Server Name', size=256, required=True),
'from_email' : fields.char('Email From', size=256),
'email' : fields.char('Email Address', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}),
'email':fields.char('Email Address', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}),
'cc_to':fields.char('Send copy to', size=256, readonly=True, states={'new':[('readonly',False)]}, help="use comma to supply multiple address. email@domain.com, email2@domain.com"),
'bcc_to':fields.char('Send blind copy to', size=256, readonly=True, states={'new':[('readonly',False)]}, help="use comma to supply multiple address. email@domain.com, email2@domain.com"),
'user' : fields.char('User Name', size=256, readonly=True, states={'new':[('readonly',False)]}),
'password' : fields.char('Password', size=1024, invisible=True, readonly=True, states={'new':[('readonly',False)]}),
'server' : fields.char('SMTP Server', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}),
@@ -386,7 +388,7 @@ class smtpclient(osv.osv):
emailto = [emailto]
ir_pool = self.pool.get('ir.attachment')
for to in emailto:
msg = MIMEMultipart()
msg['Subject'] = tools.ustr(subject)
@@ -420,7 +422,13 @@ class smtpclient(osv.osv):
msg['X-OpenERP-Server-Host'] = socket.gethostname()
msg['X-OpenERP-Server-Version'] = release.version
msg['Message-Id'] = "<%s-openerp-@%s>" % (time.time(), socket.gethostname())
if smtp_server.cc_to:
msg['Cc'] = smtp_server.cc_to
if smtp_server.bcc_to:
msg['Bcc'] = smtp_server.bcc_to
#attach files from disc
for file in attachments:
part = MIMEBase('application', "octet-stream")
@@ -442,8 +450,8 @@ class smtpclient(osv.osv):
data = {
'to':to,
'server_id':server_id,
'cc':False,
'bcc':False,
'cc':smtp_server.cc_to or False,
'bcc':smtp_server.bcc_to or False,
'name':subject,
'body':body,
'serialized_message':message,
@@ -499,7 +507,7 @@ class smtpclient(osv.osv):
self.open_connection(cr, uid, ids, email.server_id.id)
try:
self.smtpServer[email.server_id.id].sendmail(email.server_id.email, email.to, tools.ustr(email.serialized_message))
self.smtpServer[email.server_id.id].sendmail(email.server_id.email, [email.to, email.cc, email.bcc], tools.ustr(email.serialized_message))
message = "message sent successfully to %s from %s server" % (email.to, email.server_id.name)
logger.notifyChannel('smtp', netsvc.LOG_INFO, message)
except Exception, e:
@@ -539,6 +547,7 @@ class smtpclient(osv.osv):
return result
def set_to_draft(self, cr, uid, ids, context={}):
self.stop_process(cr, uid, ids, context)
self.write(cr, uid, ids, {'state':'new', 'code':False})
return True
@@ -555,17 +564,19 @@ class smtpclient(osv.osv):
'interval_type':'minutes',
'user_id':uid,
'numbercall':-1,
'doall':False
'doall':False,
'active':False
}
id = self.pool.get('ir.cron').create(cr, uid, res)
self.write(cr, uid, ids, {'process_id':id})
return True
def start_process(self, cr, uid, ids, context={}):
process = self.browse(cr, uid, ids[0], context)
if not process.process_id:
if not process.process_id or process.state != 'confirm':
raise osv.except_osv(_('SMTP Server Error !'), _('Server is not Verified, Please Verify the Server !'))
pid = process.process_id.id
self.pool.get('ir.cron').write(cr, uid, [pid], {'active':True})
self.write(cr, uid, ids, {'pstate':'running'})
+6
View File
@@ -77,6 +77,12 @@
<field name="delete_queue" select="2" />
<field name="delete_queue_period" select="2" attrs="{'readonly':[('delete_queue','not in',['content','all'])]}"/>
</group>
<group col="2" colspan="2">
<separator string="Email Copy Configuration" colspan="2" />
<field name="cc_to" select="2" />
<field name="bcc_to" select="2" />
</group>
<newline/>
<separator string="Server Information" colspan="4"/>
<group col="3" colspan="4">
+1 -1
View File
@@ -48,7 +48,7 @@ class verifycode(wizard.interface):
if code == data['form']['code']:
if server_pool.create_process(cr, uid, [data['id']], context={}):
server_pool.write(cr, uid, [data['id']], {'state':'confirm', 'pstate':'running'})
print 'process created'
server_pool.start_process(cr, uid, [data['id']], context)
else:
raise osv.except_osv(_('Error'), _('Verification failed. Invalid Verification Code!'))
+1 -1
View File
@@ -47,4 +47,4 @@
##############################################################################
import partner
import installer

Some files were not shown because too many files have changed in this diff Show More