[MERGE] Merge with lp:~openerp-dev/openobject-addons/trunk-dev-addons2
bzr revid: ysa@tinyerp.co.in-20100510113845-pv2tcn0g49mqtzd8
This commit is contained in:
commit
82a466922c
|
@ -33,18 +33,115 @@ class base_action_rule(osv.osv):
|
|||
|
||||
_name = 'base.action.rule'
|
||||
_description = 'Action Rules'
|
||||
|
||||
def _state_get(self, cr, uid, context={}):
|
||||
""" Get State
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return self.state_get(cr, uid, context=context)
|
||||
|
||||
|
||||
def _priority_get(self, cr, uid, context={}):
|
||||
""" Get Priority
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return self.priority_get(cr, uid, context=context)
|
||||
|
||||
def state_get(self, cr, uid, context={}):
|
||||
""" Get State
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return [('','')]
|
||||
|
||||
def priority_get(self, cr, uid, context={}):
|
||||
""" Get Priority
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return [('','')]
|
||||
|
||||
_columns = {
|
||||
'name': fields.many2one('ir.model', 'Model', required=True),
|
||||
'max_level': fields.integer('Max Level', help='Specifies maximum level.'),
|
||||
'rule_lines': fields.one2many('base.action.rule.line','rule_id','Rule Lines'),
|
||||
'create_date': fields.datetime('Create Date', readonly=1),
|
||||
'active': fields.boolean('Active')
|
||||
'active': fields.boolean('Active', help="If the active field is set to true,\
|
||||
it will allow you to hide the rule without removing it."),
|
||||
'sequence': fields.integer('Sequence', help="Gives the sequence order when\
|
||||
displaying a list of rules."),
|
||||
|
||||
'trg_date_type': fields.selection([
|
||||
('none','None'),
|
||||
('create','Creation Date'),
|
||||
('action_last','Last Action Date'),
|
||||
('date','Date'),
|
||||
('deadline', 'Deadline'),
|
||||
], 'Trigger Date', size=16),
|
||||
'trg_date_range': fields.integer('Delay after trigger date',help="Delay After Trigger Date,\
|
||||
specifies you can put a negative number " \
|
||||
"if you need a delay before the trigger date, like sending a reminder 15 minutes before a meeting."),
|
||||
'trg_date_range_type': fields.selection([('minutes', 'Minutes'),('hour','Hours'),\
|
||||
('day','Days'),('month','Months')], 'Delay type'),
|
||||
|
||||
|
||||
'trg_user_id': fields.many2one('res.users', 'Responsible'),
|
||||
|
||||
'trg_partner_id': fields.many2one('res.partner', 'Partner'),
|
||||
'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
|
||||
'trg_state_from': fields.selection(_state_get, 'State', size=16),
|
||||
'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16),
|
||||
'trg_priority_from': fields.selection(_priority_get, 'Minimum Priority'),
|
||||
'trg_priority_to': fields.selection(_priority_get, 'Maximum Priority'),
|
||||
|
||||
'act_method': fields.char('Call Object Method', size=64),
|
||||
'act_user_id': fields.many2one('res.users', 'Set responsible to'),
|
||||
'act_state': fields.selection(_state_get, 'Set state to', size=16),
|
||||
'act_priority': fields.selection(_priority_get, 'Set priority to'),
|
||||
'act_email_cc': fields.char('Add watchers (Cc)', size=250, help="These people\
|
||||
will receive a copy of the future communication between partner and users by email"),
|
||||
|
||||
'act_remind_partner': fields.boolean('Remind Partner', help="Check this if\
|
||||
you want the rule to send a reminder by email to the partner."),
|
||||
'act_remind_user': fields.boolean('Remind responsible', help="Check this if \
|
||||
you want the rule to send a reminder by email to the user."),
|
||||
'act_reply_to': fields.char('Reply-To', size=64),
|
||||
'act_remind_attach': fields.boolean('Remind with attachment', help="Check this if\
|
||||
you want that all documents attached to the object be attached \
|
||||
to the reminder email sent."),
|
||||
|
||||
'act_mail_to_user': fields.boolean('Mail to responsible',help="Check this if \
|
||||
you want the rule to send an email to the responsible person."),
|
||||
'act_mail_to_watchers': fields.boolean('Mail to watchers (CC)',help="Check this\
|
||||
if you want the rule to mark CC(mail to any other person\
|
||||
defined in actions)."),
|
||||
'act_mail_to_email': fields.char('Mail to these emails', size=128,help="Email-id \
|
||||
of the persons whom mail is to be sent"),
|
||||
'act_mail_body': fields.text('Mail body',help="Content of mail"),
|
||||
'regex_name': fields.char('Regular Expression on Model Name', size=128),
|
||||
'server_action_id': fields.many2one('ir.actions.server','Server Action',help="Describes the\
|
||||
action name." \
|
||||
"eg:on which object which action to be taken on basis of which condition"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'active': lambda *a: True,
|
||||
'max_level': lambda *a: 15,
|
||||
'trg_date_type': lambda *a: 'none',
|
||||
'trg_date_range_type': lambda *a: 'day',
|
||||
'act_mail_to_user': lambda *a: 0,
|
||||
'act_remind_partner': lambda *a: 0,
|
||||
'act_remind_user': lambda *a: 0,
|
||||
'act_mail_to_watchers': lambda *a: 0,
|
||||
}
|
||||
|
||||
def format_body(self, body):
|
||||
|
@ -201,175 +298,56 @@ class base_action_rule(osv.osv):
|
|||
|
||||
if not scrit:
|
||||
scrit = []
|
||||
rule_line_obj = self.pool.get('base.action.rule.line')
|
||||
for rule in self.browse(cr, uid, ids):
|
||||
level = rule.max_level
|
||||
for action in self.browse(cr, uid, ids):
|
||||
level = action.max_level
|
||||
if not level:
|
||||
break
|
||||
newactions = []
|
||||
scrit += [('rule_id','=',rule.id)]
|
||||
line_ids = rule_line_obj.search(cr, uid, scrit)
|
||||
actions = rule_line_obj.browse(cr, uid, line_ids, context=context)
|
||||
model_obj = self.pool.get(rule.name.model)
|
||||
model_obj = self.pool.get(action.name.model)
|
||||
for obj in objects:
|
||||
for action in actions:
|
||||
ok = self.do_check(cr, uid, action, obj, context=context)
|
||||
if not ok:
|
||||
continue
|
||||
ok = self.do_check(cr, uid, action, obj, context=context)
|
||||
if not ok:
|
||||
continue
|
||||
|
||||
base = False
|
||||
if hasattr(obj, 'create_date') and action.trg_date_type=='create':
|
||||
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
|
||||
elif hasattr(obj, 'create_date') and action.trg_date_type=='action_last':
|
||||
if hasattr(obj, 'date_action_last') and obj.date_action_last:
|
||||
base = mx.DateTime.strptime(obj.date_action_last, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
|
||||
elif hasattr(obj, 'date_deadline') and action.trg_date_type=='deadline' \
|
||||
and obj.date_deadline:
|
||||
base = mx.DateTime.strptime(obj.date_deadline, '%Y-%m-%d %H:%M:%S')
|
||||
elif hasattr(obj, 'date') and action.trg_date_type=='date' and obj.date:
|
||||
base = mx.DateTime.strptime(obj.date, '%Y-%m-%d %H:%M:%S')
|
||||
if base:
|
||||
fnct = {
|
||||
'minutes': lambda interval: mx.DateTime.RelativeDateTime(minutes=interval),
|
||||
'day': lambda interval: mx.DateTime.RelativeDateTime(days=interval),
|
||||
'hour': lambda interval: mx.DateTime.RelativeDateTime(hours=interval),
|
||||
'month': lambda interval: mx.DateTime.RelativeDateTime(months=interval),
|
||||
}
|
||||
d = base + fnct[action.trg_date_range_type](action.trg_date_range)
|
||||
dt = d.strftime('%Y-%m-%d %H:%M:%S')
|
||||
ok = False
|
||||
if hasattr(obj, 'date_action_last') and hasattr(obj, 'date_action_next'):
|
||||
ok = (dt <= time.strftime('%Y-%m-%d %H:%M:%S')) and \
|
||||
((not obj.date_action_next) or \
|
||||
(dt >= obj.date_action_next and \
|
||||
obj.date_action_last < obj.date_action_next))
|
||||
if not ok:
|
||||
if not obj.date_action_next or dt < obj.date_action_next:
|
||||
obj.date_action_next = dt
|
||||
model_obj.write(cr, uid, [obj.id], {'date_action_next': dt}, context)
|
||||
base = False
|
||||
if hasattr(obj, 'create_date') and action.trg_date_type=='create':
|
||||
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
|
||||
elif hasattr(obj, 'create_date') and action.trg_date_type=='action_last':
|
||||
if hasattr(obj, 'date_action_last') and obj.date_action_last:
|
||||
base = mx.DateTime.strptime(obj.date_action_last, '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
ok = action.trg_date_type=='none'
|
||||
base = mx.DateTime.strptime(obj.create_date[:19], '%Y-%m-%d %H:%M:%S')
|
||||
elif hasattr(obj, 'date_deadline') and action.trg_date_type=='deadline' \
|
||||
and obj.date_deadline:
|
||||
base = mx.DateTime.strptime(obj.date_deadline, '%Y-%m-%d %H:%M:%S')
|
||||
elif hasattr(obj, 'date') and action.trg_date_type=='date' and obj.date:
|
||||
base = mx.DateTime.strptime(obj.date, '%Y-%m-%d %H:%M:%S')
|
||||
if base:
|
||||
fnct = {
|
||||
'minutes': lambda interval: mx.DateTime.RelativeDateTime(minutes=interval),
|
||||
'day': lambda interval: mx.DateTime.RelativeDateTime(days=interval),
|
||||
'hour': lambda interval: mx.DateTime.RelativeDateTime(hours=interval),
|
||||
'month': lambda interval: mx.DateTime.RelativeDateTime(months=interval),
|
||||
}
|
||||
d = base + fnct[action.trg_date_range_type](action.trg_date_range)
|
||||
dt = d.strftime('%Y-%m-%d %H:%M:%S')
|
||||
ok = False
|
||||
if hasattr(obj, 'date_action_last') and hasattr(obj, 'date_action_next'):
|
||||
ok = (dt <= time.strftime('%Y-%m-%d %H:%M:%S')) and \
|
||||
((not obj.date_action_next) or \
|
||||
(dt >= obj.date_action_next and \
|
||||
obj.date_action_last < obj.date_action_next))
|
||||
if not ok:
|
||||
if not obj.date_action_next or dt < obj.date_action_next:
|
||||
obj.date_action_next = dt
|
||||
model_obj.write(cr, uid, [obj.id], {'date_action_next': dt}, context)
|
||||
else:
|
||||
ok = action.trg_date_type=='none'
|
||||
|
||||
if ok:
|
||||
self.do_action(cr, uid, action, model_obj, obj, context)
|
||||
break
|
||||
if ok:
|
||||
self.do_action(cr, uid, action, model_obj, obj, context)
|
||||
break
|
||||
level -= 1
|
||||
return True
|
||||
base_action_rule()
|
||||
|
||||
class base_action_rule_line(osv.osv):
|
||||
_name = 'base.action.rule.line'
|
||||
_description = 'Action Rule Lines'
|
||||
|
||||
def _state_get(self, cr, uid, context={}):
|
||||
""" Get State
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return self.state_get(cr, uid, context=context)
|
||||
def _priority_get(self, cr, uid, context={}):
|
||||
""" Get Priority
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return self.priority_get(cr, uid, context=context)
|
||||
|
||||
def state_get(self, cr, uid, context={}):
|
||||
""" Get State
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return [('','')]
|
||||
def priority_get(self, cr, uid, context={}):
|
||||
""" Get Priority
|
||||
@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 context: A standard dictionary for contextual values """
|
||||
|
||||
return [('','')]
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Rule Name',size=64, required=True),
|
||||
'rule_id': fields.many2one('base.action.rule','Rule'),
|
||||
'active': fields.boolean('Active', help="If the active field is set to true,\
|
||||
it will allow you to hide the rule without removing it."),
|
||||
'sequence': fields.integer('Sequence', help="Gives the sequence order when\
|
||||
displaying a list of rules."),
|
||||
|
||||
'trg_date_type': fields.selection([
|
||||
('none','None'),
|
||||
('create','Creation Date'),
|
||||
('action_last','Last Action Date'),
|
||||
('date','Date'),
|
||||
('deadline', 'Deadline'),
|
||||
], 'Trigger Date', size=16),
|
||||
'trg_date_range': fields.integer('Delay after trigger date',help="Delay After Trigger Date,\
|
||||
specifies you can put a negative number " \
|
||||
"if you need a delay before the trigger date, like sending a reminder 15 minutes before a meeting."),
|
||||
'trg_date_range_type': fields.selection([('minutes', 'Minutes'),('hour','Hours'),\
|
||||
('day','Days'),('month','Months')], 'Delay type'),
|
||||
|
||||
|
||||
'trg_user_id': fields.many2one('res.users', 'Responsible'),
|
||||
|
||||
'trg_partner_id': fields.many2one('res.partner', 'Partner'),
|
||||
'trg_partner_categ_id': fields.many2one('res.partner.category', 'Partner Category'),
|
||||
'trg_state_from': fields.selection(_state_get, 'State', size=16),
|
||||
'trg_state_to': fields.selection(_state_get, 'Button Pressed', size=16),
|
||||
'trg_priority_from': fields.selection(_priority_get, 'Minimum Priority'),
|
||||
'trg_priority_to': fields.selection(_priority_get, 'Maximum Priority'),
|
||||
|
||||
'act_method': fields.char('Call Object Method', size=64),
|
||||
'act_user_id': fields.many2one('res.users', 'Set responsible to'),
|
||||
'act_state': fields.selection(_state_get, 'Set state to', size=16),
|
||||
'act_priority': fields.selection(_priority_get, 'Set priority to'),
|
||||
'act_email_cc': fields.char('Add watchers (Cc)', size=250, help="These people\
|
||||
will receive a copy of the future communication between partner and users by email"),
|
||||
|
||||
'act_remind_partner': fields.boolean('Remind Partner', help="Check this if\
|
||||
you want the rule to send a reminder by email to the partner."),
|
||||
'act_remind_user': fields.boolean('Remind responsible', help="Check this if \
|
||||
you want the rule to send a reminder by email to the user."),
|
||||
'act_reply_to': fields.char('Reply-To', size=64),
|
||||
'act_remind_attach': fields.boolean('Remind with attachment', help="Check this if\
|
||||
you want that all documents attached to the object be attached \
|
||||
to the reminder email sent."),
|
||||
|
||||
'act_mail_to_user': fields.boolean('Mail to responsible',help="Check this if \
|
||||
you want the rule to send an email to the responsible person."),
|
||||
'act_mail_to_watchers': fields.boolean('Mail to watchers (CC)',help="Check this\
|
||||
if you want the rule to mark CC(mail to any other person\
|
||||
defined in actions)."),
|
||||
'act_mail_to_email': fields.char('Mail to these emails', size=128,help="Email-id \
|
||||
of the persons whom mail is to be sent"),
|
||||
'act_mail_body': fields.text('Mail body',help="Content of mail"),
|
||||
'regex_name': fields.char('Regular Expression on Model Name', size=128),
|
||||
'server_action_id': fields.many2one('ir.actions.server','Server Action',help="Describes the\
|
||||
action name." \
|
||||
"eg:on which object which action to be taken on basis of which condition"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'active': lambda *a: 1,
|
||||
'trg_date_type': lambda *a: 'none',
|
||||
'trg_date_range_type': lambda *a: 'day',
|
||||
'act_mail_to_user': lambda *a: 0,
|
||||
'act_remind_partner': lambda *a: 0,
|
||||
'act_remind_user': lambda *a: 0,
|
||||
'act_mail_to_watchers': lambda *a: 0,
|
||||
}
|
||||
|
||||
_order = 'sequence'
|
||||
|
||||
|
||||
def _check_mail(self, cr, uid, ids, context=None):
|
||||
""" Check Mail
|
||||
|
@ -393,6 +371,6 @@ class base_action_rule_line(osv.osv):
|
|||
(_check_mail, 'Error: The mail is not well formated', ['act_mail_body']),
|
||||
]
|
||||
|
||||
base_action_rule_line()
|
||||
base_action_rule()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -17,51 +17,6 @@
|
|||
<field name="name" select="1"/>
|
||||
<field name="active"/>
|
||||
<field name="max_level" />
|
||||
<separator colspan="4" string="Rule lines"/>
|
||||
<field name="rule_lines" colspan="4" nolabel="1"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action Rule Tree View -->
|
||||
|
||||
<record id="view_base_action_rule_tree" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.tree</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Action Rule">
|
||||
<field name="name" colspan="4"/>
|
||||
<field name="max_level"/>
|
||||
<field name="rule_lines" colspan="4" nolabel="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action Rule Action -->
|
||||
|
||||
<record id="base_action_rule_act" model="ir.actions.act_window">
|
||||
<field name="name">Action Rules</field>
|
||||
<field name="res_model">base.action.rule</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_base_action_rule_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_base_action_rule_form"
|
||||
parent="menu_base_action_rule" action="base_action_rule_act" />
|
||||
|
||||
<!--
|
||||
Action Rule Lines Form View
|
||||
-->
|
||||
<record id="view_base_action_rule_line_form" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.line.form</field>
|
||||
<field name="model">base.action.rule.line</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Action Rule Line">
|
||||
<field name="name" select="1"/>
|
||||
<field name="active"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Conditions">
|
||||
<group col="2" colspan="2" name="model">
|
||||
|
@ -133,29 +88,35 @@
|
|||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</record>
|
||||
|
||||
<!-- Action Rule Lines Tree View -->
|
||||
<!-- Action Rule Tree View -->
|
||||
|
||||
<record id="view_base_action_rule_line_tree" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.line.tree</field>
|
||||
<field name="model">base.action.rule.line</field>
|
||||
<record id="view_base_action_rule_tree" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.tree</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Action Rule Lines">
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
<tree string="Action Rule">
|
||||
<field name="name" colspan="4"/>
|
||||
<field name="max_level"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action Rule Action -->
|
||||
|
||||
<record id="base_action_rule_act" model="ir.actions.act_window">
|
||||
<field name="name">Action Rules</field>
|
||||
<field name="res_model">base.action.rule</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_base_action_rule_tree"/>
|
||||
</record>
|
||||
|
||||
<act_window
|
||||
domain="[('rule_id', '=', active_id)]"
|
||||
id="act_rule_lines_open"
|
||||
name="Open Rule Lines"
|
||||
context="{'rule_id': active_id}"
|
||||
res_model="base.action.rule.line"
|
||||
src_model="base.action.rule"/>
|
||||
<menuitem id="menu_base_action_rule_form"
|
||||
parent="menu_base_action_rule" action="base_action_rule_act" />
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_base_action_rule","base.action.rule","model_base_action_rule",,1,1,1,1
|
||||
"access_base_action_rule_line","base.action.rule.line","model_base_action_rule_line",,1,1,1,1
|
||||
|
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, date
|
||||
from dateutil import parser
|
||||
from dateutil import rrule
|
||||
from osv import fields, osv
|
||||
|
@ -36,7 +36,7 @@ months = {
|
|||
10: "October", 11: "November", 12: "December"
|
||||
}
|
||||
|
||||
def get_recurrent_dates(rrulestring, exdate, startdate=None):
|
||||
def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
|
||||
"""
|
||||
Get recurrent dates based on Rule string considering exdate and start date
|
||||
@param rrulestring: Rulestring
|
||||
|
@ -56,6 +56,8 @@ def get_recurrent_dates(rrulestring, exdate, startdate=None):
|
|||
for date in exdate:
|
||||
datetime_obj = todate(date)
|
||||
rset1._exdate.append(datetime_obj)
|
||||
if exrule:
|
||||
rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
|
||||
re_dates = map(lambda x:x.strftime('%Y-%m-%d %H:%M:%S'), rset1._iter())
|
||||
return re_dates
|
||||
|
||||
|
@ -426,6 +428,72 @@ property or property parameter."),
|
|||
self.write(cr, uid, ids, {'state': status})
|
||||
return True
|
||||
|
||||
def get_ics_file(self, cr, uid, event_obj, context=None):
|
||||
"""
|
||||
Returns iCalendar file for the event invitation
|
||||
@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 event_obj: Event object (browse record)
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: .ics file content
|
||||
"""
|
||||
res = None
|
||||
def ics_datetime(idate, short=False):
|
||||
if short:
|
||||
return date.fromtimestamp(time.mktime(time.strptime(idate, '%Y-%m-%d')))
|
||||
else:
|
||||
return datetime.strptime(idate, '%Y-%m-%d %H:%M:%S')
|
||||
try:
|
||||
import vobject
|
||||
except ImportError:
|
||||
return res
|
||||
cal = vobject.iCalendar()
|
||||
event = cal.add('vevent')
|
||||
event.add('created').value = ics_datetime(time.strftime('%Y-%m-%d %H:%M:%S'))
|
||||
event.add('dtstart').value = ics_datetime(event_obj.date)
|
||||
event.add('dtend').value = ics_datetime(event_obj.date_deadline)
|
||||
event.add('summary').value = event_obj.name
|
||||
if event_obj.description:
|
||||
event.add('description').value = event_obj.description
|
||||
if event_obj.location:
|
||||
event.add('location').value = event_obj.location
|
||||
if event_obj.rrule:
|
||||
event.add('rrule').value = event_obj.rrule
|
||||
|
||||
if event_obj.alarm_id:
|
||||
# computes alarm data
|
||||
valarm = event.add('valarm')
|
||||
alarm_object = self.pool.get('res.alarm')
|
||||
alarm_data = alarm_object.read(cr, uid, event_obj.alarm_id.id, context=context)
|
||||
# Compute trigger data
|
||||
interval = alarm_data['trigger_interval']
|
||||
occurs = alarm_data['trigger_occurs']
|
||||
duration = (occurs == 'after' and alarm_data['trigger_duration']) \
|
||||
or -(alarm_data['trigger_duration'])
|
||||
related = alarm_data['trigger_related']
|
||||
trigger = valarm.add('TRIGGER')
|
||||
trigger.params['related'] = [related.upper()]
|
||||
if interval == 'days':
|
||||
delta = timedelta(days=duration)
|
||||
if interval == 'hours':
|
||||
delta = timedelta(hours=duration)
|
||||
if interval == 'minutes':
|
||||
delta = timedelta(minutes=duration)
|
||||
trigger.value = delta
|
||||
|
||||
# Compute other details
|
||||
valarm.add('DESCRIPTION').value = alarm_data['name'] or 'OpenERP'
|
||||
|
||||
for attendee in event_obj.attendee_ids:
|
||||
attendee_add = event.add('attendee')
|
||||
attendee_add.params['CUTYPE'] = [str(attendee.cutype)]
|
||||
attendee_add.params['ROLE'] = [str(attendee.role)]
|
||||
attendee_add.params['RSVP'] = [str(attendee.rsvp)]
|
||||
attendee_add.value = 'MAILTO:' + attendee.email
|
||||
res = cal.serialize()
|
||||
return res
|
||||
|
||||
def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context={}):
|
||||
"""
|
||||
Send mail for calendar attendee.
|
||||
|
@ -443,7 +511,7 @@ property or property parameter."),
|
|||
res_obj = att.ref
|
||||
sub = '[%s Invitation][%d] %s' % (company, att.id, res_obj.name)
|
||||
att_infos = []
|
||||
other_invitaion_ids = self.search(cr, uid, [('ref', '=', att.ref._name + ',' + str(att.ref.id))])
|
||||
other_invitaion_ids = self.search(cr, uid, [('ref', '=', res_obj._name + ',' + str(res_obj.id))])
|
||||
for att2 in self.browse(cr, uid, other_invitaion_ids):
|
||||
att_infos.append(((att2.user_id and att2.user_id.name) or \
|
||||
(att2.partner_id and att2.partner_id.name) or \
|
||||
|
@ -459,15 +527,17 @@ property or property parameter."),
|
|||
'company': company
|
||||
}
|
||||
body = html_invitation % body_vals
|
||||
attach = self.get_ics_file(cr, uid, res_obj, context=context)
|
||||
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,
|
||||
attach=attach and [('invitation.ics', attach)] or None,
|
||||
subtype='html',
|
||||
reply_to=email_from
|
||||
)
|
||||
return True
|
||||
|
||||
def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
|
||||
|
@ -659,41 +729,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.
|
||||
|
@ -981,7 +1051,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, \
|
||||
|
@ -1164,8 +1234,10 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
|
|||
if count > limit:
|
||||
break
|
||||
event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
|
||||
if start_date and start_date <= event_date:
|
||||
start_date = event_date
|
||||
# To check: If the start date is replace by event date .. the event date will be changed by that of calendar code
|
||||
# if start_date and start_date <= event_date:
|
||||
# start_date = event_date
|
||||
start_date = event_date
|
||||
if not data['rrule']:
|
||||
if start_date and (event_date < start_date):
|
||||
continue
|
||||
|
@ -1213,7 +1285,7 @@ e.g.: Every other month on the last Sunday of the month for 10 occurrences:\
|
|||
if isinstance(select, (str, int, long)):
|
||||
return ids and ids[0] or False
|
||||
return ids
|
||||
|
||||
|
||||
def compute_rule_string(self, cr, uid, datas, context=None, *args):
|
||||
"""
|
||||
Compute rule string.
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
<field name="date" string="Start Date" required="1" select="1"
|
||||
on_change="onchange_dates(date,duration,False,allday)" />
|
||||
<field name="duration" widget="float_time"
|
||||
on_change="onchange_dates(date,duration,False,allday)" attrs="{'readonly': [('allday', '=', True)]}"/>
|
||||
on_change="onchange_dates(date,duration,False,allday)" attrs="{'invisible': [('allday', '=', True)]}"/>
|
||||
<field name="date_deadline" string="End Date" required="1"
|
||||
on_change="onchange_dates(date,False,date_deadline)" />
|
||||
<field name="location" />
|
||||
|
|
|
@ -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
|
||||
|
|
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:
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -27,9 +27,6 @@ import crm_meeting
|
|||
import crm_opportunity
|
||||
import crm_lead
|
||||
import crm_phonecall
|
||||
import crm_claim
|
||||
import crm_fundraising
|
||||
import crm_helpdesk
|
||||
|
||||
import report
|
||||
import wizard
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
'category': 'Generic Modules/CRM & SRM',
|
||||
'description': """The generic Open ERP Customer Relationship Management
|
||||
system enables a group of people to intelligently and efficiently manage
|
||||
leads, opportunities, claims, meeting, phonecall etc.
|
||||
leads, opportunities, meeting, phonecall etc.
|
||||
It manages key tasks such as communication, identification, prioritization,
|
||||
assignment, resolution and notification.
|
||||
|
||||
|
@ -55,13 +55,10 @@ between mails and Open ERP.""",
|
|||
'init_xml': [
|
||||
'crm_data.xml',
|
||||
'crm_meeting_data.xml',
|
||||
'crm_claims_data.xml',
|
||||
'crm_fund_data.xml',
|
||||
'crm_helpdesk_data.xml',
|
||||
'crm_lead_data.xml',
|
||||
'crm_meeting_data.xml',
|
||||
'crm_opportunity_data.xml',
|
||||
'crm_phonecall_data.xml',
|
||||
'crm_lead_data.xml',
|
||||
'crm_meeting_data.xml',
|
||||
'crm_opportunity_data.xml',
|
||||
'crm_phonecall_data.xml',
|
||||
],
|
||||
|
||||
'update_xml': [
|
||||
|
@ -83,27 +80,21 @@ between mails and Open ERP.""",
|
|||
'crm_action_rule_view.xml',
|
||||
'crm_lead_view.xml',
|
||||
'crm_lead_menu.xml',
|
||||
|
||||
'crm_meeting_view.xml',
|
||||
'crm_meeting_menu.xml',
|
||||
|
||||
'crm_phonecall_view.xml',
|
||||
'crm_phonecall_menu.xml',
|
||||
|
||||
'crm_opportunity_view.xml',
|
||||
'crm_opportunity_menu.xml',
|
||||
'crm_fund_view.xml',
|
||||
'crm_fund_menu.xml',
|
||||
'crm_claims_view.xml',
|
||||
'crm_claims_menu.xml',
|
||||
|
||||
'crm_helpdesk_view.xml',
|
||||
'crm_helpdesk_menu.xml',
|
||||
|
||||
|
||||
'security/crm_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
'report/crm_report_view.xml',
|
||||
'report/crm_claim_report_view.xml',
|
||||
'report/crm_lead_report_view.xml',
|
||||
'report/crm_fundraising_report_view.xml',
|
||||
'report/crm_opportunity_report_view.xml' ,
|
||||
'report/crm_phonecall_report_view.xml',
|
||||
|
||||
|
@ -111,14 +102,16 @@ between mails and Open ERP.""",
|
|||
],
|
||||
'demo_xml': [
|
||||
'crm_demo.xml',
|
||||
'crm_claims_demo.xml',
|
||||
'crm_fund_demo.xml',
|
||||
'crm_helpdesk_demo.xml',
|
||||
'crm_lead_demo.xml',
|
||||
'crm_meeting_demo.xml',
|
||||
'crm_opportunity_demo.xml',
|
||||
'crm_phonecall_demo.xml'
|
||||
],
|
||||
'test': ['test/test_crm_lead.yml',
|
||||
'test/test_crm_meeting.yml',
|
||||
'test/test_crm_opportunity.yml',
|
||||
'test/test_crm_phonecall.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
'certificate': '0079056041421',
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -152,6 +152,18 @@ class base_action_rule(osv.osv):
|
|||
""" Base Action Rule """
|
||||
_inherit = 'base.action.rule'
|
||||
_description = 'Action Rules'
|
||||
|
||||
_columns = {
|
||||
'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'),
|
||||
'trg_max_history': fields.integer('Maximum Communication History'),
|
||||
'trg_categ_id': fields.many2one('crm.case.categ', 'Category'),
|
||||
'regex_history' : fields.char('Regular Expression on Case History', size=128),
|
||||
'act_section_id': fields.many2one('crm.case.section', 'Set Team to'),
|
||||
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
|
||||
'act_mail_to_partner': fields.boolean('Mail to partner',help="Check this \
|
||||
if you want the rule to send an email to the partner."),
|
||||
}
|
||||
|
||||
|
||||
def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from',False), context={}):
|
||||
body = self.format_mail(obj, body)
|
||||
|
@ -207,7 +219,7 @@ class base_action_rule(osv.osv):
|
|||
res = super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context)
|
||||
write = {}
|
||||
|
||||
if hasattr(action, act_section_id) and action.act_section_id:
|
||||
if hasattr(action, 'act_section_id') and action.act_section_id:
|
||||
obj.section_id = action.act_section_id
|
||||
write['section_id'] = action.act_section_id.id
|
||||
|
||||
|
@ -231,13 +243,6 @@ class base_action_rule(osv.osv):
|
|||
return True
|
||||
|
||||
|
||||
base_action_rule()
|
||||
|
||||
class base_action_rule_line(osv.osv):
|
||||
""" Base Action Rule Line """
|
||||
_inherit = 'base.action.rule.line'
|
||||
_description = 'Base Action Rule Line'
|
||||
|
||||
def state_get(self, cr, uid, context={}):
|
||||
|
||||
"""@param self: The object pointer
|
||||
|
@ -245,7 +250,7 @@ class base_action_rule_line(osv.osv):
|
|||
@param uid: the current user’s ID for security checks,
|
||||
@param context: A standard dictionary for contextual values """
|
||||
|
||||
res = super(base_action_rule_line, self).state_get(cr, uid, context=context)
|
||||
res = super(base_action_rule, self).state_get(cr, uid, context=context)
|
||||
return res + [('escalate','Escalate')] + crm.AVAILABLE_STATES
|
||||
|
||||
def priority_get(self, cr, uid, context={}):
|
||||
|
@ -255,18 +260,10 @@ class base_action_rule_line(osv.osv):
|
|||
@param uid: the current user’s ID for security checks,
|
||||
@param context: A standard dictionary for contextual values """
|
||||
|
||||
res = super(base_action_rule_line, self).priority_get(cr, uid, context=context)
|
||||
res = super(base_action_rule, self).priority_get(cr, uid, context=context)
|
||||
return res + crm.AVAILABLE_PRIORITIES
|
||||
|
||||
_columns = {
|
||||
'trg_section_id': fields.many2one('crm.case.section', 'Sales Team'),
|
||||
'trg_max_history': fields.integer('Maximum Communication History'),
|
||||
'trg_categ_id': fields.many2one('crm.case.categ', 'Category'),
|
||||
'regex_history' : fields.char('Regular Expression on Case History', size=128),
|
||||
'act_section_id': fields.many2one('crm.case.section', 'Set Team to'),
|
||||
'act_categ_id': fields.many2one('crm.case.categ', 'Set Category to'),
|
||||
'act_mail_to_partner': fields.boolean('Mail to partner',help="Check this \
|
||||
if you want the rule to send an email to the partner."),
|
||||
}
|
||||
|
||||
base_action_rule_line()
|
||||
base_action_rule()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
<data>
|
||||
<!-- Case rules -->
|
||||
<record id="view_base_action_rule_line_form1" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.line.form.inherit</field>
|
||||
<field name="model">base.action.rule.line</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_line_form"/>
|
||||
<field name="name">base.action.rule.form.inherit</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<group name="partner" position="after">
|
||||
|
@ -19,9 +19,9 @@
|
|||
</record>
|
||||
|
||||
<record id="view_base_action_rule_line_form2" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.line.form2.inherit</field>
|
||||
<field name="model">base.action.rule.line</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_line_form"/>
|
||||
<field name="name">base.action.rule.form2.inherit</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<group name="partner" position="after">
|
||||
|
@ -35,9 +35,9 @@
|
|||
</record>
|
||||
|
||||
<record id="view_base_action_rule_line_form3" model="ir.ui.view">
|
||||
<field name="name">base.action.rule.line.form3.inherit</field>
|
||||
<field name="model">base.action.rule.line</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_line_form"/>
|
||||
<field name="name">base.action.rule.form3.inherit</field>
|
||||
<field name="model">base.action.rule</field>
|
||||
<field name="inherit_id" ref="base_action_rule.view_base_action_rule_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="act_user_id" position="after">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<field eval="time.strftime('%Y-%m-03 10:20:03')" name="date"/>
|
||||
<field name="categ_id" ref="crm.categ_meet2"/>
|
||||
<field eval=""Follow-up on proposal"" name="name"/>
|
||||
<field eval="time.strftime('%Y-%m-10')" name="date_deadline"/>
|
||||
<field eval="time.strftime('%Y-%m-03 16:38:03')" name="date_deadline"/>
|
||||
<field eval="6.3" name="duration"/>
|
||||
</record>
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
|||
<field eval="time.strftime('%Y-%m-05 12:01:01')" name="date"/>
|
||||
<field name="categ_id" ref="crm.categ_meet3"/>
|
||||
<field eval=""Initial discussion"" name="name"/>
|
||||
<field eval="time.strftime('%Y-%m-12')" name="date_deadline"/>
|
||||
<field eval="time.strftime('%Y-%m-05 19:01:01')" name="date_deadline"/>
|
||||
<field eval=""contact@tecsas.fr"" name="email_from"/>
|
||||
</record>
|
||||
|
||||
|
@ -48,9 +48,9 @@
|
|||
<field eval="time.strftime('%Y-%m-12 15:55:05')" name="date"/>
|
||||
<field name="categ_id" ref="crm.categ_meet1"/>
|
||||
<field eval=""Discuss pricing"" name="name"/>
|
||||
<field eval="time.strftime('%Y-%m-20')" name="date_deadline"/>
|
||||
<field eval="time.strftime('%Y-%m-12 18:55:05')" name="date_deadline"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="crm_case_reviewneeds0" model="crm.meeting">
|
||||
<field name="partner_address_id" ref="base.res_partner_address_15"/>
|
||||
<field eval="1" name="active"/>
|
||||
|
@ -62,9 +62,9 @@
|
|||
<field eval="time.strftime('%Y-%m-20 10:02:02')" name="date"/>
|
||||
<field name="categ_id" ref="crm.categ_meet3"/>
|
||||
<field eval=""Review needs"" name="name"/>
|
||||
<field eval="time.strftime('%Y-%m-25')" name="date_deadline"/>
|
||||
<field eval="time.strftime('%Y-%m-20 16:02:02')" name="date_deadline"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="crm_case_changesindesigning0" model="crm.meeting">
|
||||
<field name="partner_address_id" ref="base.res_partner_address_1"/>
|
||||
<field eval="1" name="active"/>
|
||||
|
@ -76,7 +76,7 @@
|
|||
<field eval="time.strftime('%Y-%m-22 11:05:05')" name="date"/>
|
||||
<field name="categ_id" ref="crm.categ_meet2"/>
|
||||
<field eval=""Changes in Designing"" name="name"/>
|
||||
<field eval="time.strftime('%Y-%m-28')" name="date_deadline"/>
|
||||
<field eval="time.strftime('%Y-%m-22 16:05:05')" name="date_deadline"/>
|
||||
<field eval=""info@opensides.be"" name="email_from"/>
|
||||
</record>
|
||||
|
||||
|
@ -91,7 +91,7 @@
|
|||
<field name="categ_id" ref="crm.categ_meet2"/>
|
||||
<field eval=""Update the data"" name="name"/>
|
||||
<field eval="13.3" name="duration"/>
|
||||
<field eval="time.strftime('%Y-%m-28')" name="date_deadline"/>
|
||||
<field eval="time.strftime('%Y-%m-19 02:30:49')" name="date_deadline"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -25,12 +25,15 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Meetings">
|
||||
<group col="6" colspan="4">
|
||||
<field name="name" select="1" string="Summary"
|
||||
colspan="4" />
|
||||
<field name="categ_id" widget="selection"
|
||||
string="Meeting Type"
|
||||
groups="base.group_extended"
|
||||
domain="[('object_id.model', '=', 'crm.meeting')]" />
|
||||
<group col="6" colspan="6">
|
||||
<field name="name" select="1" string="Summary"
|
||||
colspan="2" />
|
||||
<field name="categ_id" widget="selection"
|
||||
string="Meeting Type"
|
||||
groups="base.group_extended"
|
||||
domain="[('object_id.model', '=', 'crm.meeting')]" />
|
||||
<field name="allday" colspan="2" on_change="onchange_allday(allday)" />
|
||||
</group>
|
||||
<newline/>
|
||||
<field name="date" string="Start Date" required="1"
|
||||
on_change="onchange_dates(date,duration,False)" />
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -21,12 +21,9 @@
|
|||
|
||||
#import report_businessopp
|
||||
import crm_report
|
||||
import crm_claim_report
|
||||
import crm_lead_report
|
||||
import crm_phonecall_report
|
||||
import crm_fundraising_report
|
||||
import crm_opportunity_report
|
||||
import crm_helpdesk_report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
"access_crm_case_categ","crm.case.categ","model_crm_case_categ","crm.group_crm_user",1,0,0,0
|
||||
"access_crm_case_manger","crm.case manager","model_crm_case","crm.group_crm_user",1,1,1,1
|
||||
"access_crm_case","crm.case","model_crm_case","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_claim","crm.claim","model_crm_claim","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_fundraising","crm.fundraising","model_crm_fundraising","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_helpdesk","crm.helpdesk","model_crm_helpdesk","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_meeting","crm.meeting","model_crm_meeting","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_lead","crm.lead","model_crm_lead","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_opportunity","crm.opportunity","model_crm_opportunity","crm.group_crm_manager",1,1,1,1
|
||||
|
@ -26,8 +23,6 @@
|
|||
"access_crm_case_resource_type_manager","crm_case_resource_type manager","model_crm_case_resource_type","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_case_report_user","crm.case.report","model_crm_case_report","crm.group_crm_user",1,0,0,0
|
||||
"access_crm_case_report_manager","crm.case.report manager","model_crm_case_report","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_claim_report_user","crm.claim.report","model_crm_claim_report","crm.group_crm_user",1,0,0,0
|
||||
"access_crm_fundraising_report_user","crm.fundraising.report","model_crm_fundraising_report","crm.group_crm_user",1,0,0,0
|
||||
"access_crm_lead_report_user","crm.lead.report","model_crm_lead_report","crm.group_crm_user",1,0,0,0
|
||||
"access_crm_phonecall_report_user","crm.phonecall.report","model_crm_phonecall_report","crm.group_crm_user",1,0,0,0
|
||||
"access_crm_opportunity_report_user","crm.opportunity.report","model_crm_opportunity_report","crm.group_crm_user",1,0,0,0
|
||||
|
@ -38,7 +33,6 @@
|
|||
"access_crm_phonecall2phonecall","crm.phonecall2phonecall","model_crm_phonecall2phonecall","crm.group_crm_user",1,1,1,1
|
||||
"access_crm_phonecall2partner","crm.phonecall2partner","model_crm_phonecall2partner","crm.group_crm_user",1,1,1,1
|
||||
"access_crm_phonecall2opportunity","crm.phonecall2opportunity","model_crm_phonecall2opportunity","crm.group_crm_user",1,1,1,1
|
||||
"access_report_crm_helpdesk","report.crm.helpdesk","model_crm_helpdesk_report","crm.group_crm_user",1,1,1,1
|
||||
"access_crm_send_mail","crm.send.mail","model_crm_send_mail","crm.group_crm_user",1,1,1,1
|
||||
"access_crm_partner2opportunity","crm.partner2opportunity","model_crm_partner2opportunity","crm.group_crm_user",1,1,1,1
|
||||
"access_crm_lead2opportunity_partner","crm.lead2opportunity.partner","model_crm_lead2opportunity_partner","crm.group_crm_user",1,1,1,1
|
||||
|
|
|
|
@ -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,8 @@
|
|||
|
||||
from osv import fields, osv
|
||||
from crm import crm
|
||||
from caldav import caldav
|
||||
from base_calendar import base_calendar
|
||||
from caldav import calendar
|
||||
from datetime import datetime
|
||||
|
||||
class crm_meeting(osv.osv):
|
||||
_inherit = 'crm.meeting'
|
||||
|
@ -56,6 +56,51 @@ class crm_meeting(osv.osv):
|
|||
vals = event_obj.import_cal(cr, uid, data, context=context)
|
||||
return self.check_import(cr, uid, vals, context=context)
|
||||
|
||||
def check_import(self, cr, uid, vals, context={}):
|
||||
"""
|
||||
@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 vals: Get Values
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
ids = []
|
||||
model_obj = self.pool.get(context.get('model'))
|
||||
recur_pool = {}
|
||||
try:
|
||||
for val in vals:
|
||||
exists, r_id = calendar.uid2openobjectid(cr, val['id'], context.get('model'), \
|
||||
val.get('recurrent_id'))
|
||||
if val.has_key('create_date'): val.pop('create_date')
|
||||
u_id = val.get('id', None)
|
||||
val.pop('id')
|
||||
if exists and r_id:
|
||||
val.update({'recurrent_uid': exists})
|
||||
model_obj.write(cr, uid, [r_id], val)
|
||||
ids.append(r_id)
|
||||
elif exists:
|
||||
# Compute value of duration
|
||||
if 'date_deadline' in val and 'duration' not in val:
|
||||
start = datetime.strptime(val['date'], '%Y-%m-%d %H:%M:%S')
|
||||
end = datetime.strptime(val['date_deadline'], '%Y-%m-%d %H:%M:%S')
|
||||
diff = end - start
|
||||
val['duration'] = (diff.seconds/float(86400) + diff.days) * 24
|
||||
model_obj.write(cr, uid, [exists], val)
|
||||
ids.append(exists)
|
||||
else:
|
||||
if u_id in recur_pool and val.get('recurrent_id'):
|
||||
val.update({'recurrent_uid': recur_pool[u_id]})
|
||||
revent_id = model_obj.create(cr, uid, val)
|
||||
ids.append(revent_id)
|
||||
else:
|
||||
event_id = model_obj.create(cr, uid, val)
|
||||
recur_pool[u_id] = event_id
|
||||
ids.append(event_id)
|
||||
except Exception, e:
|
||||
raise osv.except_osv(('Error !'), (str(e)))
|
||||
return ids
|
||||
|
||||
crm_meeting()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# -*- 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 crm_claim
|
||||
import report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# -*- 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
{
|
||||
'name': 'Customer & Supplier Relationship Management',
|
||||
'version': '1.0',
|
||||
'category': 'Generic Modules/CRM & SRM',
|
||||
'description': """claim""",
|
||||
'author': 'Tiny',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['crm'],
|
||||
'init_xml': [
|
||||
'crm_claim_data.xml',
|
||||
],
|
||||
|
||||
'update_xml': [
|
||||
'crm_claim_view.xml',
|
||||
'crm_claim_menu.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'report/crm_claim_report_view.xml',
|
||||
],
|
||||
'demo_xml': [
|
||||
'crm_claim_demo.xml',
|
||||
'test/test_crm_claim.yml'
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -20,7 +20,7 @@
|
|||
##############################################################################
|
||||
|
||||
from osv import fields, osv
|
||||
import crm
|
||||
from crm import crm
|
||||
|
||||
class crm_claim(osv.osv):
|
||||
"""
|
||||
|
@ -70,4 +70,4 @@ class crm_claim(osv.osv):
|
|||
|
||||
crm_claim()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -8,19 +8,19 @@
|
|||
|
||||
<record model="crm.case.categ" id="categ_claim1">
|
||||
<field name="name">Factual Claims</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.categ" id="categ_claim2">
|
||||
<field name="name">Value Claims</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.categ" id="categ_claim3">
|
||||
<field name="name">Policy Claims</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
|
@ -30,13 +30,13 @@
|
|||
|
||||
<record model="crm.case.resource.type" id="type_claim1">
|
||||
<field name="name">Corrective</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.resource.type" id="type_claim2">
|
||||
<field name="name">Preventive</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
|
@ -46,33 +46,33 @@
|
|||
|
||||
<record model="crm.case.stage" id="stage_claim1">
|
||||
<field name="name">Accepted as Claim</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.stage" id="stage_claim2">
|
||||
<field name="name">Fixed</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.stage" id="stage_claim3">
|
||||
<field name="name">Won't fix</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.stage" id="stage_claim4">
|
||||
<field name="name">Invalid</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.stage" id="stage_claim5">
|
||||
<field name="name">Awaiting Response</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
|
@ -9,7 +9,7 @@
|
|||
<record id="crm_case_claim01" model="crm.claim">
|
||||
<field name="partner_address_id" ref="base.res_partner_address_15"/>
|
||||
<field eval="time.strftime('%Y-%m-04 10:45:36')" name="date"/>
|
||||
<field name="type_id" ref="crm.type_claim1"/>
|
||||
<field name="type_id" ref="crm_claim.type_claim1"/>
|
||||
<field name="partner_id" ref="base.res_partner_11"/>
|
||||
<field eval=""3"" name="priority"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
|
@ -18,15 +18,15 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval=""(726) 782-0636"" name="partner_mobile"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="categ_id" ref="crm.categ_claim1"/>
|
||||
<field name="stage_id" ref="crm.stage_claim1"/>
|
||||
<field name="categ_id" ref="crm_claim.categ_claim1"/>
|
||||
<field name="stage_id" ref="crm_claim.stage_claim1"/>
|
||||
<field eval=""(769) 703-274"" name="partner_phone"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_claim02" model="crm.claim">
|
||||
<field name="partner_address_id" ref="base.res_partner_address_6"/>
|
||||
<field eval="time.strftime('%Y-%m-11 11:19:25')" name="date"/>
|
||||
<field name="type_id" ref="crm.type_claim2"/>
|
||||
<field name="type_id" ref="crm_claim.type_claim2"/>
|
||||
<field name="partner_id" ref="base.res_partner_6"/>
|
||||
<field eval=""4"" name="priority"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
|
@ -35,15 +35,15 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval=""(392) 895-7917"" name="partner_mobile"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="categ_id" ref="crm.categ_claim2"/>
|
||||
<field name="stage_id" ref="crm.stage_claim5"/>
|
||||
<field name="categ_id" ref="crm_claim.categ_claim2"/>
|
||||
<field name="stage_id" ref="crm_claim.stage_claim5"/>
|
||||
<field eval=""(956) 293-2595"" name="partner_phone"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_claim03" model="crm.claim">
|
||||
<field name="partner_address_id" ref="base.res_partner_address_2"/>
|
||||
<field eval="time.strftime('%Y-%m-15 17:44:12')" name="date"/>
|
||||
<field name="type_id" ref="crm.type_claim1"/>
|
||||
<field name="type_id" ref="crm_claim.type_claim1"/>
|
||||
<field name="partner_id" ref="base.res_partner_10"/>
|
||||
<field eval=""2"" name="priority"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
|
@ -52,15 +52,15 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval=""(820) 167-3208"" name="partner_mobile"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="categ_id" ref="crm.categ_claim3"/>
|
||||
<field name="stage_id" ref="crm.stage_claim2"/>
|
||||
<field name="categ_id" ref="crm_claim.categ_claim3"/>
|
||||
<field name="stage_id" ref="crm_claim.stage_claim2"/>
|
||||
<field eval=""(079) 681-2139"" name="partner_phone"/>
|
||||
<field eval=""contact@tecsas.fr"" name="email_from"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_claim04" model="crm.claim">
|
||||
<field eval="time.strftime('%Y-%m-21 14:10:23')" name="date"/>
|
||||
<field name="type_id" ref="crm.type_claim2"/>
|
||||
<field name="type_id" ref="crm_claim.type_claim2"/>
|
||||
<field name="partner_id" ref="base.res_partner_tinyatwork"/>
|
||||
<field eval=""3"" name="priority"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
|
@ -69,15 +69,15 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval=""(077) 582-4035"" name="partner_mobile"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="categ_id" ref="crm.categ_claim1"/>
|
||||
<field name="stage_id" ref="crm.stage_claim5"/>
|
||||
<field name="categ_id" ref="crm_claim.categ_claim1"/>
|
||||
<field name="stage_id" ref="crm_claim.stage_claim5"/>
|
||||
<field eval=""(514) 698-4118"" name="partner_phone"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_claim05" model="crm.claim">
|
||||
<field name="partner_address_id" ref="base.res_partner_address_10"/>
|
||||
<field eval="time.strftime('%Y-%m-28 16:20:43')" name="date"/>
|
||||
<field name="type_id" ref="crm.type_claim1"/>
|
||||
<field name="type_id" ref="crm_claim.type_claim1"/>
|
||||
<field name="partner_id" ref="base.res_partner_5"/>
|
||||
<field eval=""3"" name="priority"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
|
@ -86,15 +86,15 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval=""(333) 715-1450"" name="partner_mobile"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="categ_id" ref="crm.categ_claim3"/>
|
||||
<field name="stage_id" ref="crm.stage_claim3"/>
|
||||
<field name="categ_id" ref="crm_claim.categ_claim3"/>
|
||||
<field name="stage_id" ref="crm_claim.stage_claim3"/>
|
||||
<field eval=""(855) 924-4364"" name="partner_phone"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_claim06" model="crm.claim">
|
||||
<field name="partner_address_id" ref="base.res_partner_address_1"/>
|
||||
<field eval="1" name="active"/>
|
||||
<field name="type_id" ref="crm.type_claim2"/>
|
||||
<field name="type_id" ref="crm_claim.type_claim2"/>
|
||||
<field name="partner_id" ref="base.res_partner_9"/>
|
||||
<field eval=""3"" name="priority"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
|
@ -103,15 +103,15 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval=""(468) 017-2684"" name="partner_mobile"/>
|
||||
<field eval="time.strftime('%Y-%m-28 14:15:30')" name="date"/>
|
||||
<field name="categ_id" ref="crm.categ_claim1"/>
|
||||
<field name="stage_id" ref="crm.stage_claim4"/>
|
||||
<field name="categ_id" ref="crm_claim.categ_claim1"/>
|
||||
<field name="stage_id" ref="crm_claim.stage_claim4"/>
|
||||
<field eval=""(373) 907-1009"" name="partner_phone"/>
|
||||
<field eval=""info@opensides.be"" name="email_from"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_case_claims07" model="crm.claim">
|
||||
<field eval="1" name="active"/>
|
||||
<field name="type_id" ref="crm.type_claim1"/>
|
||||
<field name="type_id" ref="crm_claim.type_claim1"/>
|
||||
<field name="partner_id" ref="base.res_partner_seagate"/>
|
||||
<field eval=""3"" name="priority"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
|
@ -120,8 +120,8 @@
|
|||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="" (463) 014-1208"" name="partner_mobile"/>
|
||||
<field eval="time.strftime('%Y-%m-19 13:01:05')" name="date"/>
|
||||
<field name="categ_id" ref="crm.categ_claim3"/>
|
||||
<field name="stage_id" ref="crm.stage_claim2"/>
|
||||
<field name="categ_id" ref="crm_claim.categ_claim3"/>
|
||||
<field name="stage_id" ref="crm_claim.stage_claim2"/>
|
||||
<field eval=""(282) 603-7489"" name="partner_phone"/>
|
||||
</record>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<field name="view_mode">tree,calendar,form,graph</field>
|
||||
<field name="view_id" ref="crm_case_claims_tree_view"/>
|
||||
<field name="context">{"search_default_section_id":section_id,"search_default_current":1,"search_default_my_claims":1}</field>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_claims_filter"/>
|
||||
<field name="search_view_id" ref="crm_claim.view_crm_case_claims_filter"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_claim0">
|
|
@ -0,0 +1,294 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Claims categories -->
|
||||
|
||||
<record id="crm_claim_categ_action" model="ir.actions.act_window">
|
||||
<field name="name">Claim Categories</field>
|
||||
<field name="res_model">crm.case.categ</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="crm.crm_case_categ_tree-view"/>
|
||||
<field name="domain">[('object_id.model', '=', 'crm.claim')]</field>
|
||||
<field name="context">{'object_id':'crm.claim'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="crm_claim_categ_action"
|
||||
id="menu_crm_case_claim-act" parent="crm.menu_crm_case_categ" />
|
||||
|
||||
<!-- Claim Stages -->
|
||||
|
||||
<record id="crm_claim_stage_act" model="ir.actions.act_window">
|
||||
<field name="name">Claim Stages</field>
|
||||
<field name="res_model">crm.case.stage</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="crm.crm_case_stage_tree"/>
|
||||
<field name="domain">[('object_id.model', '=', 'crm.claim')]</field>
|
||||
<field name="context">{'object_id':'crm.claim'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="crm_claim_stage_act"
|
||||
id="menu_crm_claim_stage_act" parent="crm.menu_crm_case_stage" />
|
||||
|
||||
<!-- Claim Resource Type -->
|
||||
|
||||
<record id="crm_claim_resource_act" model="ir.actions.act_window">
|
||||
<field name="name">Claim Resource Type</field>
|
||||
<field name="res_model">crm.case.resource.type</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="crm.crm_case_resource_type_tree"/>
|
||||
<field name="domain">[('object_id.model', '=', 'crm.claim')]</field>
|
||||
<field name="context">{'object_id':'crm.claim'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem action="crm_claim_resource_act"
|
||||
id="menu_crm_claim_stage_act"
|
||||
parent="crm.menu_crm_case_resource_type" />
|
||||
|
||||
<!-- Claims -->
|
||||
|
||||
<record model="ir.ui.view" id="crm_case_claims_tree_view">
|
||||
<field name="name">CRM - Claims Tree</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Claims" colors="blue:state=='pending';black:state=='open';grey:state in ('close', 'cancel')">
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="user_id" />
|
||||
<field name="section_id"/>
|
||||
<field name="date" string="Claim Date"/>
|
||||
<field name="date_deadline" string="Deadline"/>
|
||||
<field name="date_closed" string="Closure Date"/>
|
||||
<field name="categ_id" string="Type" select="1"/>
|
||||
<field name="state"/>
|
||||
<button name="case_open" string="Open"
|
||||
states="draft,pending" type="object"
|
||||
icon="gtk-go-forward" />
|
||||
<button name="case_pending" string="Pending"
|
||||
states="draft,open" type="object"
|
||||
icon="gtk-media-pause" />
|
||||
<button name="case_close" string="Close"
|
||||
states="open,draft,pending" type="object"
|
||||
icon="gtk-close" />
|
||||
<button name="case_cancel" string="Cancel"
|
||||
states="draft,open,pending" type="object"
|
||||
icon="gtk-cancel" />
|
||||
<button name="case_reset" string="Reset to Draft"
|
||||
states="done,cancel" type="object" icon="gtk-convert" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="crm_case_claims_form_view">
|
||||
<field name="name">CRM - Claims Form</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Claims">
|
||||
<group colspan="4" col="4">
|
||||
<field name="name"/>
|
||||
<field name="date" string="Date of Claim"/>
|
||||
<field name="date_deadline" string="Deadline"/>
|
||||
</group>
|
||||
<group colspan="4" col="4">
|
||||
|
||||
<notebook>
|
||||
<page string="Claim Info">
|
||||
<separator colspan="4" string="Communication"/>
|
||||
<group colspan="4" col="4">
|
||||
<field name="partner_id" string="Partner"
|
||||
on_change="onchange_partner_id(partner_id)" />
|
||||
<field name="partner_address_id" string="Contact"
|
||||
on_change="onchange_partner_address_id(partner_address_id, email_from)" />
|
||||
<field name="partner_phone"/>
|
||||
<field name="partner_mobile"/>
|
||||
<field name="email_from"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Status and Categorization"/>
|
||||
<group colspan="4" col="6">
|
||||
<field name="user_id" string="Responsible"/>
|
||||
<field name="section_id" widget="selection"/>
|
||||
<label string="Stage: " align="1.0"/>
|
||||
<group colspan="1" col="2">
|
||||
<field name="stage_id" select="1" nolabel="1"/>
|
||||
<button icon="gtk-go-forward" string="" name="stage_next" type="object"/>
|
||||
</group>
|
||||
<field name="categ_id" select="1" widget="selection"
|
||||
domain="[('object_id.model', '=', 'crm.claim')]" />
|
||||
<field name="type_id" string="Type of Action" select="1"
|
||||
domain="[('object_id.model', '=', 'crm.claim')]" />
|
||||
<field name="priority"/>
|
||||
</group>
|
||||
<separator colspan="4" string="References"/>
|
||||
<group colspan="4" col="4">
|
||||
<field name="ref"/>
|
||||
<field name="ref2"/>
|
||||
</group>
|
||||
<separator colspan="4" string="Claim/Action Description"/>
|
||||
<field name="description" colspan="4" nolabel="1"/>
|
||||
<separator colspan="4" string=""/>
|
||||
<group col="8" colspan="4">
|
||||
<field name="state" select="1"/>
|
||||
<button name="case_close" string="Done"
|
||||
states="open,draft,pending" type="object"
|
||||
icon="gtk-jump-to" />
|
||||
<button name="case_open" string="Open"
|
||||
states="draft,pending" type="object"
|
||||
icon="gtk-go-forward" />
|
||||
<button name="case_cancel" string="Cancel"
|
||||
states="draft,open,pending" type="object"
|
||||
icon="gtk-cancel" />
|
||||
<button name="case_pending" string="Pending"
|
||||
states="draft,open" type="object"
|
||||
icon="gtk-media-pause" />
|
||||
<button name="case_reset"
|
||||
string="Reset to Draft" states="done,cancel"
|
||||
type="object" icon="gtk-convert" />
|
||||
</group>
|
||||
</page>
|
||||
<page string="History" groups="base.group_extended">
|
||||
<field name="id" select="1"/>
|
||||
<field name="active"/>
|
||||
<field name="canal_id"/>
|
||||
<field name="som"/>
|
||||
<separator colspan="4" string="Dates"/>
|
||||
<field name="create_date"/>
|
||||
<field name="date_closed"/>
|
||||
<field name="log_ids" nolabel="1" colspan="4">
|
||||
<form string="Actions">
|
||||
<separator string="Action Information" colspan="4"/>
|
||||
<field name="name" colspan="4"/>
|
||||
<field name="date"/>
|
||||
<field name="user_id"/>
|
||||
</form>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Emails" groups="base.group_extended">
|
||||
<group colspan="4">
|
||||
<field colspan="4" name="email_cc" string="CC"/>
|
||||
</group>
|
||||
<field name="history_line" colspan="4" nolabel="1" mode="form,tree">
|
||||
<form string="Communication history">
|
||||
<group col="7" colspan="4">
|
||||
<field name="date"/>
|
||||
<field name="email_to"/>
|
||||
<field name="email_from"/>
|
||||
<button
|
||||
string="Add a CC"
|
||||
name="%(crm.action_view_crm_email_add_cc_wizard)d"
|
||||
icon="gtk-add" type="action"/>
|
||||
</group>
|
||||
<newline/>
|
||||
<field name="description" colspan="4" nolabel="1"/>
|
||||
<button colspan="4"
|
||||
string="Reply to Last Email"
|
||||
name="%(crm.action_crm_send_mail)d"
|
||||
context="{'mail':'reply', 'model': 'crm.claim'}"
|
||||
icon="gtk-undo" type="action" />
|
||||
</form>
|
||||
<tree string="Communication history">
|
||||
<field name="description"/>
|
||||
<field name="email_to"/>
|
||||
<field name="date"/>
|
||||
</tree>
|
||||
</field>
|
||||
<button colspan="4" string="Send New Email"
|
||||
name="%(crm.action_crm_send_mail)d"
|
||||
context="{'mail':'new', 'model': 'crm.claim'}"
|
||||
icon="gtk-go-forward" type="action" />
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Claim Calendar view -->
|
||||
|
||||
<record model="ir.ui.view" id="crm_case_claims_calendar_view">
|
||||
<field name="name">CRM - Claims Calendar</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="type">calendar</field>
|
||||
<field name="priority" eval="2"/>
|
||||
<field name="arch" type="xml">
|
||||
<calendar string="Claims" date_start="date" color="user_id">
|
||||
<field name="name"/>
|
||||
<field name="partner_name"/>
|
||||
<field name="categ_id"/>
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Claim Graph view -->
|
||||
|
||||
<record model="ir.ui.view" id="crm_case_graph_view_stage_cost">
|
||||
<field name="name">CRM -Graph</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Cases By Stage and Estimates" type="bar" orientation="vertical">
|
||||
<field name="stage_id"/>
|
||||
<field name="planned_cost" operator="+"/>
|
||||
<field name="planned_revenue" operator="+"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Crm claim Search view -->
|
||||
|
||||
<record id="view_crm_case_claims_filter" model="ir.ui.view">
|
||||
<field name="name">CRM - Claims Search</field>
|
||||
<field name="model">crm.claim</field>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Claims">
|
||||
<filter icon="gtk-new" string="Current"
|
||||
domain="[('state','in',('draft', 'open'))]"
|
||||
separator="1" help="Current Claims" default="1"
|
||||
/>
|
||||
<filter icon="gtk-execute"
|
||||
string="In Progress"
|
||||
domain="[('state','=','open')]"
|
||||
separator="1" help="In Progress Claims"
|
||||
/>
|
||||
<filter icon="gtk-media-pause"
|
||||
string="Pending"
|
||||
domain="[('state','=','pending')]"
|
||||
separator="1" help="All pending Claims"
|
||||
/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name" select='1'/>
|
||||
<field name="partner_id" select="1"/>
|
||||
<field name="user_id" select="1" widget="selection">
|
||||
<filter icon="terp-partner"
|
||||
domain="[('user_id','=',uid)]" help="My Claims"
|
||||
default="1" />
|
||||
<filter icon="terp-partner"
|
||||
domain="[('user_id','=', False)]"
|
||||
help="Unassigned Claims" />
|
||||
</field>
|
||||
<field name="section_id" select="1"
|
||||
widget="selection"
|
||||
default="context.get('section_id', False)">
|
||||
<filter icon="terp-crm"
|
||||
domain="[('section_id','=',context.get('section_id',False))]"
|
||||
help="My section" />
|
||||
</field>
|
||||
<newline/>
|
||||
<group expand="1" string="Group By..." colspan="10" col="20">
|
||||
<filter string="Deadline" icon="terp-crm"
|
||||
domain="[]"
|
||||
context="{'group_by':'date_deadline'}" />
|
||||
<filter string="Closure" icon="terp-crm"
|
||||
domain="[]"
|
||||
context="{'group_by':'date_closed'}" />
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,25 @@
|
|||
# -*- 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 crm_claim_report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
from osv import fields,osv
|
||||
import tools
|
||||
import crm_report
|
||||
from crm.report import crm_report
|
||||
|
||||
|
||||
class crm_claim_report(osv.osv):
|
|
@ -33,7 +33,7 @@
|
|||
<record id="view_report_crm_claim_form" model="ir.ui.view">
|
||||
<field name="name">crm.claim.report.form</field>
|
||||
<field name="model">crm.claim.report</field>
|
||||
<field name="inherit_id" ref="view_crm_case_form"/>
|
||||
<field name="inherit_id" ref="crm.view_crm_case_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="nbr" position="after">
|
||||
|
@ -66,7 +66,7 @@
|
|||
<record id="view_report_crm_claim_filter" model="ir.ui.view">
|
||||
<field name="name">crm.claim.report.select</field>
|
||||
<field name="model">crm.claim.report</field>
|
||||
<field name="inherit_id" ref="view_crm_case_filter"/>
|
||||
<field name="inherit_id" ref="crm.view_crm_case_filter"/>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
|
@ -0,0 +1,3 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_crm_claim","crm.claim","model_crm_claim","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_claim_report_user","crm.claim.report","model_crm_claim_report","crm.group_crm_user",1,0,0,0
|
|
|
@ -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_claim.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.
|
||||
-
|
||||
!assert {model: crm.claim, id: crm_claim.crm_claim_damagedproduct0}:
|
||||
stage_id: crm_claim.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,26 @@
|
|||
# -*- 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 crm_fundraising
|
||||
import report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# -*- 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
{
|
||||
'name': 'CRM Fundraising',
|
||||
'version': '1.0',
|
||||
'category': 'Generic Modules/CRM & SRM',
|
||||
'description': """Fundraising""",
|
||||
'author': 'Tiny',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['crm'],
|
||||
'init_xml': [
|
||||
'crm_fundraising_data.xml',
|
||||
],
|
||||
|
||||
'update_xml': [
|
||||
'crm_fundraising_view.xml',
|
||||
'crm_fundraising_menu.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'report/crm_fundraising_report_view.xml',
|
||||
],
|
||||
'demo_xml': [
|
||||
'crm_fundraising_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -20,7 +20,7 @@
|
|||
##############################################################################
|
||||
|
||||
from osv import fields, osv, orm
|
||||
import crm
|
||||
from crm import crm
|
||||
|
||||
class crm_fundraising(osv.osv):
|
||||
""" Fund Raising Cases """
|
|
@ -6,25 +6,25 @@
|
|||
|
||||
<record model="crm.case.categ" id="categ_fund1">
|
||||
<field name="name">Social Rehabilitation And Rural Upliftment</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.categ" id="categ_fund2">
|
||||
<field name="name">Learning And Education</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.categ" id="categ_fund3">
|
||||
<field name="name">Healthcare</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.categ" id="categ_fund4">
|
||||
<field name="name">Arts And Culture</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
|
@ -32,25 +32,25 @@
|
|||
|
||||
<record model="crm.case.resource.type" id="type_fund1">
|
||||
<field name="name">Cash</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.resource.type" id="type_fund2">
|
||||
<field name="name">Cheque</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.resource.type" id="type_fund3">
|
||||
<field name="name">Credit Card</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
||||
<record model="crm.case.resource.type" id="type_fund4">
|
||||
<field name="name">Demand Draft</field>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field name="object_id" search="[('model','=','crm.fundraising')]" model="ir.model"/>
|
||||
</record>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field eval=""open"" name="state"/>
|
||||
<field eval="250000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-01 10:35:50')" name="date"/>
|
||||
<field name="categ_id" ref="categ_fund1"/>
|
||||
<field eval=""Helping Street Children"" name="name"/>
|
||||
|
@ -30,7 +30,7 @@
|
|||
<field name="user_id" ref="base.user_root"/>
|
||||
<field eval=""draft"" name="state"/>
|
||||
<field eval="2000000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-05 12:35:50')" name="date"/>
|
||||
<field eval="8.0" name="duration"/>
|
||||
<field name="categ_id" ref="categ_fund1"/>
|
||||
|
@ -48,7 +48,7 @@
|
|||
<field name="user_id" ref="base.user_root"/>
|
||||
<field eval=""open"" name="state"/>
|
||||
<field eval="500000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-07 13:50:50')" name="date"/>
|
||||
<field name="categ_id" ref="categ_fund2"/>
|
||||
<field eval=""Donating books to school libraries"" name="name"/>
|
||||
|
@ -65,7 +65,7 @@
|
|||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field eval=""draft"" name="state"/>
|
||||
<field eval="1000000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-12 15:10:50')" name="date"/>
|
||||
<field name="categ_id" ref="categ_fund2"/>
|
||||
<field eval="4.3" name="duration"/>
|
||||
|
@ -82,7 +82,7 @@
|
|||
<field name="user_id" ref="base.user_root"/>
|
||||
<field eval=""open"" name="state"/>
|
||||
<field eval="5000000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-17 19:00:15')" name="date"/>
|
||||
<field eval="3" name="duration"/>
|
||||
<field name="categ_id" ref="categ_fund3"/>
|
||||
|
@ -99,7 +99,7 @@
|
|||
<field name="user_id" ref="base.user_root"/>
|
||||
<field eval=""done"" name="state"/>
|
||||
<field eval="10000000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-27 09:00:15')" name="date"/>
|
||||
<field eval="12" name="duration"/>
|
||||
<field name="categ_id" ref="categ_fund3"/>
|
||||
|
@ -118,7 +118,7 @@
|
|||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field eval=""draft"" name="state"/>
|
||||
<field eval="10000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-01 10:00:15')" name="date"/>
|
||||
<field name="categ_id" ref="categ_fund4"/>
|
||||
<field eval=""Encouraging arts"" name="name"/>
|
||||
|
@ -136,7 +136,7 @@
|
|||
<field name="user_id" ref="base.user_root"/>
|
||||
<field eval=""open"" name="state"/>
|
||||
<field eval="800000.0" name="planned_cost"/>
|
||||
<field name="section_id" ref="section_sales_department"/>
|
||||
<field name="section_id" ref="crm.section_sales_department"/>
|
||||
<field eval="time.strftime('%Y-%m-24 22:00:15')" name="date"/>
|
||||
<field name="categ_id" ref="categ_fund4"/>
|
||||
<field eval=""Promoting cultural programs and preserving dying art forms"" name="name"/>
|
|
@ -12,29 +12,29 @@
|
|||
<field name="name">Funds</field>
|
||||
<field name="res_model">crm.fundraising</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="view_id" ref="crm.crm_case_tree_view_fund"/>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_fund_filter"/>
|
||||
<field name="view_id" ref="crm_fundraising.crm_case_tree_view_fund"/>
|
||||
<field name="search_view_id" ref="crm_fundraising.view_crm_case_fund_filter"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_fund_all1">
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="crm.crm_case_tree_view_fund"/>
|
||||
<field name="act_window_id" ref="crm_case_category_act_fund_all1"/>
|
||||
<field name="view_id" ref="crm_fundraising.crm_case_tree_view_fund"/>
|
||||
<field name="act_window_id" ref="crm_fundraising.crm_case_category_act_fund_all1"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_crm_tag_form_view_fund_all1">
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="crm.crm_case_form_view_fund"/>
|
||||
<field name="act_window_id" ref="crm_case_category_act_fund_all1"/>
|
||||
<field name="view_id" ref="crm_fundraising.crm_case_form_view_fund"/>
|
||||
<field name="act_window_id" ref="crm_fundraising.crm_case_category_act_fund_all1"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_crm_tag_graph_view_fund_all1">
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="crm.crm_case_graph_view_fund"/>
|
||||
<field name="act_window_id" ref="crm_case_category_act_fund_all1"/>
|
||||
<field name="view_id" ref="crm_fundraising.crm_case_graph_view_fund"/>
|
||||
<field name="act_window_id" ref="crm_fundraising.crm_case_category_act_fund_all1"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="Fund Raising" id="menu_crm_case_fund_raise"
|
|
@ -197,7 +197,7 @@
|
|||
<field name="description" colspan="4" nolabel="1"/>
|
||||
<button colspan="4"
|
||||
string="Reply to Last Email"
|
||||
name="%(action_crm_send_mail)d"
|
||||
name="%(crm.action_crm_send_mail)d"
|
||||
context="{'mail':'reply', 'model': 'crm.fundraising'}"
|
||||
icon="gtk-undo" type="action" />
|
||||
</form>
|
||||
|
@ -208,7 +208,7 @@
|
|||
</tree>
|
||||
</field>
|
||||
<button colspan="4" string="Send New Email"
|
||||
name="%(action_crm_send_mail)d"
|
||||
name="%(crm.action_crm_send_mail)d"
|
||||
context="{'mail':'new', 'model': 'crm.fundraising'}"
|
||||
icon="gtk-go-forward" type="action" />
|
||||
</page>
|
|
@ -0,0 +1,25 @@
|
|||
# -*- 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 crm_fundraising_report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
<record id="view_report_crm_fundraising_form" model="ir.ui.view">
|
||||
<field name="name">crm.fundraising.report.form</field>
|
||||
<field name="model">crm.fundraising.report</field>
|
||||
<field name="inherit_id" ref="view_crm_case_form"/>
|
||||
<field name="inherit_id" ref="crm.view_crm_case_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="nbr" position="after">
|
||||
|
@ -65,7 +65,7 @@
|
|||
<record id="view_report_crm_fundraising_filter" model="ir.ui.view">
|
||||
<field name="name">crm.fundraising.report.select</field>
|
||||
<field name="model">crm.fundraising.report</field>
|
||||
<field name="inherit_id" ref="view_crm_case_filter"/>
|
||||
<field name="inherit_id" ref="crm.view_crm_case_filter"/>
|
||||
<field name="type">search</field>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
|
@ -0,0 +1,3 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_crm_fundraising","crm.fundraising","model_crm_fundraising","crm.group_crm_manager",1,1,1,1
|
||||
"access_crm_fundraising_report_user","crm.fundraising.report","model_crm_fundraising_report","crm.group_crm_user",1,0,0,0
|
|
|
@ -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,26 @@
|
|||
# -*- 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 crm_helpdesk
|
||||
import report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# -*- 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
{
|
||||
'name': 'CRM Helpdesk',
|
||||
'version': '1.0',
|
||||
'category': 'Generic Modules/CRM & SRM',
|
||||
'description': """Helpdesk Management""",
|
||||
'author': 'Tiny',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['crm'],
|
||||
'init_xml': [
|
||||
'crm_helpdesk_data.xml',
|
||||
],
|
||||
|
||||
'update_xml': [
|
||||
'crm_helpdesk_view.xml',
|
||||
'crm_helpdesk_menu.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'report/crm_helpdesk_report_view.xml',
|
||||
],
|
||||
'demo_xml': [
|
||||
'crm_helpdesk_demo.xml',
|
||||
'test/test_crm_helpdesk.yml'
|
||||
],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -22,7 +22,7 @@
|
|||
from osv import fields
|
||||
from osv import orm
|
||||
from osv import osv
|
||||
import crm
|
||||
from crm import crm
|
||||
|
||||
class crm_helpdesk(osv.osv):
|
||||
""" Helpdesk Cases """
|
|
@ -11,7 +11,7 @@
|
|||
<field name="res_model">crm.helpdesk</field>
|
||||
<field name="view_mode">tree,calendar,form</field>
|
||||
<field name="view_id" ref="crm_case_tree_view_helpdesk"/>
|
||||
<field name="search_view_id" ref="crm.view_crm_case_helpdesk_filter"/>
|
||||
<field name="search_view_id" ref="view_crm_case_helpdesk_filter"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_crm_sec_tree_view_act111">
|
|
@ -127,7 +127,7 @@
|
|||
<field name="description" colspan="4" nolabel="1"/>
|
||||
<button colspan="4"
|
||||
string="Reply to Last Email"
|
||||
name="%(action_crm_send_mail)d"
|
||||
name="%(crm.action_crm_send_mail)d"
|
||||
context="{'mail':'reply', 'model': 'crm.helpdesk'}"
|
||||
icon="gtk-undo" type="action" />
|
||||
</form>
|
||||
|
@ -138,7 +138,7 @@
|
|||
</tree>
|
||||
</field>
|
||||
<button colspan="4" string="Send New Email"
|
||||
name="%(action_crm_send_mail)d"
|
||||
name="%(crm.action_crm_send_mail)d"
|
||||
context="{'mail':'new', 'model': 'crm.helpdesk'}"
|
||||
icon="gtk-go-forward" type="action" />
|
||||
</page>
|
|
@ -0,0 +1,25 @@
|
|||
# -*- 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 crm_helpdesk_report
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
<record id="view_report_crm_helpdesk_form" model="ir.ui.view">
|
||||
<field name="name">crm.helpdesk.report.form</field>
|
||||
<field name="model">crm.helpdesk.report</field>
|
||||
<field name="inherit_id" ref="view_crm_case_form"/>
|
||||
<field name="inherit_id" ref="crm.view_crm_case_form"/>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="nbr" position="after">
|
|
@ -0,0 +1,3 @@
|
|||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_crm_helpdesk","crm.helpdesk","model_crm_helpdesk","crm.group_crm_manager",1,1,1,1
|
||||
"access_report_crm_helpdesk","report.crm.helpdesk","model_crm_helpdesk_report","crm.group_crm_user",1,1,1,1
|
|
|
@ -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')])
|
||||
-
|
|
@ -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 = [
|
||||
|
@ -202,52 +201,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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -97,7 +97,7 @@ class content_wrapper(StringIO.StringIO):
|
|||
cr = db.cursor()
|
||||
cr.commit()
|
||||
try:
|
||||
getattr(self.pool.get('document.directory.content'), 'process_write_'+self.node.content.extension[1:])(cr, self.uid, self.node, self.getvalue())
|
||||
getattr(self.pool.get('document.directory.content'), 'process_write')(cr, self.uid, self.node, self.getvalue())
|
||||
finally:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue