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
44 _logger = logging.getLogger(__name__)
47 """ Convert a string with time representation (from db) into time (float)
49 Note: a place to fix if datetime is used in db.
54 if isinstance(cre, basestring) and '.' in cre:
56 frac = float(cre[fdot:])
58 return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
60 def get_node_context(cr, uid, context):
61 return node_context(cr, uid, context)
63 class node_context(object):
64 """ This is the root node, representing access to some particular context
66 A context is a set of persistent data, which may influence the structure
67 of the nodes. All other transient information during a data query should
68 be passed down with function arguments.
71 node_file_class = None
73 def __init__(self, cr, uid, context=None):
74 self.dbname = cr.dbname
76 self.context = context
80 self._dirobj = pooler.get_pool(cr.dbname).get('document.directory')
81 self.node_file_class = node_file
82 self.extra_ctx = {} # Extra keys for context, that do _not_ trigger inequality
84 self._dirobj._prepare_context(cr, uid, self, context=context)
85 self.rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
87 def __eq__(self, other):
88 if not type(other) == node_context:
90 if self.dbname != other.dbname:
92 if self.uid != other.uid:
94 if self.context != other.context:
96 if self.rootdir != other.rootdir:
100 def __ne__(self, other):
101 return not self.__eq__(other)
103 def get(self, name, default=None):
104 return self.context.get(name, default)
106 def get_uri(self, cr, uri):
107 """ Although this fn passes back to doc.dir, it is needed since
108 it is a potential caching point.
110 (ndir, duri) = self._dirobj._locate_child(cr, self.uid, self.rootdir, uri, None, self)
112 ndir = ndir.child(cr, duri[0])
118 def get_dir_node(self, cr, dbro):
119 """Create (or locate) a node for a directory
120 @param dbro a browse object of document.directory
123 fullpath = dbro.get_full_path(context=self.context)
124 klass = dbro.get_node_class(dbro, context=self.context)
125 return klass(fullpath, None ,self, dbro)
127 def get_file_node(self, cr, fbro):
128 """ Create or locate a node for a static file
129 @param fbro a browse object of an ir.attachment
133 parent = self.get_dir_node(cr, fbro.parent_id)
135 return self.node_file_class(fbro.name, parent, self, fbro)
138 class node_descriptor(object):
139 """A file-like interface to the data contents of a node.
141 This class is NOT a node, but an /open descriptor/ for some
142 node. It can hold references to a cursor or a file object,
143 because the life of a node_descriptor will be the open period
145 It should also take care of locking, with any native mechanism
147 For the implementation, it would be OK just to wrap around file,
148 StringIO or similar class. The node_descriptor is only needed to
149 provide the link to the parent /node/ object.
152 def __init__(self, parent):
153 assert isinstance(parent, node_class)
154 self.name = parent.displayname
155 self.__parent = parent
157 def _get_parent(self):
160 def open(self, **kwargs):
161 raise NotImplementedError
164 raise NotImplementedError
166 def read(self, size=None):
167 raise NotImplementedError
169 def seek(self, offset, whence=None):
170 raise NotImplementedError
173 raise NotImplementedError
175 def write(self, str):
176 raise NotImplementedError
179 raise NotImplementedError
184 def __nonzero__(self):
185 """ Ensure that a node_descriptor will never equal False
187 Since we do define __len__ and __iter__ for us, we must avoid
188 being regarded as non-true objects.
193 raise NotImplementedError
195 class node_class(object):
196 """ this is a superclass for our inodes
197 It is an API for all code that wants to access the document files.
198 Nodes have attributes which contain usual file properties
200 our_type = 'baseclass'
204 def __init__(self, path, parent, context):
205 assert isinstance(context,node_context)
206 assert (not parent ) or isinstance(parent,node_class)
208 self.context = context
209 self.type=self.our_type
211 self.uidperms = 5 # computed permissions for our uid, in unix bits
212 self.mimetype = 'application/octet-stream'
213 self.create_date = None
214 self.write_date = None
215 self.unixperms = 0660
217 self.ugroup = 'group'
218 self.content_length = 0
222 self.dctx = parent.dctx.copy()
223 self.displayname = 'Object'
225 def __eq__(self, other):
226 return NotImplemented
228 def __ne__(self, other):
229 return not self.__eq__(other)
232 """ Return the components of the full path for some
234 The returned list only contains the names of nodes.
237 s = self.parent.full_path()
240 if isinstance(self.path,list):
242 elif self.path is None:
246 return s #map(lambda x: '/' +x, s)
249 return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
251 def children(self, cr, domain=None):
252 print "node_class.children()"
255 def child(self,cr, name, domain=None):
256 print "node_class.child()"
259 def get_uri(self, cr, uri):
263 ndir = ndir.child(cr, duri[0])
270 print "node_class.path_get()"
273 def get_data(self,cr):
274 raise TypeError('no data for %s'% self.type)
276 def open_data(self, cr, mode):
277 """ Open a node_descriptor object for this node.
279 @param the mode of open, eg 'r', 'w', 'a', like file.open()
281 This operation may lock the data for this node (and accross
282 other node hierarchies), until the descriptor is close()d. If
283 the node is locked, subsequent opens (depending on mode) may
284 immediately fail with an exception (which?).
285 For this class, there is no data, so no implementation. Each
286 child class that has data should override this.
288 raise TypeError('no data for %s' % self.type)
290 def _get_storage(self,cr):
291 raise RuntimeError("no storage for base class")
293 def get_etag(self,cr):
294 """ Get a tag, unique per object + modification.
296 see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
297 return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr))
299 def _get_wtag(self, cr):
300 """ Return the modification time as a unique, compact string """
301 return str(_str2time(self.write_date)).replace('.','')
303 def _get_ttag(self, cr):
304 """ Get a unique tag for this type/id of object.
305 Must be overriden, so that each node is uniquely identified.
307 print "node_class.get_ttag()",self
308 raise NotImplementedError("get_ttag stub()")
310 def get_dav_props(self, cr):
311 """ If this class has special behaviour for GroupDAV etc, export
313 # This fn is placed here rather than WebDAV, because we want the
314 # baseclass methods to apply to all node subclasses
315 return self.DAV_PROPS or {}
317 def match_dav_eprop(self, cr, match, ns, prop):
318 res = self.get_dav_eprop(cr, ns, prop)
323 def get_dav_eprop(self, cr, ns, prop):
324 if not self.DAV_M_NS:
327 if self.DAV_M_NS.has_key(ns):
328 prefix = self.DAV_M_NS[ns]
330 _logger.debug('No namespace: %s ("%s")',ns, prop)
333 mname = prefix + "_" + prop.replace('-','_')
335 if not hasattr(self, mname):
339 m = getattr(self, mname)
342 except AttributeError:
343 _logger.debug('Property %s not supported' % prop, exc_info=True)
346 def get_dav_resourcetype(self, cr):
347 """ Get the DAV resource type.
349 Is here because some nodes may exhibit special behaviour, like
350 CalDAV/GroupDAV collections
352 raise NotImplementedError
354 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
355 """ Move this node to a new parent directory.
356 @param ndir_node the collection that this node should be moved under
357 @param new_name a name to rename this node to. If omitted, the old
359 @param fil_obj, can be None, is the browse object for the file,
360 if already available.
361 @param ndir_obj must be the browse object to the new doc.directory
362 location, where this node should be moved to.
363 in_write: When called by write(), we shouldn't attempt to write the
364 object, but instead return the dict of vals (avoid re-entrance).
365 If false, we should write all data to the object, here, as if the
366 caller won't do anything after calling move_to()
369 True: the node is moved, the caller can update other values, too.
370 False: the node is either removed or fully updated, the caller
371 must discard the fil_obj, not attempt to write any more to it.
372 dict: values to write back to the object. *May* contain a new id!
374 Depending on src and target storage, implementations of this function
375 could do various things.
376 Should also consider node<->content, dir<->dir moves etc.
378 Move operations, as instructed from APIs (e.g. request from DAV) could
381 raise NotImplementedError(repr(self))
383 def create_child(self, cr, path, data=None):
384 """ Create a regular file under this node
386 _logger.warning("Attempted to create a file under %r, not possible.", self)
387 raise IOError(errno.EPERM, "Not allowed to create files here")
389 def create_child_collection(self, cr, objname):
390 """ Create a child collection (directory) under self
392 _logger.warning("Attempted to create a collection under %r, not possible.", self)
393 raise IOError(errno.EPERM, "Not allowed to create folders here")
396 raise NotImplementedError(repr(self))
399 raise NotImplementedError(repr(self))
401 def get_domain(self, cr, filters):
405 def check_perms(self, perms):
406 """ Check the permissions of the current node.
408 @param perms either an integers of the bits to check, or
409 a string with the permission letters
411 Permissions of nodes are (in a unix way):
412 1, x : allow descend into dir
413 2, w : allow write into file, or modification to dir
414 4, r : allow read of file, or listing of dir contents
415 8, u : allow remove (unlink)
418 if isinstance(perms, str):
420 chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
424 elif isinstance(perms, int):
425 if perms < 0 or perms > 15:
426 raise ValueError("Invalid permission bits")
428 raise ValueError("Invalid permission attribute")
430 return ((self.uidperms & perms) == perms)
432 class node_database(node_class):
433 """ A node representing the database directory
436 our_type = 'database'
437 def __init__(self, path=[], parent=False, context=None):
438 super(node_database,self).__init__(path, parent, context)
439 self.unixperms = 040750
442 def children(self, cr, domain=None):
443 res = self._child_get(cr, domain=domain) + self._file_get(cr)
446 def child(self, cr, name, domain=None):
447 res = self._child_get(cr, name, domain=None)
450 res = self._file_get(cr,name)
455 def _child_get(self, cr, name=False, domain=None):
456 dirobj = self.context._dirobj
457 uid = self.context.uid
458 ctx = self.context.context.copy()
459 ctx.update(self.dctx)
460 where = [('parent_id','=', False), ('ressource_parent_type_id','=',False)]
462 where.append(('name','=',name))
463 is_allowed = self.check_perms(1)
465 is_allowed = self.check_perms(5)
468 raise IOError(errno.EPERM, "Permission into directory denied")
471 where = where + domain
472 ids = dirobj.search(cr, uid, where, context=ctx)
474 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
475 klass = dirr.get_node_class(dirr, context=ctx)
476 res.append(klass(dirr.name, self, self.context,dirr))
480 def _file_get(self,cr, nodename=False):
484 def _get_ttag(self,cr):
485 return 'db-%s' % cr.dbname
487 def mkdosname(company_name, default='noname'):
488 """ convert a string to a dos-like name"""
491 badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
493 for c in company_name[:8]:
494 n += (c in badchars and '_') or c
498 def _uid2unixperms(perms, has_owner):
499 """ Convert the uidperms and the owner flag to full unix bits
503 res |= (perms & 0x07) << 6
504 res |= (perms & 0x05) << 3
506 res |= (perms & 0x07) << 6
507 res |= (perms & 0x07) << 3
509 res |= (perms & 0x07) << 6
510 res |= (perms & 0x05) << 3
514 class node_dir(node_database):
515 our_type = 'collection'
516 def __init__(self, path, parent, context, dirr, dctx=None):
517 super(node_dir,self).__init__(path, parent,context)
518 self.dir_id = dirr and dirr.id or False
519 #todo: more info from dirr
520 self.mimetype = 'application/x-directory'
521 # 'httpd/unix-directory'
522 self.create_date = dirr and dirr.create_date or False
523 self.domain = dirr and dirr.domain or []
524 self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
525 # TODO: the write date should be MAX(file.write)..
526 self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
527 self.content_length = 0
529 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
531 self.uuser = 'nobody'
532 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
533 self.uidperms = dirr.get_dir_permissions()
534 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
536 self.dctx.update(dctx)
537 dc2 = self.context.context
538 dc2.update(self.dctx)
539 dc2['dir_id'] = self.dir_id
540 self.displayname = dirr and dirr.name or False
541 if dirr and dirr.dctx_ids:
542 for dfld in dirr.dctx_ids:
544 self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
546 print "Cannot eval %s" % dfld.expr
550 def __eq__(self, other):
551 if type(self) != type(other):
553 if not self.context == other.context:
555 # Two directory nodes, for the same document.directory, may have a
556 # different context! (dynamic folders)
557 if self.dctx != other.dctx:
559 return self.dir_id == other.dir_id
561 def get_data(self, cr):
563 #for child in self.children(cr):
564 # res += child.get_data(cr)
567 def _file_get(self, cr, nodename=False):
568 res = super(node_dir,self)._file_get(cr, nodename)
570 is_allowed = self.check_perms(nodename and 1 or 5)
572 raise IOError(errno.EPERM, "Permission into directory denied")
574 cntobj = self.context._dirobj.pool.get('document.directory.content')
575 uid = self.context.uid
576 ctx = self.context.context.copy()
577 ctx.update(self.dctx)
578 where = [('directory_id','=',self.dir_id) ]
579 ids = cntobj.search(cr, uid, where, context=ctx)
580 for content in cntobj.browse(cr, uid, ids, context=ctx):
581 res3 = cntobj._file_get(cr, self, nodename, content)
587 def _child_get(self, cr, name=None, domain=None):
588 dirobj = self.context._dirobj
589 uid = self.context.uid
590 ctx = self.context.context.copy()
591 ctx.update(self.dctx)
592 where = [('parent_id','=',self.dir_id)]
594 where.append(('name','=',name))
595 is_allowed = self.check_perms(1)
597 is_allowed = self.check_perms(5)
600 raise IOError(errno.EPERM, "Permission into directory denied")
605 where2 = where + domain + [('ressource_parent_type_id','=',False)]
606 ids = dirobj.search(cr, uid, where2, context=ctx)
608 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
609 klass = dirr.get_node_class(dirr, context=ctx)
610 res.append(klass(dirr.name, self, self.context,dirr))
612 # Static directories should never return files with res_model/res_id
613 # because static dirs are /never/ related to a record.
614 # In fact, files related to some model and parented by the root dir
615 # (the default), will NOT be accessible in the node system unless
616 # a resource folder for that model exists (with resource_find_all=True).
617 # Having resource attachments in a common folder is bad practice,
618 # because they would be visible to all users, and their names may be
619 # the same, conflicting.
620 where += [('res_model', '=', False)]
621 fil_obj = dirobj.pool.get('ir.attachment')
622 ids = fil_obj.search(cr, uid, where, context=ctx)
624 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
625 klass = self.context.node_file_class
626 res.append(klass(fil.name, self, self.context, fil))
630 uid = self.context.uid
631 directory = self.context._dirobj.browse(cr, uid, self.dir_id)
634 raise OSError(2, 'Not such file or directory.')
635 if not self.check_perms('u'):
636 raise IOError(errno.EPERM,"Permission denied")
638 if directory._table_name=='document.directory':
639 if self.children(cr):
640 raise OSError(39, 'Directory not empty.')
641 res = self.context._dirobj.unlink(cr, uid, [directory.id])
643 raise OSError(1, 'Operation not permited.')
646 def create_child_collection(self, cr, objname):
648 if not self.check_perms(2):
649 raise IOError(errno.EPERM,"Permission denied")
651 dirobj = self.context._dirobj
652 uid = self.context.uid
653 ctx = self.context.context.copy()
654 ctx.update(self.dctx)
655 obj = dirobj.browse(cr, uid, self.dir_id)
656 if obj and (obj.type == 'ressource') and not object2:
657 raise OSError(1, 'Operation not permited.')
662 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
663 'ressource_id': object2 and object2.id or False,
664 'parent_id' : obj and obj.id or False
667 return dirobj.create(cr, uid, val)
670 def create_child(self, cr, path, data=None):
671 """ API function to create a child file object and node
672 Return the node_* created
674 if not self.check_perms(2):
675 raise IOError(errno.EPERM,"Permission denied")
677 dirobj = self.context._dirobj
678 uid = self.context.uid
679 ctx = self.context.context.copy()
680 ctx.update(self.dctx)
681 fil_obj=dirobj.pool.get('ir.attachment')
685 'parent_id': self.dir_id,
686 # Datas are not set here
689 fil_id = fil_obj.create(cr, uid, val, context=ctx)
690 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
691 fnode = node_file(path, self, self.context, fil)
693 fnode.set_data(cr, data, fil)
696 def _get_ttag(self,cr):
697 return 'dir-%d' % self.dir_id
699 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
700 """ Move directory. This operation is simple, since the present node is
701 only used for static, simple directories.
702 Note /may/ be called with ndir_node = None, to rename the document root.
704 if ndir_node and (ndir_node.context != self.context):
705 raise NotImplementedError("Cannot move directories between contexts")
707 if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
708 raise IOError(errno.EPERM,"Permission denied")
710 dir_obj = self.context._dirobj
712 dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
715 assert dbro.id == self.dir_id
718 raise IndexError("Cannot locate dir %d", self.dir_id)
720 if (not self.parent) and ndir_node:
721 if not dbro.parent_id:
722 raise IOError(errno.EPERM, "Cannot move the root directory!")
723 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
726 if self.parent != ndir_node:
727 _logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
728 raise NotImplementedError('Cannot move dir to another dir')
731 if new_name and (new_name != dbro.name):
732 if ndir_node.child(cr, new_name):
733 raise IOError(errno.EEXIST, "Destination path already exists")
734 ret['name'] = new_name
739 # We have to update the data ourselves
741 ctx = self.context.context.copy()
742 ctx['__from_node'] = True
743 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
748 class node_res_dir(node_class):
749 """ A folder containing dynamic folders
750 A special sibling to node_dir, which does only contain dynamically
751 created folders foreach resource in the foreign model.
752 All folders should be of type node_res_obj and merely behave like
753 node_dirs (with limited domain).
755 our_type = 'collection'
757 def __init__(self, path, parent, context, dirr, dctx=None ):
758 super(node_res_dir,self).__init__(path, parent, context)
759 self.dir_id = dirr.id
760 #todo: more info from dirr
761 self.mimetype = 'application/x-directory'
762 # 'httpd/unix-directory'
763 self.create_date = dirr.create_date
764 # TODO: the write date should be MAX(file.write)..
765 self.write_date = dirr.write_date or dirr.create_date
766 self.content_length = 0
768 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
770 self.uuser = 'nobody'
771 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
772 self.uidperms = dirr.get_dir_permissions()
773 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
774 self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
775 self.resm_id = dirr.ressource_id
776 self.res_find_all = dirr.resource_find_all
777 self.namefield = dirr.resource_field.name or 'name'
778 self.displayname = dirr.name
779 # Important: the domain is evaluated using the *parent* dctx!
780 self.domain = dirr.domain
781 self.ressource_tree = dirr.ressource_tree
782 # and then, we add our own vars in the dctx:
784 self.dctx.update(dctx)
786 # and then, we prepare a dctx dict, for deferred evaluation:
788 for dfld in dirr.dctx_ids:
789 self.dctx_dict['dctx_' + dfld.field] = dfld.expr
791 def __eq__(self, other):
792 if type(self) != type(other):
794 if not self.context == other.context:
796 # Two nodes, for the same document.directory, may have a
797 # different context! (dynamic folders)
798 if self.dctx != other.dctx:
800 return self.dir_id == other.dir_id
802 def children(self, cr, domain=None):
803 return self._child_get(cr, domain=domain)
805 def child(self,cr, name, domain=None):
806 res = self._child_get(cr, name, domain=domain)
811 def _child_get(self, cr, name=None, domain=None):
812 """ return virtual children of resource, based on the
815 Note that many objects use NULL for a name, so we should
816 better call the name_search(),name_get() set of methods
818 obj = self.context._dirobj.pool.get(self.res_model)
821 dirobj = self.context._dirobj
822 uid = self.context.uid
823 ctx = self.context.context.copy()
824 ctx.update(self.dctx)
827 app = safe_eval(self.domain, ctx)
830 elif isinstance(app, list):
832 elif isinstance(app, tuple):
835 raise RuntimeError("incorrect domain expr: %s" % self.domain)
837 where.append(('id','=',self.resm_id))
840 # The =like character will match underscores against any characters
841 # including the special ones that couldn't exist in a FTP/DAV request
842 where.append((self.namefield,'=like',name.replace('\\','\\\\')))
843 is_allowed = self.check_perms(1)
845 is_allowed = self.check_perms(5)
848 raise IOError(errno.EPERM,"Permission denied")
850 # print "Where clause for %s" % self.res_model, where
851 if self.ressource_tree:
854 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
855 if obj._parent_name in obj.fields_get(cr, uid):
856 where.append((obj._parent_name,'=',object2 and object2.id or False))
858 resids = obj.search(cr, uid, where, context=ctx)
860 for bo in obj.browse(cr, uid, resids, context=ctx):
863 res_name = getattr(bo, self.namefield)
866 # Yes! we can't do better but skip nameless records.
868 # Escape the name for characters not supported in filenames
869 res_name = res_name.replace('/','_') # any other weird char?
871 if name and (res_name != ustr(name)):
872 # we have matched _ to any character, but we only meant to match
874 # Eg. 'a_c' will find 'abc', 'a/c', 'a_c', may only
875 # return 'a/c' and 'a_c'
878 res.append(self.res_obj_class(res_name, self.dir_id, self, self.context, self.res_model, bo))
881 def _get_ttag(self,cr):
882 return 'rdir-%d' % self.dir_id
884 class node_res_obj(node_class):
885 """ A dynamically created folder.
886 A special sibling to node_dir, which does only contain dynamically
887 created folders foreach resource in the foreign model.
888 All folders should be of type node_res_obj and merely behave like
889 node_dirs (with limited domain).
891 our_type = 'collection'
892 def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
893 super(node_res_obj,self).__init__(path, parent,context)
895 #todo: more info from dirr
897 self.mimetype = 'application/x-directory'
898 # 'httpd/unix-directory'
899 self.create_date = parent.create_date
900 # TODO: the write date should be MAX(file.write)..
901 self.write_date = parent.write_date
902 self.content_length = 0
903 self.uidperms = parent.uidperms & 15
904 self.unixperms = 040000 | _uid2unixperms(self.uidperms, True)
905 self.uuser = parent.uuser
906 self.ugroup = parent.ugroup
907 self.res_model = res_model
908 self.domain = parent.domain
909 self.displayname = path
910 self.dctx_dict = parent.dctx_dict
911 if isinstance(parent, node_res_dir):
912 self.res_find_all = parent.res_find_all
914 self.res_find_all = False
916 self.res_id = res_bo.id
917 dc2 = self.context.context.copy()
918 dc2.update(self.dctx)
919 dc2['res_model'] = res_model
920 dc2['res_id'] = res_bo.id
922 for fld,expr in self.dctx_dict.items():
924 self.dctx[fld] = safe_eval(expr, dc2)
926 print "Cannot eval %s for %s" % (expr, fld)
932 def __eq__(self, other):
933 if type(self) != type(other):
935 if not self.context == other.context:
937 if not self.res_model == other.res_model:
939 if not self.res_id == other.res_id:
941 if self.domain != other.domain:
943 if self.res_find_all != other.res_find_all:
945 if self.dctx != other.dctx:
947 return self.dir_id == other.dir_id
949 def children(self, cr, domain=None):
950 return self._child_get(cr, domain=domain) + self._file_get(cr)
952 def child(self, cr, name, domain=None):
953 res = self._child_get(cr, name, domain=domain)
956 res = self._file_get(cr, name)
961 def _file_get(self,cr, nodename=False):
963 is_allowed = self.check_perms((nodename and 1) or 5)
965 raise IOError(errno.EPERM,"Permission denied")
967 cntobj = self.context._dirobj.pool.get('document.directory.content')
968 uid = self.context.uid
969 ctx = self.context.context.copy()
970 ctx.update(self.dctx)
971 where = [('directory_id','=',self.dir_id) ]
973 # where.extend(self.domain)
974 # print "res_obj file_get clause", where
975 ids = cntobj.search(cr, uid, where, context=ctx)
976 for content in cntobj.browse(cr, uid, ids, context=ctx):
977 res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
983 def get_dav_props_DEPR(self, cr):
984 # Deprecated! (but document_ics must be cleaned, first)
986 cntobj = self.context._dirobj.pool.get('document.directory.content')
987 uid = self.context.uid
988 ctx = self.context.context.copy()
989 ctx.update(self.dctx)
990 where = [('directory_id','=',self.dir_id) ]
991 ids = cntobj.search(cr, uid, where, context=ctx)
992 for content in cntobj.browse(cr, uid, ids, context=ctx):
993 if content.extension == '.ics': # FIXME: call the content class!
994 res['http://groupdav.org/'] = ('resourcetype',)
997 def get_dav_eprop_DEPR(self, cr, ns, prop):
999 if ns != 'http://groupdav.org/' or prop != 'resourcetype':
1000 _logger.warning("Who asked for %s:%s?" % (ns, prop))
1002 cntobj = self.context._dirobj.pool.get('document.directory.content')
1003 uid = self.context.uid
1004 ctx = self.context.context.copy()
1005 ctx.update(self.dctx)
1006 where = [('directory_id','=',self.dir_id) ]
1007 ids = cntobj.search(cr,uid,where,context=ctx)
1008 for content in cntobj.browse(cr, uid, ids, context=ctx):
1009 # TODO: remove relic of GroupDAV
1010 if content.extension == '.ics': # FIXME: call the content class!
1011 return ('vevent-collection','http://groupdav.org/')
1014 def _child_get(self, cr, name=None, domain=None):
1015 dirobj = self.context._dirobj
1017 is_allowed = self.check_perms((name and 1) or 5)
1019 raise IOError(errno.EPERM,"Permission denied")
1021 uid = self.context.uid
1022 ctx = self.context.context.copy()
1023 ctx.update(self.dctx)
1024 directory = dirobj.browse(cr, uid, self.dir_id)
1025 obj = dirobj.pool.get(self.res_model)
1029 where.append(('name','=',name))
1031 # Directory Structure display in tree structure
1032 if self.res_id and directory.ressource_tree:
1035 where1.append(('name','=like',name.replace('\\','\\\\')))
1036 if obj._parent_name in obj.fields_get(cr, uid):
1037 where1.append((obj._parent_name, '=', self.res_id))
1038 namefield = directory.resource_field.name or 'name'
1039 resids = obj.search(cr, uid, where1, context=ctx)
1040 for bo in obj.browse(cr, uid, resids, context=ctx):
1043 res_name = getattr(bo, namefield)
1046 res_name = res_name.replace('/', '_')
1047 if name and (res_name != ustr(name)):
1050 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
1051 rnode = klass(res_name, dir_id=self.dir_id, parent=self, context=self.context,
1052 res_model=self.res_model, res_bo=bo)
1053 rnode.res_find_all = self.res_find_all
1057 where2 = where + [('parent_id','=',self.dir_id) ]
1058 ids = dirobj.search(cr, uid, where2, context=ctx)
1059 bo = obj.browse(cr, uid, self.res_id, context=ctx)
1061 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
1062 if name and (name != dirr.name):
1064 if dirr.type == 'directory':
1065 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1066 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id))
1067 elif dirr.type == 'ressource':
1068 # child resources can be controlled by properly set dctx
1069 klass = dirr.get_node_class(dirr, context=ctx)
1070 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id})) # bo?
1072 fil_obj = dirobj.pool.get('ir.attachment')
1073 if self.res_find_all:
1075 where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
1076 # print "where clause for dir_obj", where3
1077 ids = fil_obj.search(cr, uid, where3, context=ctx)
1079 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
1080 klass = self.context.node_file_class
1081 res.append(klass(fil.name, self, self.context, fil))
1084 # Get Child Ressource Directories
1085 if directory.ressource_type_id and directory.ressource_type_id.id:
1086 where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
1087 where5 = where4 + ['|', ('ressource_id','=',0), ('ressource_id','=',self.res_id)]
1088 dirids = dirobj.search(cr,uid, where5)
1089 for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1090 if dirr.type == 'directory' and not dirr.parent_id:
1091 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1092 rnode = klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = bo, res_id = self.res_id)
1093 rnode.res_find_all = dirr.resource_find_all
1095 if dirr.type == 'ressource':
1096 klass = dirr.get_node_class(dirr, context=ctx)
1097 rnode = klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id})
1098 rnode.res_find_all = dirr.resource_find_all
1102 def create_child_collection(self, cr, objname):
1103 dirobj = self.context._dirobj
1104 is_allowed = self.check_perms(2)
1106 raise IOError(errno.EPERM,"Permission denied")
1108 uid = self.context.uid
1109 ctx = self.context.context.copy()
1110 ctx.update(self.dctx)
1111 res_obj = dirobj.pool.get(self.res_model)
1113 object2 = res_obj.browse(cr, uid, self.res_id) or False
1115 obj = dirobj.browse(cr, uid, self.dir_id)
1116 if obj and (obj.type == 'ressource') and not object2:
1117 raise OSError(1, 'Operation not permited.')
1122 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1123 'ressource_id': object2 and object2.id or False,
1124 'parent_id' : False,
1125 'resource_find_all': False,
1127 if (obj and (obj.type in ('directory'))) or not object2:
1128 val['parent_id'] = obj and obj.id or False
1130 return dirobj.create(cr, uid, val)
1132 def create_child(self, cr, path, data=None):
1133 """ API function to create a child file object and node
1134 Return the node_* created
1136 is_allowed = self.check_perms(2)
1138 raise IOError(errno.EPERM,"Permission denied")
1140 dirobj = self.context._dirobj
1141 uid = self.context.uid
1142 ctx = self.context.context.copy()
1143 ctx.update(self.dctx)
1144 fil_obj=dirobj.pool.get('ir.attachment')
1147 'datas_fname': path,
1148 'res_model': self.res_model,
1149 'res_id': self.res_id,
1150 # Datas are not set here
1152 if not self.res_find_all:
1153 val['parent_id'] = self.dir_id
1155 fil_id = fil_obj.create(cr, uid, val, context=ctx)
1156 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1157 klass = self.context.node_file_class
1158 fnode = klass(path, self, self.context, fil)
1159 if data is not None:
1160 fnode.set_data(cr, data, fil)
1163 def _get_ttag(self,cr):
1164 return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1166 node_res_dir.res_obj_class = node_res_obj
1168 class node_file(node_class):
1170 def __init__(self, path, parent, context, fil):
1171 super(node_file,self).__init__(path, parent,context)
1172 self.file_id = fil.id
1173 #todo: more info from ir_attachment
1174 if fil.file_type and '/' in fil.file_type:
1175 self.mimetype = str(fil.file_type)
1176 self.create_date = fil.create_date
1177 self.write_date = fil.write_date or fil.create_date
1178 self.content_length = fil.file_size
1179 self.displayname = fil.name
1183 if not parent.check_perms('x'):
1185 elif not parent.check_perms('w'):
1189 self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1191 self.uuser = 'nobody'
1192 self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1194 # This only propagates the problem to get_data. Better
1195 # fix those files to point to the root dir.
1196 self.storage_id = None
1199 if par.storage_id and par.storage_id.id:
1200 self.storage_id = par.storage_id.id
1204 def __eq__(self, other):
1205 if type(self) != type(other):
1207 if not self.context == other.context:
1209 if self.dctx != other.dctx:
1211 return self.file_id == other.file_id
1214 def open_data(self, cr, mode):
1215 stor = self.storage_id
1216 assert stor, "No storage for file #%s" % self.file_id
1217 if not self.check_perms(4):
1218 raise IOError(errno.EPERM, "Permission denied")
1220 # If storage is not set properly, we are just screwed here, don't
1221 # try to get it from default.
1222 stobj = self.context._dirobj.pool.get('document.storage')
1223 return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1226 uid = self.context.uid
1227 if not self.check_perms(8):
1228 raise IOError(errno.EPERM, "Permission denied")
1229 document_obj = self.context._dirobj.pool.get('ir.attachment')
1230 if self.type in ('collection','database'):
1232 document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1234 if document and document._table_name == 'ir.attachment':
1235 res = document_obj.unlink(cr, uid, [document.id])
1238 def fix_ppath(self, cr, fbro):
1239 """Sometimes we may init this w/o path, parent.
1240 This function fills the missing path from the file browse object
1242 Note: this may be an expensive operation, do on demand. However,
1243 once caching is in, we might want to do that at init time and keep
1246 if self.path or self.parent:
1249 uid = self.context.uid
1253 dirobj = self.context._dirobj.pool.get('document.directory')
1254 dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1255 if fbro.datas_fname:
1256 dirpath.append(fbro.datas_fname)
1258 dirpath.append(fbro.name)
1263 self.path = dirpath[0]
1265 def get_data(self, cr, fil_obj = None):
1266 """ Retrieve the data for some file.
1267 fil_obj may optionally be specified, and should be a browse object
1268 for the file. This is useful when the caller has already initiated
1269 the browse object. """
1270 # this is where storage kicks in..
1271 stor = self.storage_id
1272 assert stor, "No storage for file #%s" % self.file_id
1273 if not self.check_perms(4):
1274 raise IOError(errno.EPERM, "Permission denied")
1276 # If storage is not set properly, we are just screwed here, don't
1277 # try to get it from default.
1278 stobj = self.context._dirobj.pool.get('document.storage')
1279 return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1281 def get_data_len(self, cr, fil_obj = None):
1282 # TODO: verify with the storage object!
1283 bin_size = self.context.context.get('bin_size', False)
1284 if bin_size and not self.content_length:
1285 self.content_length = fil_obj.db_datas
1286 return self.content_length
1288 def set_data(self, cr, data, fil_obj = None):
1289 """ Store data at some file.
1290 fil_obj may optionally be specified, and should be a browse object
1291 for the file. This is useful when the caller has already initiated
1292 the browse object. """
1293 # this is where storage kicks in..
1294 stor = self.storage_id
1295 assert stor, "No storage for file #%s" % self.file_id
1296 if not self.check_perms(2):
1297 raise IOError(errno.EPERM, "Permission denied")
1299 stobj = self.context._dirobj.pool.get('document.storage')
1300 return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1302 def _get_ttag(self,cr):
1303 return 'file-%d' % self.file_id
1305 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1306 if ndir_node and ndir_node.context != self.context:
1307 raise NotImplementedError("Cannot move files between contexts")
1309 if (not self.check_perms(8)) and ndir_node.check_perms(2):
1310 raise IOError(errno.EPERM, "Permission denied")
1312 doc_obj = self.context._dirobj.pool.get('ir.attachment')
1314 dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1317 assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1320 raise IndexError("Cannot locate doc %d", self.file_id)
1322 if (not self.parent):
1323 # there *must* be a parent node for this one
1324 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1328 if ndir_node and self.parent != ndir_node:
1329 if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1330 _logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1331 raise NotImplementedError('Cannot move files between dynamic folders')
1334 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1335 ndir_node.dir_id, context=self.context.context)
1337 assert ndir_obj.id == ndir_node.dir_id
1339 stobj = self.context._dirobj.pool.get('document.storage')
1340 r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1341 context=self.context.context)
1344 if new_name and (new_name != dbro.name):
1346 raise NotImplementedError("Cannot rename and move") # TODO
1347 stobj = self.context._dirobj.pool.get('document.storage')
1348 r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1354 # We have to update the data ourselves
1356 ctx = self.context.context.copy()
1357 ctx['__from_node'] = True
1358 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1363 class node_content(node_class):
1364 our_type = 'content'
1365 def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1366 super(node_content,self).__init__(path, parent,context)
1367 self.cnt_id = cnt.id
1368 self.create_date = False
1369 self.write_date = False
1370 self.content_length = False
1371 self.unixperms = 0640
1373 self.uidperms = parent.uidperms & 14
1374 self.uuser = parent.uuser
1375 self.ugroup = parent.ugroup
1377 self.extension = cnt.extension
1378 self.report_id = cnt.report_id and cnt.report_id.id
1379 #self.mimetype = cnt.extension.
1380 self.displayname = path
1382 self.dctx.update(dctx)
1383 self.act_id = act_id
1385 def fill_fields(self, cr, dctx = None):
1386 """ Try to read the object and fill missing fields, like mimetype,
1388 This function must be different from the constructor, because
1389 it uses the db cursor.
1392 cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1395 if res and res[0][0]:
1396 self.mimetype = str(res[0][0])
1399 def get_data(self, cr, fil_obj = None):
1400 cntobj = self.context._dirobj.pool.get('document.directory.content')
1401 if not self.check_perms(4):
1402 raise IOError(errno.EPERM, "Permission denied")
1404 ctx = self.context.context.copy()
1405 ctx.update(self.dctx)
1406 data = cntobj.process_read(cr, self.context.uid, self, ctx)
1408 self.content_length = len(data)
1411 def open_data(self, cr, mode):
1412 if mode.endswith('b'):
1414 if mode in ('r', 'w'):
1416 elif mode in ('r+', 'w+'):
1419 raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1421 if not self.check_perms(cperms):
1422 raise IOError(errno.EPERM, "Permission denied")
1424 ctx = self.context.context.copy()
1425 ctx.update(self.dctx)
1427 return nodefd_content(self, cr, mode, ctx)
1429 def get_data_len(self, cr, fil_obj = None):
1430 # FIXME : here, we actually generate the content twice!!
1431 # we should have cached the generated content, but it is
1432 # not advisable to do keep it in memory, until we have a cache
1434 if not self.content_length:
1435 self.get_data(cr,fil_obj)
1436 return self.content_length
1438 def set_data(self, cr, data, fil_obj = None):
1439 cntobj = self.context._dirobj.pool.get('document.directory.content')
1440 if not self.check_perms(2):
1441 raise IOError(errno.EPERM, "Permission denied")
1443 ctx = self.context.context.copy()
1444 ctx.update(self.dctx)
1445 return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1447 def _get_ttag(self,cr):
1448 return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1450 def get_dav_resourcetype(self, cr):
1453 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 _logger.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 _logger.exception('Cannot update db content #%d for close:', par.cnt_id)
1506 StringIO.close(self)
1508 class nodefd_static(StringIO, node_descriptor):
1510 """ A descriptor to nodes with static data.
1512 def __init__(self, parent, cr, mode, ctx=None):
1513 node_descriptor.__init__(self, parent)
1517 if mode in ('r', 'r+'):
1518 data = parent.get_data(cr)
1520 self._size = len(data)
1521 parent.content_length = len(data)
1522 StringIO.__init__(self, data)
1523 elif mode in ('w', 'w+'):
1524 StringIO.__init__(self, None)
1525 # at write, we start at 0 (= overwrite), but have the original
1526 # data available, in case of a seek()
1528 StringIO.__init__(self, None)
1530 _logger.error("Incorrect mode %s specified", mode)
1531 raise IOError(errno.EINVAL, "Invalid file mode")
1538 # we now open a *separate* cursor, to update the data.
1539 # FIXME: this may be improved, for concurrency handling
1540 if self.mode == 'r':
1541 StringIO.close(self)
1544 par = self._get_parent()
1545 # uid = par.context.uid
1546 cr = pooler.get_db(par.context.dbname).cursor()
1548 if self.mode in ('w', 'w+', 'r+'):
1549 data = self.getvalue()
1550 par.set_data(cr, data)
1551 elif self.mode == 'a':
1552 raise NotImplementedError
1555 _logger.exception('Cannot update db content #%d for close:', par.cnt_id)
1559 StringIO.close(self)
1562 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: