[MERGE]: Merge with lp:~openerp-dev/openobject-addons/trunk-dev-addons2
bzr revid: rpa@tinyerp.com-20100428104744-gqjjqnw5gusv9vbp
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,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>
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -265,3 +265,5 @@ class base_action_rule(osv.osv):
|
||||
|
||||
|
||||
base_action_rule()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -73,3 +73,5 @@ class crm_fundraising(osv.osv):
|
||||
}
|
||||
|
||||
crm_fundraising()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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"/>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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')])
|
||||
|
||||
@@ -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')])
|
||||
@@ -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')])
|
||||
-
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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')])
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -37,6 +37,9 @@
|
||||
'security/ir.model.access.csv'
|
||||
],
|
||||
'demo_xml': [],
|
||||
'test': [
|
||||
'test/document_ftp_test.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
'certificate': None,
|
||||
|
||||
@@ -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.
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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={}):
|
||||
|
||||
@@ -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 & 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>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'demo_xml': [],
|
||||
'test':['test/test_idea.yml'],
|
||||
'installable': True,
|
||||
'certificate': '0071515601309',
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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'),
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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!'))
|
||||
|
||||
|
||||
@@ -47,4 +47,4 @@
|
||||
##############################################################################
|
||||
|
||||
import partner
|
||||
|
||||
import installer
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user