Doc: allow override of dynamic foleders' class
[odoo/odoo.git] / addons / document / nodes.py
index f1ac35c..46674b6 100644 (file)
 #
 ##############################################################################
 
-# import base64
-# import StringIO
-from osv import osv, fields
-from osv.orm import except_orm
 # import urlparse
 import pooler
 from tools.safe_eval import safe_eval
 
 import errno
-import os
+# import os
 import time
+import logging
 
 from StringIO import StringIO
 
@@ -44,6 +41,22 @@ from StringIO import StringIO
 #   root: if we are at the first directory of a ressource
 #
 
+logger = logging.getLogger('doc2.nodes')
+
+def _str2time(cre):
+    """ Convert a string with time representation (from db) into time (float)
+    
+        Note: a place to fix if datetime is used in db.
+    """
+    if not cre:
+        return time.time()
+    frac = 0.0
+    if isinstance(cre, basestring) and '.' in cre:
+        fdot = cre.find('.')
+        frac = float(cre[fdot:])
+        cre = cre[:fdot]
+    return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
+
 def get_node_context(cr, uid, context):
     return node_context(cr, uid, context)
 
@@ -51,13 +64,17 @@ class node_context(object):
     """ This is the root node, representing access to some particular
         context """
     cached_roots = {}
+    node_file_class = None
 
     def __init__(self, cr, uid, context=None):
         self.dbname = cr.dbname
         self.uid = uid
         self.context = context
         self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
+        self.node_file_class = node_file
+        self.extra_ctx = {} # Extra keys for context, that do _not_ trigger inequality
         assert self._dirobj
+        self._dirobj._prepare_context(cr, uid, self, context=context)
         self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
 
     def __eq__(self, other):
@@ -91,14 +108,10 @@ class node_context(object):
         """Create (or locate) a node for a directory
             @param dbro a browse object of document.directory
         """
-        fullpath = self._dirobj.get_full_path(cr, self.uid, dbro.id, self.context)
-        if dbro.type == 'directory':
-            return node_dir(fullpath, None ,self, dbro)
-        elif dbro.type == 'ressource':
-            assert dbro.ressource_parent_type_id == False
-            return node_res_dir(fullpath, None, self, dbro)
-        else:
-            raise ValueError("dir node for %s type", dbro.type)
+        
+        fullpath = dbro.get_full_path(context=self.context)
+        klass = dbro.get_node_class(dbro, context=self.context)
+        return klass(fullpath, None ,self, dbro)
 
     def get_file_node(self, cr, fbro):
         """ Create or locate a node for a static file
@@ -108,7 +121,7 @@ class node_context(object):
         if fbro.parent_id:
             parent = self.get_dir_node(cr, fbro.parent_id)
 
-        return node_file(fbro.name,parent,self,fbro)
+        return self.node_file_class(fbro.name, parent, self, fbro)
 
 
 class node_descriptor(object):
@@ -157,6 +170,8 @@ class node_class(object):
         Nodes have attributes which contain usual file properties
         """
     our_type = 'baseclass'
+    DAV_PROPS = None
+    DAV_M_NS = None
 
     def __init__(self, path, parent, context):
         assert isinstance(context,node_context)
@@ -253,24 +268,23 @@ class node_class(object):
             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):
+    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)
+        return str(_str2time(self.write_date))
 
     def _get_ttag(self,cr):
         """ Get a unique tag for this type/id of object.
             Must be overriden, so that each node is uniquely identified.
         """
         print "node_class.get_ttag()",self
-        raise RuntimeError("get_etag stub()")
+        raise NotImplementedError("get_etag stub()")
 
     def get_dav_props(self, cr):
         """ If this class has special behaviour for GroupDAV etc, export
         its capabilities """
-        return {}
+        # This fn is placed here rather than WebDAV, because we want the
+        # baseclass methods to apply to all node subclasses
+        return self.DAV_PROPS or {}
 
     def match_dav_eprop(self, cr, match, ns, prop):
         res = self.get_dav_eprop(cr, ns, prop)
@@ -279,8 +293,36 @@ class node_class(object):
         return False
 
     def get_dav_eprop(self, cr, ns, prop):
+        if not self.DAV_M_NS:
+            return None
+        
+        if self.DAV_M_NS.has_key(ns):
+            prefix = self.DAV_M_NS[ns]
+        else:
+            logger.debug('No namespace: %s ("%s")',ns, prop)
+            return None
+
+        mname = prefix + "_" + prop.replace('-','_')
+
+        if not hasattr(self, mname):
+            return None
+
+        try:
+            m = getattr(self, mname)
+            r = m(cr)
+            return r
+        except AttributeError:
+            logger.debug('Property %s not supported' % prop, exc_info=True)
         return None
 
