1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
23 from document_webdav import nodes
24 from document.nodes import _str2time
27 from orm_utils import get_last_modified
30 from tools.dict_tools import dict_merge, dict_merge2
32 from document.dict_tools import dict_merge, dict_merge2
34 # TODO: implement DAV-aware errors, inherit from IOError
36 # Assuming that we have set global properties right, we mark *all*
37 # directories as having calendar-access.
38 nodes.node_dir.http_options = dict_merge2(nodes.node_dir.http_options,
39 { 'DAV': ['calendar-access',] })
41 class node_calendar_collection(nodes.node_dir):
42 DAV_PROPS = dict_merge2(nodes.node_dir.DAV_PROPS,
43 { "http://calendarserver.org/ns/" : ('getctag',), } )
44 DAV_M_NS = dict_merge2(nodes.node_dir.DAV_M_NS,
45 { "http://calendarserver.org/ns/" : '_get_dav', } )
47 def _file_get(self,cr, nodename=False):
50 def _child_get(self, cr, name=False, parent_id=False, domain=None):
51 dirobj = self.context._dirobj
52 uid = self.context.uid
53 ctx = self.context.context.copy()
55 where = [('collection_id','=',self.dir_id)]
57 if name and name.endswith('.ics'):
61 where.append(('name','=',name))
64 where = where + domain
65 fil_obj = dirobj.pool.get('basic.calendar')
66 ids = fil_obj.search(cr,uid,where,context=ctx)
68 for cal in fil_obj.browse(cr, uid, ids, context=ctx):
69 if (not name) or not ext:
70 res.append(node_calendar(cal.name, self, self.context, cal))
71 if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
72 # these ones must not see the webcal entry.
74 if cal.has_webcal and (not name) or ext:
75 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
76 # May be both of them!
79 def _get_ttag(self, cr):
80 return 'calen-dir-%d' % self.dir_id
82 def _get_dav_getctag(self, cr):
83 dirobj = self.context._dirobj
84 uid = self.context.uid
85 ctx = self.context.context.copy()
87 where = [('collection_id','=',self.dir_id)]
88 bc_obj = dirobj.pool.get('basic.calendar')
90 res = get_last_modified(bc_obj, cr, uid, where, context=ctx)
93 class node_calendar_res_col(nodes.node_res_obj):
94 """ Calendar collection, as a dynamically created node
96 This class shall be used instead of node_calendar_collection, when the
97 node is under dynamic ones.
99 DAV_PROPS = dict_merge2(nodes.node_res_obj.DAV_PROPS,
100 { "http://calendarserver.org/ns/" : ('getctag',), } )
101 DAV_M_NS = dict_merge2(nodes.node_res_obj.DAV_M_NS,
102 { "http://calendarserver.org/ns/" : '_get_dav', } )
104 def _file_get(self,cr, nodename=False):
107 def _child_get(self, cr, name=False, parent_id=False, domain=None):
108 dirobj = self.context._dirobj
109 uid = self.context.uid
110 ctx = self.context.context.copy()
111 ctx.update(self.dctx)
112 where = [('collection_id','=',self.dir_id)]
114 if name and name.endswith('.ics'):
118 where.append(('name','=',name))
121 where = where + domain
122 fil_obj = dirobj.pool.get('basic.calendar')
123 ids = fil_obj.search(cr,uid,where,context=ctx)
125 # TODO: shall we use any of our dynamic information??
126 for cal in fil_obj.browse(cr, uid, ids, context=ctx):
127 if (not name) or not ext:
128 res.append(node_calendar(cal.name, self, self.context, cal))
129 if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
130 # these ones must not see the webcal entry.
132 if cal.has_webcal and (not name) or ext:
133 res.append(res_node_calendar(cal.name+'.ics', self, self.context, cal))
134 # May be both of them!
137 def _get_ttag(self, cr):
138 return 'calen-dir-%d' % self.dir_id
140 def _get_dav_getctag(self, cr):
141 dirobj = self.context._dirobj
142 uid = self.context.uid
143 ctx = self.context.context.copy()
144 ctx.update(self.dctx)
145 where = [('collection_id','=',self.dir_id)]
146 bc_obj = dirobj.pool.get('basic.calendar')
148 res = get_last_modified(bc_obj, cr, uid, where, context=ctx)
149 return _str2time(res)
151 class node_calendar(nodes.node_class):
152 our_type = 'collection'
154 "DAV:": ('supported-report-set',),
155 # "http://cal.me.com/_namespace/" : ('user-state',),
156 "http://calendarserver.org/ns/" : ( 'getctag',),
157 'http://groupdav.org/': ('resourcetype',),
158 "urn:ietf:params:xml:ns:caldav" : (
159 'calendar-description',
160 'supported-calendar-component-set',
162 "http://apple.com/ns/ical/": ("calendar-color", "calendar-order"),
165 "urn:ietf:params:xml:ns:caldav" : (
168 'supported-calendar-data',
176 # "http://cal.me.com/_namespace/": '_get_dav',
177 'http://groupdav.org/': '_get_gdav',
178 "http://calendarserver.org/ns/" : '_get_dav',
179 "urn:ietf:params:xml:ns:caldav" : '_get_caldav',
180 "http://apple.com/ns/ical/": '_get_apple_cal',
183 http_options = { 'DAV': ['calendar-access'] }
185 def __init__(self,path, parent, context, calendar):
186 super(node_calendar,self).__init__(path, parent,context)
187 self.calendar_id = calendar.id
188 self.mimetype = 'application/x-directory'
189 self.create_date = calendar.create_date
190 self.write_date = calendar.write_date or calendar.create_date
191 self.content_length = 0
192 self.displayname = calendar.name
193 self.cal_type = calendar.type
194 self.cal_color = calendar.calendar_color or None
195 self.cal_order = calendar.calendar_order or None
197 self.uuser = (calendar.user_id and calendar.user_id.login) or 'nobody'
199 self.uuser = 'nobody'
201 def _get_dav_getctag(self, cr):
202 dirobj = self.context._dirobj
203 uid = self.context.uid
204 ctx = self.context.context.copy()
205 ctx.update(self.dctx)
207 bc_obj = dirobj.pool.get('basic.calendar')
208 res = bc_obj.get_cal_max_modified(cr, uid, [self.calendar_id], self, domain=[], context=ctx)
209 return _str2time(res)
211 def _get_dav_user_state(self, cr):
215 def get_dav_resourcetype(self, cr):
216 res = [ ('collection', 'DAV:'),
217 ('calendar', 'urn:ietf:params:xml:ns:caldav'),
219 if self.context.get('DAV-client', '') == 'GroupDAV':
220 res.append((str(self.cal_type + '-collection'), 'http://groupdav.org/'))
223 def get_domain(self, cr, filters):
228 _log = logging.getLogger('caldav.query')
229 if filters.localName == 'calendar-query':
231 for filter_child in filters.childNodes:
232 if filter_child.nodeType == filter_child.TEXT_NODE:
234 if filter_child.localName == 'filter':
235 for vcalendar_filter in filter_child.childNodes:
236 if vcalendar_filter.nodeType == vcalendar_filter.TEXT_NODE:
238 if vcalendar_filter.localName == 'comp-filter':
239 if vcalendar_filter.getAttribute('name') == 'VCALENDAR':
240 for vevent_filter in vcalendar_filter.childNodes:
241 if vevent_filter.nodeType == vevent_filter.TEXT_NODE:
243 if vevent_filter.localName == 'comp-filter':
244 if vevent_filter.getAttribute('name'):
245 res = [('type','=',vevent_filter.getAttribute('name').lower() )]
247 for cfe in vevent_filter.childNodes:
248 if cfe.localName == 'time-range':
249 if cfe.getAttribute('start'):
250 _log.warning("Ignore start.. ")
251 # No, it won't work in this API
252 #val = cfe.getAttribute('start')
253 #res += [('dtstart','=', cfe)]
254 elif cfe.getAttribute('end'):
255 _log.warning("Ignore end.. ")
257 _log.debug("Unknown comp-filter: %s", cfe.localName)
259 _log.debug("Unknown comp-filter: %s", vevent_filter.localName)
261 _log.debug("Unknown filter element: %s", vcalendar_filter.localName)
263 _log.debug("Unknown calendar-query element: %s", filter_child.localName)
265 elif filters.localName == 'calendar-multiget':
266 # this is not the place to process, as it wouldn't support multi-level
267 # hrefs. So, the code is moved to document_webdav/dav_fs.py
270 _log.debug("Unknown element in REPORT: %s", filters.localName)
273 def children(self, cr, domain=None):
274 return self._child_get(cr, domain=domain)
276 def child(self,cr, name, domain=None):
277 res = self._child_get(cr, name, domain=domain)
283 def _child_get(self, cr, name=False, parent_id=False, domain=None):
284 dirobj = self.context._dirobj
285 uid = self.context.uid
286 ctx = self.context.context.copy()
287 ctx.update(self.dctx)
289 bc_obj = dirobj.pool.get('basic.calendar')
292 if name.endswith('.ics'):
296 where.append(('id','=',int(name)))
298 bca_obj = dirobj.pool.get('basic.calendar.alias')
299 bc_alias = bca_obj.search(cr, uid,
300 [('cal_line_id.calendar_id', '=', self.calendar_id),
301 ('name', '=', name)] )
304 bc_val = bca_obj.read(cr, uid, bc_alias, ['res_id',])
305 where.append(('id', '=', bc_val[0]['res_id']))
307 # if somebody requests any other name than the ones we
308 # generate (non-numeric), it just won't exist
314 # we /could/ be supplying an invalid calendar id to bc_obj, it has to check
315 res = bc_obj.get_calendar_objects(cr, uid, [self.calendar_id], self, domain=where, context=ctx)
318 def create_child(self, cr, path, data):
319 """ API function to create a child file object and node
320 Return the node_* created
322 # we ignore the path, it will be re-generated automatically
323 fil_obj = self.context._dirobj.pool.get('basic.calendar')
324 ctx = self.context.context.copy()
325 ctx.update(self.dctx)
326 uid = self.context.uid
328 res = self.set_data(cr, data)
331 # We arbitrarily construct only the first node of the data
332 # that have been imported. ICS may have had more elements,
333 # but only one node can be returned here.
334 assert isinstance(res[0], (int, long))
335 fnodes = fil_obj.get_calendar_objects(cr, uid, [self.calendar_id], self,
336 domain=[('id','=',res[0])], context=ctx)
337 if self.context.get('DAV-client','') in ('iPhone', 'iCalendar',):
338 # For those buggy clients, register the alias
339 bca_obj = fil_obj.pool.get('basic.calendar.alias')
340 ourcal = fil_obj.browse(cr, uid, self.calendar_id)
342 for line in ourcal.line_ids:
343 if line.name == ourcal.type:
346 assert line_id, "Calendar #%d must have at least one %s line" % \
347 (ourcal.id, ourcal.type)
348 if path.endswith('.ics'):
350 bca_obj.create(cr, uid, { 'cal_line_id': line_id,
351 'res_id': res[0], 'name': path}, context=ctx)
353 # If we reach this line, it means that we couldn't import any useful
354 # (and matching type vs. our node kind) data from the iCal content.
358 def set_data(self, cr, data, fil_obj = None):
359 uid = self.context.uid
360 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
361 res = calendar_obj.import_cal(cr, uid, data, self.calendar_id)
364 def get_data_len(self, cr, fil_obj = None):
365 return self.content_length
367 def _get_ttag(self,cr):
368 return 'calendar-%d' % (self.calendar_id,)
373 def _get_caldav_calendar_data(self, cr):
374 if self.context.get('DAV-client', '') in ('iPhone', 'iCalendar'):
375 # Never return collective data to iClients, they get confused
376 # because they do propfind on the calendar node with Depth=1
377 # and only expect the childrens' data
380 for child in self.children(cr):
381 res.append(child._get_caldav_calendar_data(cr))
384 def _get_caldav_calendar_description(self, cr):
385 uid = self.context.uid
386 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
387 ctx = self.context.context.copy()
388 ctx.update(self.dctx)
390 calendar = calendar_obj.browse(cr, uid, self.calendar_id, context=ctx)
391 return calendar.description or calendar.name
395 def _get_dav_supported_report_set(self, cr):
397 return ('supported-report', 'DAV:',
399 ('principal-match','DAV:')
403 def _get_caldav_supported_calendar_component_set(self, cr):
404 return ('comp', 'urn:ietf:params:xml:ns:caldav', None,
405 {'name': self.cal_type.upper()} )
407 def _get_caldav_calendar_timezone(self, cr):
410 def _get_caldav_supported_calendar_data(self, cr):
411 return ('calendar-data', 'urn:ietf:params:xml:ns:caldav', None,
412 {'content-type': "text/calendar", 'version': "2.0" } )
414 def _get_caldav_max_resource_size(self, cr):
417 def _get_caldav_min_date_time(self, cr):
418 return "19700101T000000Z"
420 def _get_caldav_max_date_time(self, cr):
421 return "21001231T235959Z" # I will be dead by then
423 def _get_apple_cal_calendar_color(self, cr):
424 return self.cal_color
426 def _get_apple_cal_calendar_order(self, cr):
427 return self.cal_order
429 class res_node_calendar(nodes.node_class):
432 "http://calendarserver.org/ns/" : ('getctag',),
433 "urn:ietf:params:xml:ns:caldav" : (
434 'calendar-description',
438 "http://calendarserver.org/ns/" : '_get_dav',
439 "urn:ietf:params:xml:ns:caldav" : '_get_caldav'}
441 http_options = { 'DAV': ['calendar-access'] }
443 def __init__(self,path, parent, context, res_obj, res_model=None, res_id=None):
444 super(res_node_calendar,self).__init__(path, parent, context)
445 self.mimetype = 'text/calendar'
446 self.create_date = parent.create_date
447 self.write_date = parent.write_date or parent.create_date
448 self.calendar_id = hasattr(parent, 'calendar_id') and parent.calendar_id or False
450 if not self.calendar_id: self.calendar_id = res_obj.id
451 pr = res_obj.perm_read(context=context, details=False)[0]
452 self.create_date = pr.get('create_date')
453 self.write_date = pr.get('write_date') or pr.get('create_date')
454 self.displayname = res_obj.name
456 self.content_length = 0
458 self.model = res_model
461 def open(self, cr, mode=False):
462 if self.type in ('collection','database'):
464 s = StringIO.StringIO(self.get_data(cr))
468 def get_data(self, cr, fil_obj = None):
469 uid = self.context.uid
470 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
471 context = self.context.context.copy()
472 context.update(self.dctx)
473 context.update({'model': self.model, 'res_id':self.res_id})
474 res = calendar_obj.export_cal(cr, uid, [self.calendar_id], context=context)
477 def _get_caldav_calendar_data(self, cr):
478 return self.get_data(cr)
480 def get_data_len(self, cr, fil_obj = None):
481 return self.content_length
483 def set_data(self, cr, data, fil_obj = None):
484 uid = self.context.uid
485 context = self.context.context.copy()
486 context.update(self.dctx)
487 context.update({'model': self.model, 'res_id':self.res_id})
488 calendar_obj = self.context._dirobj.pool.get('basic.calendar')
489 res = calendar_obj.import_cal(cr, uid, data, self.calendar_id, context=context)
492 def _get_ttag(self,cr):
494 if self.model and self.res_id:
495 res = '%s_%d' % (self.model, self.res_id)
496 elif self.calendar_id:
497 res = '%d' % (self.calendar_id)
501 uid = self.context.uid
503 if self.type in ('collection','database'):
505 if self.model and self.res_id:
506 document_obj = self.context._dirobj.pool.get(self.model)
508 res = document_obj.unlink(cr, uid, [self.res_id])
514 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4