1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 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 ##############################################################################
24 from tools.safe_eval import safe_eval
26 from tools.misc import ustr
32 from StringIO import StringIO
35 # An object that represent an uri
36 # path: the uri of the object
37 # content: the Content it belongs to (_print.pdf)
38 # type: content or collection
39 # content: objct = res.partner
40 # collection: object = directory, object2 = res.partner
41 # file: objct = ir.attachement
42 # root: if we are at the first directory of a ressource
45 logger = logging.getLogger('doc2.nodes')
48 """ Convert a string with time representation (from db) into time (float)
50 Note: a place to fix if datetime is used in db.
55 if isinstance(cre, basestring) and '.' in cre:
57 frac = float(cre[fdot:])
59 return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
61 def get_node_context(cr, uid, context):
62 return node_context(cr, uid, context)
64 class node_context(object):
65 """ This is the root node, representing access to some particular context
67 A context is a set of persistent data, which may influence the structure
68 of the nodes. All other transient information during a data query should
69 be passed down with function arguments.
72 node_file_class = None
74 def __init__(self, cr, uid, context=None):
75 self.dbname = cr.dbname
77 self.context = context
81 self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
82 self.node_file_class = node_file
83 self.extra_ctx = {} # Extra keys for context, that do _not_ trigger inequality
85 self._dirobj._prepare_context(cr, uid, self, context=context)
86 self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
88 def __eq__(self, other):
89 if not type(other) == node_context:
91 if self.dbname != other.dbname:
93 if self.uid != other.uid:
95 if self.context != other.context:
97 if self.rootdir != other.rootdir:
101 def __ne__(self, other):
102 return not self.__eq__(other)
104 def get(self, name, default=None):
105 return self.context.get(name, default)
107 def get_uri(self, cr, uri):
108 """ Although this fn passes back to doc.dir, it is needed since
109 it is a potential caching point.
111 (ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
113 ndir = ndir.child(cr, duri[0])
119 def get_dir_node(self, cr, dbro):
120 """Create (or locate) a node for a directory
121 @param dbro a browse object of document.directory
124 fullpath = dbro.get_full_path(context=self.context)
125 klass = dbro.get_node_class(dbro, context=self.context)
126 return klass(fullpath, None ,self, dbro)
128 def get_file_node(self, cr, fbro):
129 """ Create or locate a node for a static file
130 @param fbro a browse object of an ir.attachment
134 parent = self.get_dir_node(cr, fbro.parent_id)
136 return self.node_file_class(fbro.name, parent, self, fbro)
139 class node_descriptor(object):
140 """A file-like interface to the data contents of a node.
142 This class is NOT a node, but an /open descriptor/ for some
143 node. It can hold references to a cursor or a file object,
144 because the life of a node_descriptor will be the open period
146 It should also take care of locking, with any native mechanism
148 For the implementation, it would be OK just to wrap around file,
149 StringIO or similar class. The node_descriptor is only needed to
150 provide the link to the parent /node/ object.
153 def __init__(self, parent):
154 assert isinstance(parent, node_class)
155 self.name = parent.displayname
156 self.__parent = parent
158 def _get_parent(self):
161 def open(self, **kwargs):
162 raise NotImplementedError
165 raise NotImplementedError
167 def read(self, size=None):
168 raise NotImplementedError
170 def seek(self, offset, whence=None):
171 raise NotImplementedError
174 raise NotImplementedError
176 def write(self, str):
177 raise NotImplementedError
180 raise NotImplementedError
185 def __nonzero__(self):
186 """ Ensure that a node_descriptor will never equal False
188 Since we do define __len__ and __iter__ for us, we must avoid
189 being regarded as non-true objects.
194 raise NotImplementedError
196 class node_class(object):
197 """ this is a superclass for our inodes
198 It is an API for all code that wants to access the document files.
199 Nodes have attributes which contain usual file properties
201 our_type = 'baseclass'
205 def __init__(self, path, parent, context):
206 assert isinstance(context,node_context)
207 assert (not parent ) or isinstance(parent,node_class)
209 self.context = context
210 self.type=self.our_type
212 self.uidperms = 5 # computed permissions for our uid, in unix bits
213 self.mimetype = 'application/octet-stream'
214 self.create_date = None
215 self.write_date = None
216 self.unixperms = 0660
218 self.ugroup = 'group'
219 self.content_length = 0
223 self.dctx = parent.dctx.copy()
224 self.displayname = 'Object'
226 def __eq__(self, other):
227 return NotImplemented
229 def __ne__(self, other):
230 return not self.__eq__(other)
233 """ Return the components of the full path for some
235 The returned list only contains the names of nodes.
238 s = self.parent.full_path()
241 if isinstance(self.path,list):
243 elif self.path is None:
247 return s #map(lambda x: '/' +x, s)
250 return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
252 def children(self, cr, domain=None):
253 print "node_class.children()"
256 def child(self,cr, name, domain=None):
257 print "node_class.child()"
260 def get_uri(self, cr, uri):
264 ndir = ndir.child(cr, duri[0])
271 print "node_class.path_get()"
274 def get_data(self,cr):
275 raise TypeError('no data for %s'% self.type)
277 def open_data(self, cr, mode):
278 """ Open a node_descriptor object for this node.
280 @param the mode of open, eg 'r', 'w', 'a', like file.open()
282 This operation may lock the data for this node (and accross
283 other node hierarchies), until the descriptor is close()d. If
284 the node is locked, subsequent opens (depending on mode) may
285 immediately fail with an exception (which?).
286 For this class, there is no data, so no implementation. Each
287 child class that has data should override this.
289 raise TypeError('no data for %s' % self.type)
291 def _get_storage(self,cr):
292 raise RuntimeError("no storage for base class")
294 def get_etag(self,cr):
295 """ Get a tag, unique per object + modification.
297 see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
298 return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr))
300 def _get_wtag(self, cr):
301 """ Return the modification time as a unique, compact string """
302 return str(_str2time(self.write_date)).replace('.','')
304 def _get_ttag(self, cr):
305 """ Get a unique tag for this type/id of object.
306 Must be overriden, so that each node is uniquely identified.
308 print "node_class.get_ttag()",self
309 raise NotImplementedError("get_ttag stub()")
311 def get_dav_props(self, cr):
312 """ If this class has special behaviour for GroupDAV etc, export
314 # This fn is placed here rather than WebDAV, because we want the
315 # baseclass methods to apply to all node subclasses
316 return self.DAV_PROPS or {}
318 def match_dav_eprop(self, cr, match, ns, prop):
319 res = self.get_dav_eprop(cr, ns, prop)
324 def get_dav_eprop(self, cr, ns, prop):
325 if not self.DAV_M_NS:
328 if self.DAV_M_NS.has_key(ns):
329 prefix = self.DAV_M_NS[ns]
331 logger.debug('No namespace: %s ("%s")',ns, prop)
334 mname = prefix + "_" + prop.replace('-','_')
336 if not hasattr(self, mname):
340 m = getattr(self, mname)
343 except AttributeError:
344 logger.debug('Property %s not supported' % prop, exc_info=True)
347 def get_dav_resourcetype(self, cr):
348 """ Get the DAV resource type.
350 Is here because some nodes may exhibit special behaviour, like
351 CalDAV/GroupDAV collections
353 raise NotImplementedError
355 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
356 """ Move this node to a new parent directory.
357 @param ndir_node the collection that this node should be moved under
358 @param new_name a name to rename this node to. If omitted, the old
360 @param fil_obj, can be None, is the browse object for the file,
361 if already available.
362 @param ndir_obj must be the browse object to the new doc.directory
363 location, where this node should be moved to.
364 in_write: When called by write(), we shouldn't attempt to write the
365 object, but instead return the dict of vals (avoid re-entrance).
366 If false, we should write all data to the object, here, as if the
367 caller won't do anything after calling move_to()
370 True: the node is moved, the caller can update other values, too.
371 False: the node is either removed or fully updated, the caller
372 must discard the fil_obj, not attempt to write any more to it.
373 dict: values to write back to the object. *May* contain a new id!
375 Depending on src and target storage, implementations of this function
376 could do various things.
377 Should also consider node<->content, dir<->dir moves etc.
379 Move operations, as instructed from APIs (eg. request from DAV) could
382 raise NotImplementedError(repr(self))
384 def create_child(self, cr, path, data=None):
385 """ Create a regular file under this node
387 logger.warning("Attempted to create a file under %r, not possible.", self)
388 raise IOError(errno.EPERM, "Not allowed to create files here")
390 def create_child_collection(self, cr, objname):
391 """ Create a child collection (directory) under self
393 logger.warning("Attempted to create a collection under %r, not possible.", self)
394 raise IOError(errno.EPERM, "Not allowed to create folders here")
397 raise NotImplementedError(repr(self))
400 raise NotImplementedError(repr(self))
402 def get_domain(self, cr, filters):
406 def check_perms(self, perms):
407 """ Check the permissions of the current node.
409 @param perms either an integers of the bits to check, or
410 a string with the permission letters
412 Permissions of nodes are (in a unix way):
413 1, x : allow descend into dir
414 2, w : allow write into file, or modification to dir
415 4, r : allow read of file, or listing of dir contents
416 8, u : allow remove (unlink)
419 if isinstance(perms, str):
421 chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
425 elif isinstance(perms, int):
426 if perms < 0 or perms > 15:
427 raise ValueError("Invalid permission bits")
429 raise ValueError("Invalid permission attribute")
431 return ((self.uidperms & perms) == perms)
433 class node_database(node_class):
434 """ A node representing the database directory
437 our_type = 'database'
438 def __init__(self, path=[], parent=False, context=None):
439 super(node_database,self).__init__(path, parent, context)
440 self.unixperms = 040750
443 def children(self, cr, domain=None):
444 res = self._child_get(cr, domain=domain) + self._file_get(cr)
447 def child(self, cr, name, domain=None):
448 res = self._child_get(cr, name, domain=None)
451 res = self._file_get(cr,name)
456 def _child_get(self, cr, name=False, domain=None):
457 dirobj = self.context._dirobj
458 uid = self.context.uid
459 ctx = self.context.context.copy()
460 ctx.update(self.dctx)
461 where = [('parent_id','=', False), ('ressource_parent_type_id','=',False)]
463 where.append(('name','=',name))
464 is_allowed = self.check_perms(1)
466 is_allowed = self.check_perms(5)
469 raise IOError(errno.EPERM, "Permission into directory denied")
472 where = where + domain
473 ids = dirobj.search(cr, uid, where, context=ctx)
475 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
476 klass = dirr.get_node_class(dirr, context=ctx)
477 res.append(klass(dirr.name, self, self.context,dirr))
481 def _file_get(self,cr, nodename=False):
485 def _get_ttag(self,cr):
486 return 'db-%s' % cr.dbname
488 def mkdosname(company_name, default='noname'):
489 """ convert a string to a dos-like name"""
492 badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
494 for c in company_name[:8]:
495 n += (c in badchars and '_') or c
499 def _uid2unixperms(perms, has_owner):
500 """ Convert the uidperms and the owner flag to full unix bits
504 res |= (perms & 0x07) << 6
505 res |= (perms & 0x05) << 3
507 res |= (perms & 0x07) << 6
508 res |= (perms & 0x07) << 3
510 res |= (perms & 0x07) << 6
511 res |= (perms & 0x05) << 3
515 class node_dir(node_database):
516 our_type = 'collection'
517 def __init__(self, path, parent, context, dirr, dctx=None):
518 super(node_dir,self).__init__(path, parent,context)
519 self.dir_id = dirr and dirr.id or False
520 #todo: more info from dirr
521 self.mimetype = 'application/x-directory'
522 # 'httpd/unix-directory'
523 self.create_date = dirr and dirr.create_date or False
524 self.domain = dirr and dirr.domain or []
525 self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
526 # TODO: the write date should be MAX(file.write)..
527 self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
528 self.content_length = 0
530 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
532 self.uuser = 'nobody'
533 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
534 self.uidperms = dirr.get_dir_permissions()
535 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
537 self.dctx.update(dctx)
538 dc2 = self.context.context
539 dc2.update(self.dctx)
540 dc2['dir_id'] = self.dir_id
541 self.displayname = dirr and dirr.name or False
542 if dirr and dirr.dctx_ids:
543 for dfld in dirr.dctx_ids:
545 self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
547 print "Cannot eval %s" % dfld.expr
551 def __eq__(self, other):
552 if type(self) != type(other):
554 if not self.context == other.context:
556 # Two directory nodes, for the same document.directory, may have a
557 # different context! (dynamic folders)
558 if self.dctx != other.dctx:
560 return self.dir_id == other.dir_id
562 def get_data(self, cr):
564 #for child in self.children(cr):
565 # res += child.get_data(cr)
568 def _file_get(self, cr, nodename=False):
569 res = super(node_dir,self)._file_get(cr, nodename)
571 is_allowed = self.check_perms(nodename and 1 or 5)
573 raise IOError(errno.EPERM, "Permission into directory denied")
575 cntobj = self.context._dirobj.pool.get('document.directory.content')
576 uid = self.context.uid
577 ctx = self.context.context.copy()
578 ctx.update(self.dctx)
579 where = [('directory_id','=',self.dir_id) ]
580 ids = cntobj.search(cr, uid, where, context=ctx)
581 for content in cntobj.browse(cr, uid, ids, context=ctx):
582 res3 = cntobj._file_get(cr, self, nodename, content)
588 def _child_get(self, cr, name=None, domain=None):
589 dirobj = self.context._dirobj
590 uid = self.context.uid
591 ctx = self.context.context.copy()
592 ctx.update(self.dctx)
593 where = [('parent_id','=',self.dir_id)]
595 where.append(('name','=',name))
596 is_allowed = self.check_perms(1)
598 is_allowed = self.check_perms(5)
601 raise IOError(errno.EPERM, "Permission into directory denied")
606 where2 = where + domain + [('ressource_parent_type_id','=',False)]
607 ids = dirobj.search(cr, uid, where2, context=ctx)
609 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
610 klass = dirr.get_node_class(dirr, context=ctx)
611 res.append(klass(dirr.name, self, self.context,dirr))
613 # Static directories should never return files with res_model/res_id
614 # because static dirs are /never/ related to a record.
615 # In fact, files related to some model and parented by the root dir
616 # (the default), will NOT be accessible in the node system unless
617 # a resource folder for that model exists (with resource_find_all=True).
618 # Having resource attachments in a common folder is bad practice,
619 # because they would be visible to all users, and their names may be
620 # the same, conflicting.
621 where += [('res_model', '=', False)]
622 fil_obj = dirobj.pool.get('ir.attachment')
623 ids = fil_obj.search(cr, uid, where, context=ctx)
625 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
626 klass = self.context.node_file_class
627 res.append(klass(fil.name, self, self.context, fil))
631 uid = self.context.uid
632 directory = self.context._dirobj.browse(cr, uid, self.dir_id)
635 raise OSError(2, 'Not such file or directory.')
636 if not self.check_perms('u'):
637 raise IOError(errno.EPERM,"Permission denied")
639 if directory._table_name=='document.directory':
640 if self.children(cr):
641 raise OSError(39, 'Directory not empty.')
642 res = self.context._dirobj.unlink(cr, uid, [directory.id])
644 raise OSError(1, 'Operation not permited.')
647 def create_child_collection(self, cr, objname):
649 if not self.check_perms(2):
650 raise IOError(errno.EPERM,"Permission denied")
652 dirobj = self.context._dirobj
653 uid = self.context.uid
654 ctx = self.context.context.copy()
655 ctx.update(self.dctx)
656 obj = dirobj.browse(cr, uid, self.dir_id)
657 if obj and (obj.type == 'ressource') and not object2:
658 raise OSError(1, 'Operation not permited.')
663 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
664 'ressource_id': object2 and object2.id or False,
665 'parent_id' : obj and obj.id or False
668 return dirobj.create(cr, uid, val)
671 def create_child(self, cr, path, data=None):
672 """ API function to create a child file object and node
673 Return the node_* created
675 if not self.check_perms(2):
676 raise IOError(errno.EPERM,"Permission denied")
678 dirobj = self.context._dirobj
679 uid = self.context.uid
680 ctx = self.context.context.copy()
681 ctx.update(self.dctx)
682 fil_obj=dirobj.pool.get('ir.attachment')
686 'parent_id': self.dir_id,
687 # Datas are not set here
690 fil_id = fil_obj.create(cr, uid, val, context=ctx)
691 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
692 fnode = node_file(path, self, self.context, fil)
694 fnode.set_data(cr, data, fil)
697 def _get_ttag(self,cr):
698 return 'dir-%d' % self.dir_id
700 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
701 """ Move directory. This operation is simple, since the present node is
702 only used for static, simple directories.
703 Note /may/ be called with ndir_node = None, to rename the document root.
705 if ndir_node and (ndir_node.context != self.context):
706 raise NotImplementedError("Cannot move directories between contexts")
708 if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
709 raise IOError(errno.EPERM,"Permission denied")
711 dir_obj = self.context._dirobj
713 dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
716 assert dbro.id == self.dir_id
719 raise IndexError("Cannot locate dir %d", self.dir_id)
721 if (not self.parent) and ndir_node:
722 if not dbro.parent_id:
723 raise IOError(errno.EPERM, "Cannot move the root directory!")
724 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
727 if self.parent != ndir_node:
728 logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
729 raise NotImplementedError('Cannot move dir to another dir')
732 if new_name and (new_name != dbro.name):
733 if ndir_node.child(cr, new_name):
734 raise IOError(errno.EEXIST, "Destination path already exists")
735 ret['name'] = new_name
740 # We have to update the data ourselves
742 ctx = self.context.context.copy()
743 ctx['__from_node'] = True
744 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
749 class node_res_dir(node_class):
750 """ A folder containing dynamic folders
751 A special sibling to node_dir, which does only contain dynamically
752 created folders foreach resource in the foreign model.
753 All folders should be of type node_res_obj and merely behave like
754 node_dirs (with limited domain).
756 our_type = 'collection'
758 def __init__(self, path, parent, context, dirr, dctx=None ):
759 super(node_res_dir,self).__init__(path, parent, context)
760 self.dir_id = dirr.id
761 #todo: more info from dirr
762 self.mimetype = 'application/x-directory'
763 # 'httpd/unix-directory'
764 self.create_date = dirr.create_date
765 # TODO: the write date should be MAX(file.write)..
766 self.write_date = dirr.write_date or dirr.create_date
767 self.content_length = 0
769 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
771 self.uuser = 'nobody'
772 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
773 self.uidperms = dirr.get_dir_permissions()
774 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
775 self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
776 self.resm_id = dirr.ressource_id
777 self.res_find_all = dirr.resource_find_all
778 self.namefield = dirr.resource_field.name or 'name'
779 self.displayname = dirr.name
780 # Important: the domain is evaluated using the *parent* dctx!
781 self.domain = dirr.domain
782 self.ressource_tree = dirr.ressource_tree
783 # and then, we add our own vars in the dctx:
785 self.dctx.update(dctx)
787 # and then, we prepare a dctx dict, for deferred evaluation:
789 for dfld in dirr.dctx_ids:
790 self.dctx_dict['dctx_' + dfld.field] = dfld.expr
792 def __eq__(self, other):
793 if type(self) != type(other):
795 if not self.context == other.context:
797 # Two nodes, for the same document.directory, may have a
798 # different context! (dynamic folders)
799 if self.dctx != other.dctx:
801 return self.dir_id == other.dir_id
803 def children(self, cr, domain=None):
804 return self._child_get(cr, domain=domain)
806 def child(self,cr, name, domain=None):
807 res = self._child_get(cr, name, domain=domain)
812 def _child_get(self, cr, name=None, domain=None):
813 """ return virtual children of resource, based on the
816 Note that many objects use NULL for a name, so we should
817 better call the name_search(),name_get() set of methods
819 obj = self.context._dirobj.pool.get(self.res_model)
822 dirobj = self.context._dirobj
823 uid = self.context.uid
824 ctx = self.context.context.copy()
825 ctx.update(self.dctx)
828 app = safe_eval(self.domain, ctx)
831 elif isinstance(app, list):
833 elif isinstance(app, tuple):
836 raise RuntimeError("incorrect domain expr: %s" % self.domain)
838 where.append(('id','=',self.resm_id))
841 # The =like character will match underscores against any characters
842 # including the special ones that couldn't exist in a FTP/DAV request
843 where.append((self.namefield,'=like',name.replace('\\','\\\\')))
844 is_allowed = self.check_perms(1)
846 is_allowed = self.check_perms(5)
849 raise IOError(errno.EPERM,"Permission denied")
851 # print "Where clause for %s" % self.res_model, where
852 if self.ressource_tree:
855 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
856 if obj._parent_name in obj.fields_get(cr, uid):
857 where.append((obj._parent_name,'=',object2 and object2.id or False))
859 resids = obj.search(cr, uid, where, context=ctx)
861 for bo in obj.browse(cr, uid, resids, context=ctx):
864 res_name = getattr(bo, self.namefield)
867 # Yes! we can't do better but skip nameless records.
869 # Escape the name for characters not supported in filenames
870 res_name = res_name.replace('/','_') # any other weird char?
872 if name and (res_name != ustr(name)):
873 # we have matched _ to any character, but we only meant to match
875 # Eg. 'a_c' will find 'abc', 'a/c', 'a_c', may only
876 # return 'a/c' and 'a_c'
879 res.append(self.res_obj_class(res_name, self.dir_id, self, self.context, self.res_model, bo))
882 def _get_ttag(self,cr):
883 return 'rdir-%d' % self.dir_id
885 class node_res_obj(node_class):
886 """ A dynamically created folder.
887 A special sibling to node_dir, which does only contain dynamically
888 created folders foreach resource in the foreign model.
889 All folders should be of type node_res_obj and merely behave like
890 node_dirs (with limited domain).
892 our_type = 'collection'
893 def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
894 super(node_res_obj,self).__init__(path, parent,context)
896 #todo: more info from dirr
898 self.mimetype = 'application/x-directory'
899 # 'httpd/unix-directory'
900 self.create_date = parent.create_date
901 # TODO: the write date should be MAX(file.write)..
902 self.write_date = parent.write_date
903 self.content_length = 0
904 self.uidperms = parent.uidperms & 15
905 self.unixperms = 040000 | _uid2unixperms(self.uidperms, True)
906 self.uuser = parent.uuser
907 self.ugroup = parent.ugroup
908 self.res_model = res_model
909 self.domain = parent.domain
910 self.displayname = path
911 self.dctx_dict = parent.dctx_dict
912 if isinstance(parent, node_res_dir):
913 self.res_find_all = parent.res_find_all
915 self.res_find_all = False
917 self.res_id = res_bo.id
918 dc2 = self.context.context.copy()
919 dc2.update(self.dctx)
920 dc2['res_model'] = res_model
921 dc2['res_id'] = res_bo.id
923 for fld,expr in self.dctx_dict.items():
925 self.dctx[fld] = safe_eval(expr, dc2)
927 print "Cannot eval %s for %s" % (expr, fld)
933 def __eq__(self, other):
934 if type(self) != type(other):
936 if not self.context == other.context:
938 if not self.res_model == other.res_model:
940 if not self.res_id == other.res_id:
942 if self.domain != other.domain:
944 if self.res_find_all != other.res_find_all:
946 if self.dctx != other.dctx:
948 return self.dir_id == other.dir_id
950 def children(self, cr, domain=None):
951 return self._child_get(cr, domain=domain) + self._file_get(cr)
953 def child(self, cr, name, domain=None):
954 res = self._child_get(cr, name, domain=domain)
957 res = self._file_get(cr, name)
962 def _file_get(self,cr, nodename=False):
964 is_allowed = self.check_perms((nodename and 1) or 5)
966 raise IOError(errno.EPERM,"Permission denied")
968 cntobj = self.context._dirobj.pool.get('document.directory.content')
969 uid = self.context.uid
970 ctx = self.context.context.copy()
971 ctx.update(self.dctx)
972 where = [('directory_id','=',self.dir_id) ]
974 # where.extend(self.domain)
975 # print "res_obj file_get clause", where
976 ids = cntobj.search(cr, uid, where, context=ctx)
977 for content in cntobj.browse(cr, uid, ids, context=ctx):
978 res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
984 def get_dav_props_DEPR(self, cr):
985 # Deprecated! (but document_ics must be cleaned, first)
987 cntobj = self.context._dirobj.pool.get('document.directory.content')
988 uid = self.context.uid
989 ctx = self.context.context.copy()
990 ctx.update(self.dctx)
991 where = [('directory_id','=',self.dir_id) ]
992 ids = cntobj.search(cr, uid, where, context=ctx)
993 for content in cntobj.browse(cr, uid, ids, context=ctx):
994 if content.extension == '.ics': # FIXME: call the content class!
995 res['http://groupdav.org/'] = ('resourcetype',)
998 def get_dav_eprop_DEPR(self, cr, ns, prop):
1000 if ns != 'http://groupdav.org/' or prop != 'resourcetype':
1001 logger.warning("Who asked for %s:%s?" % (ns, prop))
1003 cntobj = self.context._dirobj.pool.get('document.directory.content')
1004 uid = self.context.uid
1005 ctx = self.context.context.copy()
1006 ctx.update(self.dctx)
1007 where = [('directory_id','=',self.dir_id) ]
1008 ids = cntobj.search(cr,uid,where,context=ctx)
1009 for content in cntobj.browse(cr, uid, ids, context=ctx):
1010 # TODO: remove relic of GroupDAV
1011 if content.extension == '.ics': # FIXME: call the content class!
1012 return ('vevent-collection','http://groupdav.org/')
1015 def _child_get(self, cr, name=None, domain=None):
1016 dirobj = self.context._dirobj
1018 is_allowed = self.check_perms((name and 1) or 5)
1020 raise IOError(errno.EPERM,"Permission denied")
1022 uid = self.context.uid
1023 ctx = self.context.context.copy()
1024 ctx.update(self.dctx)
1025 directory = dirobj.browse(cr, uid, self.dir_id)
1026 obj = dirobj.pool.get(self.res_model)
1030 where.append(('name','=',name))
1032 # Directory Structure display in tree structure
1033 if self.res_id and directory.ressource_tree:
1036 where1.append(('name','=like',name.replace('\\','\\\\')))
1037 if obj._parent_name in obj.fields_get(cr, uid):
1038 where1.append((obj._parent_name, '=', self.res_id))
1039 namefield = directory.resource_field.name or 'name'
1040 resids = obj.search(cr, uid, where1, context=ctx)
1041 for bo in obj.browse(cr, uid, resids, context=ctx):
1044 res_name = getattr(bo, namefield)
1047 res_name = res_name.replace('/', '_')
1048 if name and (res_name != ustr(name)):
1051 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
1052 rnode = klass(res_name, dir_id=self.dir_id, parent=self, context=self.context,
1053 res_model=self.res_model, res_bo=bo)
1054 rnode.res_find_all = self.res_find_all
1058 where2 = where + [('parent_id','=',self.dir_id) ]
1059 ids = dirobj.search(cr, uid, where2, context=ctx)
1060 bo = obj.browse(cr, uid, self.res_id, context=ctx)
1062 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
1063 if name and (name != dirr.name):
1065 if dirr.type == 'directory':
1066 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1067 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id))
1068 elif dirr.type == 'ressource':
1069 # child resources can be controlled by properly set dctx
1070 klass = dirr.get_node_class(dirr, context=ctx)
1071 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) # bo?
1073 fil_obj = dirobj.pool.get('ir.attachment')
1074 if self.res_find_all:
1076 where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
1077 # print "where clause for dir_obj", where3
1078 ids = fil_obj.search(cr, uid, where3, context=ctx)
1080 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
1081 klass = self.context.node_file_class
1082 res.append(klass(fil.name, self, self.context, fil))
1085 # Get Child Ressource Directories
1086 if directory.ressource_type_id and directory.ressource_type_id.id:
1087 where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
1088 where5 = where4 + ['|', ('ressource_id','=',0), ('ressource_id','=',self.res_id)]
1089 dirids = dirobj.search(cr,uid, where5)
1090 for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1091 if dirr.type == 'directory' and not dirr.parent_id:
1092 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1093 rnode = klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id)
1094 rnode.res_find_all = dirr.resource_find_all
1096 if dirr.type == 'ressource':
1097 klass = dirr.get_node_class(dirr, context=ctx)
1098 rnode = klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id})
1099 rnode.res_find_all = dirr.resource_find_all
1103 def create_child_collection(self, cr, objname):
1104 dirobj = self.context._dirobj
1105 is_allowed = self.check_perms(2)
1107 raise IOError(errno.EPERM,"Permission denied")
1109 uid = self.context.uid
1110 ctx = self.context.context.copy()
1111 ctx.update(self.dctx)
1112 res_obj = dirobj.pool.get(self.res_model)
1114 object2 = res_obj.browse(cr, uid, self.res_id) or False
1116 obj = dirobj.browse(cr, uid, self.dir_id)
1117 if obj and (obj.type == 'ressource') and not object2:
1118 raise OSError(1, 'Operation not permited.')
1123 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1124 'ressource_id': object2 and object2.id or False,
1125 'parent_id' : False,
1126 'resource_find_all': False,
1128 if (obj and (obj.type in ('directory'))) or not object2:
1129 val['parent_id'] = obj and obj.id or False
1131 return dirobj.create(cr, uid, val)
1133 def create_child(self, cr, path, data=None):
1134 """ API function to create a child file object and node
1135 Return the node_* created
1137 is_allowed = self.check_perms(2)
1139 raise IOError(errno.EPERM,"Permission denied")
1141 dirobj = self.context._dirobj
1142 uid = self.context.uid
1143 ctx = self.context.context.copy()
1144 ctx.update(self.dctx)
1145 fil_obj=dirobj.pool.get('ir.attachment')
1148 'datas_fname': path,
1149 'res_model': self.res_model,
1150 'res_id': self.res_id,
1151 # Datas are not set here
1153 if not self.res_find_all:
1154 val['parent_id'] = self.dir_id
1156 fil_id = fil_obj.create(cr, uid, val, context=ctx)
1157 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1158 klass = self.context.node_file_class
1159 fnode = klass(path, self, self.context, fil)
1160 if data is not None:
1161 fnode.set_data(cr, data, fil)
1164 def _get_ttag(self,cr):
1165 return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1167 node_res_dir.res_obj_class = node_res_obj
1169 class node_file(node_class):
1171 def __init__(self, path, parent, context, fil):
1172 super(node_file,self).__init__(path, parent,context)
1173 self.file_id = fil.id
1174 #todo: more info from ir_attachment
1175 if fil.file_type and '/' in fil.file_type:
1176 self.mimetype = str(fil.file_type)
1177 self.create_date = fil.create_date
1178 self.write_date = fil.write_date or fil.create_date
1179 self.content_length = fil.file_size
1180 self.displayname = fil.name
1184 if not parent.check_perms('x'):
1186 elif not parent.check_perms('w'):
1190 self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1192 self.uuser = 'nobody'
1193 self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1195 # This only propagates the problem to get_data. Better
1196 # fix those files to point to the root dir.
1197 self.storage_id = None
1200 if par.storage_id and par.storage_id.id:
1201 self.storage_id = par.storage_id.id
1205 def __eq__(self, other):
1206 if type(self) != type(other):
1208 if not self.context == other.context:
1210 if self.dctx != other.dctx:
1212 return self.file_id == other.file_id
1215 def open_data(self, cr, mode):
1216 stor = self.storage_id
1217 assert stor, "No storage for file #%s" % self.file_id
1218 if not self.check_perms(4):
1219 raise IOError(errno.EPERM, "Permission denied")
1221 # If storage is not set properly, we are just screwed here, don't
1222 # try to get it from default.
1223 stobj = self.context._dirobj.pool.get('document.storage')
1224 return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1227 uid = self.context.uid
1228 if not self.check_perms(8):
1229 raise IOError(errno.EPERM, "Permission denied")
1230 document_obj = self.context._dirobj.pool.get('ir.attachment')
1231 if self.type in ('collection','database'):
1233 document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1235 if document and document._table_name == 'ir.attachment':
1236 res = document_obj.unlink(cr, uid, [document.id])
1239 def fix_ppath(self, cr, fbro):
1240 """Sometimes we may init this w/o path, parent.
1241 This function fills the missing path from the file browse object
1243 Note: this may be an expensive operation, do on demand. However,
1244 once caching is in, we might want to do that at init time and keep
1247 if self.path or self.parent:
1250 uid = self.context.uid
1254 dirobj = self.context._dirobj.pool.get('document.directory')
1255 dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1256 if fbro.datas_fname:
1257 dirpath.append(fbro.datas_fname)
1259 dirpath.append(fbro.name)
1264 self.path = dirpath[0]
1266 def get_data(self, cr, fil_obj = None):
1267 """ Retrieve the data for some file.
1268 fil_obj may optionally be specified, and should be a browse object
1269 for the file. This is useful when the caller has already initiated
1270 the browse object. """
1271 # this is where storage kicks in..
1272 stor = self.storage_id
1273 assert stor, "No storage for file #%s" % self.file_id
1274 if not self.check_perms(4):
1275 raise IOError(errno.EPERM, "Permission denied")
1277 # If storage is not set properly, we are just screwed here, don't
1278 # try to get it from default.
1279 stobj = self.context._dirobj.pool.get('document.storage')
1280 return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1282 def get_data_len(self, cr, fil_obj = None):
1283 # TODO: verify with the storage object!
1284 bin_size = self.context.context.get('bin_size', False)
1285 if bin_size and not self.content_length:
1286 self.content_length = fil_obj.db_datas
1287 return self.content_length
1289 def set_data(self, cr, data, fil_obj = None):
1290 """ Store data at some file.
1291 fil_obj may optionally be specified, and should be a browse object
1292 for the file. This is useful when the caller has already initiated
1293 the browse object. """
1294 # this is where storage kicks in..
1295 stor = self.storage_id
1296 assert stor, "No storage for file #%s" % self.file_id
1297 if not self.check_perms(2):
1298 raise IOError(errno.EPERM, "Permission denied")
1300 stobj = self.context._dirobj.pool.get('document.storage')
1301 return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1303 def _get_ttag(self,cr):
1304 return 'file-%d' % self.file_id
1306 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1307 if ndir_node and ndir_node.context != self.context:
1308 raise NotImplementedError("Cannot move files between contexts")
1310 if (not self.check_perms(8)) and ndir_node.check_perms(2):
1311 raise IOError(errno.EPERM, "Permission denied")
1313 doc_obj = self.context._dirobj.pool.get('ir.attachment')
1315 dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1318 assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1321 raise IndexError("Cannot locate doc %d", self.file_id)
1323 if (not self.parent):
1324 # there *must* be a parent node for this one
1325 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1329 if ndir_node and self.parent != ndir_node:
1330 if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1331 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1332 raise NotImplementedError('Cannot move files between dynamic folders')
1335 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1336 ndir_node.dir_id, context=self.context.context)
1338 assert ndir_obj.id == ndir_node.dir_id
1340 stobj = self.context._dirobj.pool.get('document.storage')
1341 r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1342 context=self.context.context)
1345 if new_name and (new_name != dbro.name):
1347 raise NotImplementedError("Cannot rename and move") # TODO
1348 stobj = self.context._dirobj.pool.get('document.storage')
1349 r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1355 # We have to update the data ourselves
1357 ctx = self.context.context.copy()
1358 ctx['__from_node'] = True
1359 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1364 class node_content(node_class):
1365 our_type = 'content'
1366 def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1367 super(node_content,self).__init__(path, parent,context)
1368 self.cnt_id = cnt.id
1369 self.create_date = False
1370 self.write_date = False
1371 self.content_length = False
1372 self.unixperms = 0640
1374 self.uidperms = parent.uidperms & 14
1375 self.uuser = parent.uuser
1376 self.ugroup = parent.ugroup
1378 self.extension = cnt.extension
1379 self.report_id = cnt.report_id and cnt.report_id.id
1380 #self.mimetype = cnt.extension.
1381 self.displayname = path
1383 self.dctx.update(dctx)
1384 self.act_id = act_id
1386 def fill_fields(self, cr, dctx = None):
1387 """ Try to read the object and fill missing fields, like mimetype,
1389 This function must be different from the constructor, because
1390 it uses the db cursor.
1393 cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1396 if res and res[0][0]:
1397 self.mimetype = str(res[0][0])
1400 def get_data(self, cr, fil_obj = None):
1401 cntobj = self.context._dirobj.pool.get('document.directory.content')
1402 if not self.check_perms(4):
1403 raise IOError(errno.EPERM, "Permission denied")
1405 ctx = self.context.context.copy()
1406 ctx.update(self.dctx)
1407 data = cntobj.process_read(cr, self.context.uid, self, ctx)
1409 self.content_length = len(data)
1412 def open_data(self, cr, mode):
1413 if mode.endswith('b'):
1415 if mode in ('r', 'w'):
1417 elif mode in ('r+', 'w+'):
1420 raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1422 if not self.check_perms(cperms):
1423 raise IOError(errno.EPERM, "Permission denied")
1425 ctx = self.context.context.copy()
1426 ctx.update(self.dctx)
1428 return nodefd_content(self, cr, mode, ctx)
1430 def get_data_len(self, cr, fil_obj = None):
1431 # FIXME : here, we actually generate the content twice!!
1432 # we should have cached the generated content, but it is
1433 # not advisable to do keep it in memory, until we have a cache
1435 if not self.content_length:
1436 self.get_data(cr,fil_obj)
1437 return self.content_length
1439 def set_data(self, cr, data, fil_obj = None):
1440 cntobj = self.context._dirobj.pool.get('document.directory.content')
1441 if not self.check_perms(2):
1442 raise IOError(errno.EPERM, "Permission denied")
1444 ctx = self.context.context.copy()
1445 ctx.update(self.dctx)
1446 return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1448 def _get_ttag(self,cr):
1449 return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1451 def get_dav_resourcetype(self, cr):
1454 class nodefd_content(StringIO, node_descriptor):
1455 """ A descriptor to content nodes
1457 def __init__(self, parent, cr, mode, ctx):
1458 node_descriptor.__init__(self, parent)
1462 if mode in ('r', 'r+'):
1463 cntobj = parent.context._dirobj.pool.get('document.directory.content')
1464 data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1466 self._size = len(data)
1467 parent.content_length = len(data)
1468 StringIO.__init__(self, data)
1469 elif mode in ('w', 'w+'):
1470 StringIO.__init__(self, None)
1471 # at write, we start at 0 (= overwrite), but have the original
1472 # data available, in case of a seek()
1474 StringIO.__init__(self, None)
1476 logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1477 raise IOError(errno.EINVAL, "Invalid file mode")
1484 # we now open a *separate* cursor, to update the data.
1485 # FIXME: this may be improved, for concurrency handling
1486 if self.mode == 'r':
1487 StringIO.close(self)
1490 par = self._get_parent()
1491 uid = par.context.uid
1492 cr = pooler.get_db(par.context.dbname).cursor()
1494 if self.mode in ('w', 'w+', 'r+'):
1495 data = self.getvalue()
1496 cntobj = par.context._dirobj.pool.get('document.directory.content')
1497 cntobj.process_write(cr, uid, par, data, par.context.context)
1498 elif self.mode == 'a':
1499 raise NotImplementedError
1502 logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1506 StringIO.close(self)
1508 class nodefd_static(StringIO, node_descriptor):
1509 """ A descriptor to nodes with static data.
1511 def __init__(self, parent, cr, mode, ctx=None):
1512 node_descriptor.__init__(self, parent)
1516 if mode in ('r', 'r+'):
1517 data = parent.get_data(cr)
1519 self._size = len(data)
1520 parent.content_length = len(data)
1521 StringIO.__init__(self, data)
1522 elif mode in ('w', 'w+'):
1523 StringIO.__init__(self, None)
1524 # at write, we start at 0 (= overwrite), but have the original
1525 # data available, in case of a seek()
1527 StringIO.__init__(self, None)
1529 logging.getLogger('document.nodes').error("Incorrect mode %s specified", mode)
1530 raise IOError(errno.EINVAL, "Invalid file mode")
1537 # we now open a *separate* cursor, to update the data.
1538 # FIXME: this may be improved, for concurrency handling
1539 if self.mode == 'r':
1540 StringIO.close(self)
1543 par = self._get_parent()
1544 # uid = par.context.uid
1545 cr = pooler.get_db(par.context.dbname).cursor()
1547 if self.mode in ('w', 'w+', 'r+'):
1548 data = self.getvalue()
1549 par.set_data(cr, data)
1550 elif self.mode == 'a':
1551 raise NotImplementedError
1554 logging.getLogger('document.nodes').exception('Cannot update db content #%d for close:', par.cnt_id)
1558 StringIO.close(self)