+    def get_dav_resourcetype(self, cr):
+        """ Get the DAV resource type.
+        
+            Is here because some nodes may exhibit special behaviour, like
+            CalDAV/GroupDAV collections
+        """
+        raise NotImplementedError
+
     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
         """ Move this node to a new parent directory.
         @param ndir_node the collection that this node should be moved under
@@ -327,6 +369,7 @@ class node_class(object):
         raise NotImplementedError(repr(self))
 
     def get_domain(self, cr, filters):
+        # TODO Document
         return []
 
     def check_perms(self, perms):
@@ -397,22 +440,20 @@ class node_database(node_class):
         if not domain:
             domain = []
 
-        where2 = where + domain + [('type', '=', 'directory')]
+        where2 = where + domain + ['|', ('type', '=', 'directory'), \
+                    '&', ('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)]
         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))
+            klass = dirr.get_node_class(dirr, context=ctx)
+            res.append(klass(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))
+                klass = self.context.node_file_class
+                res.append(klass(fil.name, self, self.context, fil))
         return res
 
     def _file_get(self,cr, nodename=False):
@@ -461,7 +502,7 @@ class node_dir(node_database):
             for dfld in dirr.dctx_ids:
                 try:
                     self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
-                except Exception,e:
+                except Exception:
                     print "Cannot eval %s" % dfld.expr
                     print e
                     pass
@@ -573,19 +614,6 @@ class node_dir(node_database):
             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
 
@@ -613,7 +641,7 @@ class node_dir(node_database):
         if (not self.parent) and ndir_node:
             if not dbro.parent_id:
                 raise IOError(errno.EPERM, "Cannot move the root directory!")
-            self.parent = self.context.get_dir_node(cr, dbro.parent_id.id)
+            self.parent = self.context.get_dir_node(cr, dbro.parent_id)
             assert self.parent
 
         if self.parent != ndir_node:
@@ -639,12 +667,14 @@ class node_dir(node_database):
         return ret
 
 class node_res_dir(node_class):
-    """ A special sibling to node_dir, which does only contain dynamically
+    """ A folder containing dynamic folders
+        A special sibling to node_dir, which does only contain dynamically
         created folders foreach resource in the foreign model.
         All folders should be of type node_res_obj and merely behave like
         node_dirs (with limited domain).
     """
     our_type = 'collection'
+    res_obj_class = None
     def __init__(self, path, parent, context, dirr, dctx=None ):
         super(node_res_dir,self).__init__(path, parent, context)
         self.dir_id = dirr.id
@@ -751,14 +781,15 @@ class node_res_dir(node_class):
                 continue
                 # Yes! we can't do better but skip nameless records.
 
-            res.append(node_res_obj(name, self.dir_id, self, self.context, self.res_model, bo))
+            res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
         return res
 
     def _get_ttag(self,cr):
         return 'rdir-%d' % self.dir_id
 
 class node_res_obj(node_class):
-    """ A special sibling to node_dir, which does only contain dynamically
+    """ A dynamically created folder.
+        A special sibling to node_dir, which does only contain dynamically
         created folders foreach resource in the foreign model.
         All folders should be of type node_res_obj and merely behave like
         node_dirs (with limited domain).
@@ -794,7 +825,7 @@ class node_res_obj(node_class):
             for fld,expr in self.dctx_dict.items():
                 try:
                     self.dctx[fld] = safe_eval(expr, dc2)
-                except Exception,e:
+                except Exception:
                     print "Cannot eval %s for %s" % (expr, fld)
                     print e
                     pass
@@ -853,6 +884,7 @@ class node_res_obj(node_class):
         return res
 
     def get_dav_props(self, cr):
+        # Deprecated! (but document_ics must be cleaned, first)
         res = {}
         cntobj = self.context._dirobj.pool.get('document.directory.content')
         uid = self.context.uid
@@ -866,10 +898,10 @@ class node_res_obj(node_class):
         return res
 
     def get_dav_eprop(self, cr, ns, prop):
+        # Deprecated!
         if ns != 'http://groupdav.org/' or prop != 'resourcetype':
-            print "Who asked for %s:%s?" % (ns, prop)
+            logger.warning("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()
@@ -877,6 +909,7 @@ class node_res_obj(node_class):
         where = [('directory_id','=',self.dir_id) ]
         ids = cntobj.search(cr,uid,where,context=ctx)
         for content in cntobj.browse(cr, uid, ids, context=ctx):
+            # TODO: remove relic of GroupDAV
             if content.extension == '.ics': # FIXME: call the content class!
                 return ('vevent-collection','http://groupdav.org/')
         return None
@@ -911,20 +944,21 @@ class node_res_obj(node_class):
                 res_name = getattr(bo, namefield)
                 if not res_name:
                     continue
-                res.append(node_res_obj(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
+                # TODO Revise
+                klass = directory.get_node_class(directory, dynamic=True, context=ctx)
+                res.append(klass(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
 
 
         where2 = where + [('parent_id','=',self.dir_id) ]
         ids = dirobj.search(cr, uid, where2, context=ctx)
         for dirr in dirobj.browse(cr, uid, ids, context=ctx):
             if dirr.type == 'directory':
-                res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
+                klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
+                res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
             elif dirr.type == 'ressource':
                 # child resources can be controlled by properly set dctx
-                res.append(node_res_dir(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
-
-
-
+                klass = dirr.get_node_class(dirr, context=ctx)
+                res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
 
         fil_obj = dirobj.pool.get('ir.attachment')
         if self.res_find_all:
@@ -934,7 +968,8 @@ class node_res_obj(node_class):
         ids = fil_obj.search(cr, uid, where3, 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))
+                klass = self.context.node_file_class
+                res.append(klass(fil.name, self, self.context, fil))
 
 
         # Get Child Ressource Directories
@@ -946,9 +981,11 @@ class node_res_obj(node_class):
             dirids = dirids + dirobj.search(cr,uid, where5)
             for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
                 if dirr.type == 'directory' and not dirr.parent_id:
-                    res.append(node_res_obj(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
+                    klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
+                    res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
                 if dirr.type == 'ressource':
-                    res.append(node_res_dir(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
+                    klass = dirr.get_node_class(dirr, context=ctx)
+                    res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
         return res
 
     def create_child_collection(self, cr, objname):
@@ -1005,7 +1042,8 @@ class node_res_obj(node_class):
 
         fil_id = fil_obj.create(cr, uid, val, context=ctx)
         fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
-        fnode = node_file(path, self, self.context, fil)
+        klass = self.context.node_file_class
+        fnode = klass(path, self, self.context, fil)
         if data is not None:
             fnode.set_data(cr, data, fil)
         return fnode
@@ -1013,6 +1051,8 @@ class node_res_obj(node_class):
     def _get_ttag(self,cr):
         return 'rodir-%d-%d' % (self.dir_id, self.res_id)
 
+node_res_dir.res_obj_class = node_res_obj
+
 class node_file(node_class):
     our_type = 'file'
     def __init__(self, path, parent, context, fil):
@@ -1148,7 +1188,7 @@ class node_file(node_class):
         return 'file-%d' % self.file_id
 
     def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
-        if ndir_node.context != self.context:
+        if ndir_node and ndir_node.context != self.context:
             raise NotImplementedError("Cannot move files between contexts")
 
         if (not self.check_perms(8)) and ndir_node.check_perms(2):
@@ -1166,11 +1206,11 @@ class node_file(node_class):
 
         if (not self.parent):
             # there *must* be a parent node for this one
-            self.parent = self.context.get_dir_node(cr, dbro.parent_id.id)
+            self.parent = self.context.get_dir_node(cr, dbro.parent_id)
             assert self.parent
         
         ret = {}
-        if self.parent != ndir_node:
+        if ndir_node and self.parent != ndir_node:
             if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
                 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
                 raise NotImplementedError('Cannot move files between dynamic folders')
@@ -1292,6 +1332,8 @@ 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 '')
 
+    def get_dav_resourcetype(self, cr):
+        return ''
 
 class nodefd_content(StringIO, node_descriptor):
     """ A descriptor to content nodes
@@ -1331,13 +1373,15 @@ class nodefd_content(StringIO, node_descriptor):
             if self.mode in ('w', 'w+', 'r+'):
                 data = self.getvalue()
                 cntobj = par.context._dirobj.pool.get('document.directory.content')
-                cntobj.process_write(cr, uid, parent, data, ctx)
+                cntobj.process_write(cr, uid, par, data, par.context.context)
             elif self.mode == 'a':
                 raise NotImplementedError
             cr.commit()
-        except Exception, e:
+        except Exception:
             logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
             raise
         finally:
             cr.close()
         StringIO.close(self)
+
+#eof
\ No newline at end of file