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
31 from StringIO import StringIO
34 # An object that represent an uri
35 # path: the uri of the object
36 # content: the Content it belongs to (_print.pdf)
37 # type: content or collection
38 # content: objct = res.partner
39 # collection: object = directory, object2 = res.partner
40 # file: objct = ir.attachement
41 # root: if we are at the first directory of a ressource
44 logger = logging.getLogger('doc2.nodes')
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
178 class node_class(object):
179 """ this is a superclass for our inodes
180 It is an API for all code that wants to access the document files.
181 Nodes have attributes which contain usual file properties
183 our_type = 'baseclass'
187 def __init__(self, path, parent, context):
188 assert isinstance(context,node_context)
189 assert (not parent ) or isinstance(parent,node_class)
191 self.context = context
192 self.type=self.our_type
194 self.uidperms = 5 # computed permissions for our uid, in unix bits
195 self.mimetype = 'application/octet-stream'
196 self.create_date = None
197 self.write_date = None
198 self.unixperms = 0660
200 self.ugroup = 'group'
201 self.content_length = 0
205 self.dctx = parent.dctx.copy()
206 self.displayname = 'Object'
208 def __eq__(self, other):
209 return NotImplemented
211 def __ne__(self, other):
212 return not self.__eq__(other)
215 """ Return the components of the full path for some
217 The returned list only contains the names of nodes.
220 s = self.parent.full_path()
223 if isinstance(self.path,list):
225 elif self.path is None:
229 return s #map(lambda x: '/' +x, s)
232 return "%s@/%s" % (self.our_type, '/'.join(self.full_path()))
234 def children(self, cr, domain=None):
235 print "node_class.children()"
238 def child(self,cr, name, domain=None):
239 print "node_class.child()"
242 def get_uri(self, cr, uri):
246 ndir = ndir.child(cr, duri[0])
253 print "node_class.path_get()"
256 def get_data(self,cr):
257 raise TypeError('no data for %s'% self.type)
259 def open_data(self, cr, mode):
260 """ Open a node_descriptor object for this node.
262 @param the mode of open, eg 'r', 'w', 'a', like file.open()
264 This operation may lock the data for this node (and accross
265 other node hierarchies), until the descriptor is close()d. If
266 the node is locked, subsequent opens (depending on mode) may
267 immediately fail with an exception (which?).
268 For this class, there is no data, so no implementation. Each
269 child class that has data should override this.
271 raise TypeError('no data for %s' % self.type)
273 def _get_storage(self,cr):
274 raise RuntimeError("no storage for base class")
276 def get_etag(self,cr):
277 """ Get a tag, unique per object + modification.
279 see. http://tools.ietf.org/html/rfc2616#section-13.3.3 """
280 return '"%s-%s"' % (self._get_ttag(cr), self._get_wtag(cr))
282 def _get_wtag(self, cr):
283 """ Return the modification time as a unique, compact string """
284 return str(_str2time(self.write_date)).replace('.','')
286 def _get_ttag(self, cr):
287 """ Get a unique tag for this type/id of object.
288 Must be overriden, so that each node is uniquely identified.
290 print "node_class.get_ttag()",self
291 raise NotImplementedError("get_ttag stub()")
293 def get_dav_props(self, cr):
294 """ If this class has special behaviour for GroupDAV etc, export
296 # This fn is placed here rather than WebDAV, because we want the
297 # baseclass methods to apply to all node subclasses
298 return self.DAV_PROPS or {}
300 def match_dav_eprop(self, cr, match, ns, prop):
301 res = self.get_dav_eprop(cr, ns, prop)
306 def get_dav_eprop(self, cr, ns, prop):
307 if not self.DAV_M_NS:
310 if self.DAV_M_NS.has_key(ns):
311 prefix = self.DAV_M_NS[ns]
313 logger.debug('No namespace: %s ("%s")',ns, prop)
316 mname = prefix + "_" + prop.replace('-','_')
318 if not hasattr(self, mname):
322 m = getattr(self, mname)
325 except AttributeError:
326 logger.debug('Property %s not supported' % prop, exc_info=True)
329 def get_dav_resourcetype(self, cr):
330 """ Get the DAV resource type.
332 Is here because some nodes may exhibit special behaviour, like
333 CalDAV/GroupDAV collections
335 raise NotImplementedError
337 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
338 """ Move this node to a new parent directory.
339 @param ndir_node the collection that this node should be moved under
340 @param new_name a name to rename this node to. If omitted, the old
342 @param fil_obj, can be None, is the browse object for the file,
343 if already available.
344 @param ndir_obj must be the browse object to the new doc.directory
345 location, where this node should be moved to.
346 in_write: When called by write(), we shouldn't attempt to write the
347 object, but instead return the dict of vals (avoid re-entrance).
348 If false, we should write all data to the object, here, as if the
349 caller won't do anything after calling move_to()
352 True: the node is moved, the caller can update other values, too.
353 False: the node is either removed or fully updated, the caller
354 must discard the fil_obj, not attempt to write any more to it.
355 dict: values to write back to the object. *May* contain a new id!
357 Depending on src and target storage, implementations of this function
358 could do various things.
359 Should also consider node<->content, dir<->dir moves etc.
361 Move operations, as instructed from APIs (eg. request from DAV) could
364 raise NotImplementedError(repr(self))
366 def create_child(self, cr, path, data=None):
367 """ Create a regular file under this node
369 raise NotImplementedError(repr(self))
371 def create_child_collection(self, cr, objname):
372 """ Create a child collection (directory) under self
374 raise NotImplementedError(repr(self))
377 raise NotImplementedError(repr(self))
380 raise NotImplementedError(repr(self))
382 def get_domain(self, cr, filters):
386 def check_perms(self, perms):
387 """ Check the permissions of the current node.
389 @param perms either an integers of the bits to check, or
390 a string with the permission letters
392 Permissions of nodes are (in a unix way):
393 1, x : allow descend into dir
394 2, w : allow write into file, or modification to dir
395 4, r : allow read of file, or listing of dir contents
396 8, u : allow remove (unlink)
399 if isinstance(perms, str):
401 chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
405 elif isinstance(perms, int):
406 if perms < 0 or perms > 15:
407 raise ValueError("Invalid permission bits")
409 raise ValueError("Invalid permission attribute")
411 return ((self.uidperms & perms) == perms)
413 class node_database(node_class):
414 """ A node representing the database directory
417 our_type = 'database'
418 def __init__(self, path=[], parent=False, context=None):
419 super(node_database,self).__init__(path, parent, context)
420 self.unixperms = 040750
423 def children(self, cr, domain=None):
424 res = self._child_get(cr, domain=domain) + self._file_get(cr)
427 def child(self, cr, name, domain=None):
428 res = self._child_get(cr, name, domain=None)
431 res = self._file_get(cr,name)
436 def _child_get(self, cr, name=False, parent_id=False, domain=None):
437 dirobj = self.context._dirobj
438 uid = self.context.uid
439 ctx = self.context.context.copy()
440 ctx.update(self.dctx)
441 where = [('parent_id','=',parent_id)]
443 where.append(('name','=',name))
444 is_allowed = self.check_perms(1)
446 is_allowed = self.check_perms(5)
449 raise IOError(errno.EPERM, "Permission into directory denied")
454 where2 = where + domain + ['|', ('type', '=', 'directory'), \
455 '&', ('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)]
456 ids = dirobj.search(cr, uid, where2, context=ctx)
458 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
459 klass = dirr.get_node_class(dirr, context=ctx)
460 res.append(klass(dirr.name, self, self.context,dirr))
462 fil_obj = dirobj.pool.get('ir.attachment')
463 ids = fil_obj.search(cr, uid, where, context=ctx)
465 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
466 klass = self.context.node_file_class
467 res.append(klass(fil.name, self, self.context, fil))
470 def _file_get(self,cr, nodename=False):
474 def _get_ttag(self,cr):
475 return 'db-%s' % cr.dbname
477 def mkdosname(company_name, default='noname'):
478 """ convert a string to a dos-like name"""
481 badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
483 for c in company_name[:8]:
484 n += (c in badchars and '_') or c
488 class node_dir(node_database):
489 our_type = 'collection'
490 def __init__(self, path, parent, context, dirr, dctx=None):
491 super(node_dir,self).__init__(path, parent,context)
492 self.dir_id = dirr and dirr.id or False
493 #todo: more info from dirr
494 self.mimetype = 'application/x-directory'
495 # 'httpd/unix-directory'
496 self.create_date = dirr and dirr.create_date or False
497 self.domain = dirr and dirr.domain or []
498 self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
499 # TODO: the write date should be MAX(file.write)..
500 self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
501 self.content_length = 0
502 self.unixperms = 040750
504 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
506 self.uuser = 'nobody'
507 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
508 self.uidperms = dirr.get_dir_permissions()
510 self.dctx.update(dctx)
511 dc2 = self.context.context
512 dc2.update(self.dctx)
513 dc2['dir_id'] = self.dir_id
514 self.displayname = dirr and dirr.name or False
515 if dirr and dirr.dctx_ids:
516 for dfld in dirr.dctx_ids:
518 self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
520 print "Cannot eval %s" % dfld.expr
524 def __eq__(self, other):
525 if type(self) != type(other):
527 if not self.context == other.context:
529 # Two directory nodes, for the same document.directory, may have a
530 # different context! (dynamic folders)
531 if self.dctx != other.dctx:
533 return self.dir_id == other.dir_id
535 def get_data(self, cr):
537 #for child in self.children(cr):
538 # res += child.get_data(cr)
541 def _file_get(self, cr, nodename=False):
542 res = super(node_dir,self)._file_get(cr, nodename)
544 is_allowed = self.check_perms(nodename and 1 or 5)
546 raise IOError(errno.EPERM, "Permission into directory denied")
548 cntobj = self.context._dirobj.pool.get('document.directory.content')
549 uid = self.context.uid
550 ctx = self.context.context.copy()
551 ctx.update(self.dctx)
552 where = [('directory_id','=',self.dir_id) ]
553 ids = cntobj.search(cr, uid, where, context=ctx)
554 for content in cntobj.browse(cr, uid, ids, context=ctx):
555 res3 = cntobj._file_get(cr, self, nodename, content)
561 def _child_get(self, cr, name=None, domain=None):
562 return super(node_dir,self)._child_get(cr, name, self.dir_id, domain=domain)
565 uid = self.context.uid
566 directory = self.context._dirobj.browse(cr, uid, self.dir_id)
569 raise OSError(2, 'Not such file or directory.')
570 if not self.check_perms('u'):
571 raise IOError(errno.EPERM,"Permission denied")
573 if directory._table_name=='document.directory':
574 if self.children(cr):
575 raise OSError(39, 'Directory not empty.')
576 res = self.context._dirobj.unlink(cr, uid, [directory.id])
578 raise OSError(1, 'Operation not permited.')
581 def create_child_collection(self, cr, objname):
583 if not self.check_perms(2):
584 raise IOError(errno.EPERM,"Permission denied")
586 dirobj = self.context._dirobj
587 uid = self.context.uid
588 ctx = self.context.context.copy()
589 ctx.update(self.dctx)
590 obj = dirobj.browse(cr, uid, self.dir_id)
591 if obj and (obj.type == 'ressource') and not object2:
592 raise OSError(1, 'Operation not permited.')
597 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
598 'ressource_id': object2 and object2.id or False,
599 'parent_id' : obj and obj.id or False
602 return dirobj.create(cr, uid, val)
605 def create_child(self, cr, path, data=None):
606 """ API function to create a child file object and node
607 Return the node_* created
609 if not self.check_perms(2):
610 raise IOError(errno.EPERM,"Permission denied")
612 dirobj = self.context._dirobj
613 uid = self.context.uid
614 ctx = self.context.context.copy()
615 ctx.update(self.dctx)
616 fil_obj=dirobj.pool.get('ir.attachment')
620 'parent_id': self.dir_id,
621 # Datas are not set here
624 fil_id = fil_obj.create(cr, uid, val, context=ctx)
625 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
626 fnode = node_file(path, self, self.context, fil)
628 fnode.set_data(cr, data, fil)
631 def _get_ttag(self,cr):
632 return 'dir-%d' % self.dir_id
634 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
635 """ Move directory. This operation is simple, since the present node is
636 only used for static, simple directories.
637 Note /may/ be called with ndir_node = None, to rename the document root.
639 if ndir_node and (ndir_node.context != self.context):
640 raise NotImplementedError("Cannot move directories between contexts")
642 if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
643 raise IOError(errno.EPERM,"Permission denied")
645 dir_obj = self.context._dirobj
647 dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
650 assert dbro.id == self.dir_id
653 raise IndexError("Cannot locate dir %d", self.dir_id)
655 if (not self.parent) and ndir_node:
656 if not dbro.parent_id:
657 raise IOError(errno.EPERM, "Cannot move the root directory!")
658 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
661 if self.parent != ndir_node:
662 logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
663 raise NotImplementedError('Cannot move dir to another dir')
666 if new_name and (new_name != dbro.name):
667 if ndir_node.child(cr, new_name):
668 raise IOError(errno.EEXIST, "Destination path already exists")
669 ret['name'] = new_name
674 # We have to update the data ourselves
676 ctx = self.context.context.copy()
677 ctx['__from_node'] = True
678 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
683 class node_res_dir(node_class):
684 """ A folder containing dynamic folders
685 A special sibling to node_dir, which does only contain dynamically
686 created folders foreach resource in the foreign model.
687 All folders should be of type node_res_obj and merely behave like
688 node_dirs (with limited domain).
690 our_type = 'collection'
692 def __init__(self, path, parent, context, dirr, dctx=None ):
693 super(node_res_dir,self).__init__(path, parent, context)
694 self.dir_id = dirr.id
695 #todo: more info from dirr
696 self.mimetype = 'application/x-directory'
697 # 'httpd/unix-directory'
698 self.create_date = dirr.create_date
699 # TODO: the write date should be MAX(file.write)..
700 self.write_date = dirr.write_date or dirr.create_date
701 self.content_length = 0
702 self.unixperms = 040750
704 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
706 self.uuser = 'nobody'
707 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
708 self.uidperms = dirr.get_dir_permissions()
709 self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
710 self.resm_id = dirr.ressource_id
711 self.res_find_all = dirr.resource_find_all
712 self.namefield = dirr.resource_field.name or 'name'
713 self.displayname = dirr.name
714 # Important: the domain is evaluated using the *parent* dctx!
715 self.domain = dirr.domain
716 self.ressource_tree = dirr.ressource_tree
717 # and then, we add our own vars in the dctx:
719 self.dctx.update(dctx)
721 # and then, we prepare a dctx dict, for deferred evaluation:
723 for dfld in dirr.dctx_ids:
724 self.dctx_dict['dctx_' + dfld.field] = dfld.expr
726 def __eq__(self, other):
727 if type(self) != type(other):
729 if not self.context == other.context:
731 # Two nodes, for the same document.directory, may have a
732 # different context! (dynamic folders)
733 if self.dctx != other.dctx:
735 return self.dir_id == other.dir_id
737 def children(self, cr, domain=None):
738 return self._child_get(cr, domain=domain)
740 def child(self,cr, name, domain=None):
741 res = self._child_get(cr, name, domain=domain)
746 def _child_get(self, cr, name = None, domain=None):
747 """ return virtual children of resource, based on the
750 Note that many objects use NULL for a name, so we should
751 better call the name_search(),name_get() set of methods
753 obj = self.context._dirobj.pool.get(self.res_model)
756 dirobj = self.context._dirobj
757 uid = self.context.uid
758 ctx = self.context.context.copy()
759 ctx.update(self.dctx)
762 app = safe_eval(self.domain, self.dctx)
765 elif isinstance(app, list):
767 elif isinstance(app, tuple):
770 raise RuntimeError("incorrect domain expr: %s" % self.domain)
772 where.append(('id','=',self.resm_id))
775 where.append((self.namefield,'=',name))
776 is_allowed = self.check_perms(1)
778 is_allowed = self.check_perms(5)
781 raise IOError(errno.EPERM,"Permission denied")
783 # print "Where clause for %s" % self.res_model, where
784 if self.ressource_tree:
787 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
788 if obj._parent_name in obj.fields_get(cr, uid):
789 where.append((obj._parent_name,'=',object2 and object2.id or False))
791 resids = obj.search(cr, uid, where, context=ctx)
793 for bo in obj.browse(cr, uid, resids, context=ctx):
796 name = getattr(bo, self.namefield)
799 # Yes! we can't do better but skip nameless records.
801 res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
804 def _get_ttag(self,cr):
805 return 'rdir-%d' % self.dir_id
807 class node_res_obj(node_class):
808 """ A dynamically created folder.
809 A special sibling to node_dir, which does only contain dynamically
810 created folders foreach resource in the foreign model.
811 All folders should be of type node_res_obj and merely behave like
812 node_dirs (with limited domain).
814 our_type = 'collection'
815 def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
816 super(node_res_obj,self).__init__(path, parent,context)
818 #todo: more info from dirr
820 self.mimetype = 'application/x-directory'
821 # 'httpd/unix-directory'
822 self.create_date = parent.create_date
823 # TODO: the write date should be MAX(file.write)..
824 self.write_date = parent.write_date
825 self.content_length = 0
826 self.unixperms = 040750
827 self.uidperms = parent.uidperms & 15
828 self.uuser = parent.uuser
829 self.ugroup = parent.ugroup
830 self.res_model = res_model
831 self.domain = parent.domain
832 self.displayname = path
833 self.dctx_dict = parent.dctx_dict
834 self.res_find_all = parent.res_find_all
836 self.res_id = res_bo.id
837 dc2 = self.context.context.copy()
838 dc2.update(self.dctx)
839 dc2['res_model'] = res_model
840 dc2['res_id'] = res_bo.id
842 for fld,expr in self.dctx_dict.items():
844 self.dctx[fld] = safe_eval(expr, dc2)
846 print "Cannot eval %s for %s" % (expr, fld)
852 def __eq__(self, other):
853 if type(self) != type(other):
855 if not self.context == other.context:
857 if not self.res_model == other.res_model:
859 if not self.res_id == other.res_id:
861 if self.domain != other.domain:
863 if self.res_find_all != other.res_find_all:
865 if self.dctx != other.dctx:
867 return self.dir_id == other.dir_id
869 def children(self, cr, domain=None):
870 return self._child_get(cr, domain=domain) + self._file_get(cr)
872 def child(self, cr, name, domain=None):
873 res = self._child_get(cr, name, domain=domain)
876 res = self._file_get(cr, name)
881 def _file_get(self,cr, nodename=False):
883 is_allowed = self.check_perms((nodename and 1) or 5)
885 raise IOError(errno.EPERM,"Permission denied")
887 cntobj = self.context._dirobj.pool.get('document.directory.content')
888 uid = self.context.uid
889 ctx = self.context.context.copy()
890 ctx.update(self.dctx)
891 where = [('directory_id','=',self.dir_id) ]
893 # where.extend(self.domain)
894 # print "res_obj file_get clause", where
895 ids = cntobj.search(cr, uid, where, context=ctx)
896 for content in cntobj.browse(cr, uid, ids, context=ctx):
897 res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
903 def get_dav_props_DEPR(self, cr):
904 # Deprecated! (but document_ics must be cleaned, first)
906 cntobj = self.context._dirobj.pool.get('document.directory.content')
907 uid = self.context.uid
908 ctx = self.context.context.copy()
909 ctx.update(self.dctx)
910 where = [('directory_id','=',self.dir_id) ]
911 ids = cntobj.search(cr, uid, where, context=ctx)
912 for content in cntobj.browse(cr, uid, ids, context=ctx):
913 if content.extension == '.ics': # FIXME: call the content class!
914 res['http://groupdav.org/'] = ('resourcetype',)
917 def get_dav_eprop_DEPR(self, cr, ns, prop):
919 if ns != 'http://groupdav.org/' or prop != 'resourcetype':
920 logger.warning("Who asked for %s:%s?" % (ns, prop))
922 cntobj = self.context._dirobj.pool.get('document.directory.content')
923 uid = self.context.uid
924 ctx = self.context.context.copy()
925 ctx.update(self.dctx)
926 where = [('directory_id','=',self.dir_id) ]
927 ids = cntobj.search(cr,uid,where,context=ctx)
928 for content in cntobj.browse(cr, uid, ids, context=ctx):
929 # TODO: remove relic of GroupDAV
930 if content.extension == '.ics': # FIXME: call the content class!
931 return ('vevent-collection','http://groupdav.org/')
934 def _child_get(self, cr, name=None, domain=None):
935 dirobj = self.context._dirobj
937 is_allowed = self.check_perms((name and 1) or 5)
939 raise IOError(errno.EPERM,"Permission denied")
941 uid = self.context.uid
942 ctx = self.context.context.copy()
943 ctx.update(self.dctx)
944 directory = dirobj.browse(cr, uid, self.dir_id)
945 obj = dirobj.pool.get(self.res_model)
949 where.append(('name','=',name))
951 # Directory Structure display in tree structure
952 if self.res_id and directory.ressource_tree:
954 if obj._parent_name in obj.fields_get(cr, uid):
955 where1 = where + [(obj._parent_name, '=', self.res_id)]
956 resids = obj.search(cr, uid, where1, context=ctx)
957 for bo in obj.browse(cr, uid, resids, context=ctx):
958 namefield = directory.resource_field.name or 'name'
961 res_name = getattr(bo, namefield)
965 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
966 res.append(klass(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo))
969 where2 = where + [('parent_id','=',self.dir_id) ]
970 ids = dirobj.search(cr, uid, where2, context=ctx)
971 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
972 if dirr.type == 'directory':
973 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
974 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
975 elif dirr.type == 'ressource':
976 # child resources can be controlled by properly set dctx
977 klass = dirr.get_node_class(dirr, context=ctx)
978 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
980 fil_obj = dirobj.pool.get('ir.attachment')
981 if self.res_find_all:
983 where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
984 # print "where clause for dir_obj", where2
985 ids = fil_obj.search(cr, uid, where3, context=ctx)
987 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
988 klass = self.context.node_file_class
989 res.append(klass(fil.name, self, self.context, fil))
992 # Get Child Ressource Directories
993 if directory.ressource_type_id and directory.ressource_type_id.id:
994 where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
995 where5 = where4 + [('ressource_id','=',0)]
996 dirids = dirobj.search(cr,uid, where5)
997 where5 = where4 + [('ressource_id','=',self.res_id)]
998 dirids = dirids + dirobj.search(cr,uid, where5)
999 for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1000 if dirr.type == 'directory' and not dirr.parent_id:
1001 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1002 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1003 if dirr.type == 'ressource':
1004 klass = dirr.get_node_class(dirr, context=ctx)
1005 res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
1008 def create_child_collection(self, cr, objname):
1009 dirobj = self.context._dirobj
1010 is_allowed = self.check_perms(2)
1012 raise IOError(errno.EPERM,"Permission denied")
1014 uid = self.context.uid
1015 ctx = self.context.context.copy()
1016 ctx.update(self.dctx)
1017 res_obj = dirobj.pool.get(self.res_model)
1019 object2 = res_obj.browse(cr, uid, self.res_id) or False
1021 obj = dirobj.browse(cr, uid, self.dir_id)
1022 if obj and (obj.type == 'ressource') and not object2:
1023 raise OSError(1, 'Operation not permited.')
1028 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1029 'ressource_id': object2 and object2.id or False,
1032 if (obj and (obj.type in ('directory'))) or not object2:
1033 val['parent_id'] = obj and obj.id or False
1035 return dirobj.create(cr, uid, val)
1037 def create_child(self, cr, path, data=None):
1038 """ API function to create a child file object and node
1039 Return the node_* created
1041 is_allowed = self.check_perms(2)
1043 raise IOError(errno.EPERM,"Permission denied")
1045 dirobj = self.context._dirobj
1046 uid = self.context.uid
1047 ctx = self.context.context.copy()
1048 ctx.update(self.dctx)
1049 fil_obj=dirobj.pool.get('ir.attachment')
1052 'datas_fname': path,
1053 'res_model': self.res_model,
1054 'res_id': self.res_id,
1055 # Datas are not set here
1057 if not self.res_find_all:
1058 val['parent_id'] = self.dir_id
1060 fil_id = fil_obj.create(cr, uid, val, context=ctx)
1061 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1062 klass = self.context.node_file_class
1063 fnode = klass(path, self, self.context, fil)
1064 if data is not None:
1065 fnode.set_data(cr, data, fil)
1068 def _get_ttag(self,cr):
1069 return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1071 node_res_dir.res_obj_class = node_res_obj
1073 class node_file(node_class):
1075 def __init__(self, path, parent, context, fil):
1076 super(node_file,self).__init__(path, parent,context)
1077 self.file_id = fil.id
1078 #todo: more info from ir_attachment
1079 if fil.file_type and '/' in fil.file_type:
1080 self.mimetype = str(fil.file_type)
1081 self.create_date = fil.create_date
1082 self.write_date = fil.write_date or fil.create_date
1083 self.content_length = fil.file_size
1084 self.displayname = fil.name
1088 if not parent.check_perms('x'):
1090 elif not parent.check_perms('w'):
1094 self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1096 self.uuser = 'nobody'
1097 self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1099 # This only propagates the problem to get_data. Better
1100 # fix those files to point to the root dir.
1101 self.storage_id = None
1104 if par.storage_id and par.storage_id.id:
1105 self.storage_id = par.storage_id.id
1109 def __eq__(self, other):
1110 if type(self) != type(other):
1112 if not self.context == other.context:
1114 if self.dctx != other.dctx:
1116 return self.file_id == other.file_id
1119 def open_data(self, cr, mode):
1120 stor = self.storage_id
1121 assert stor, "No storage for file #%s" % self.file_id
1122 if not self.check_perms(4):
1123 raise IOError(errno.EPERM, "Permission denied")
1125 # If storage is not set properly, we are just screwed here, don't
1126 # try to get it from default.
1127 stobj = self.context._dirobj.pool.get('document.storage')
1128 return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1131 uid = self.context.uid
1132 if not self.check_perms(8):
1133 raise IOError(errno.EPERM, "Permission denied")
1134 document_obj = self.context._dirobj.pool.get('ir.attachment')
1135 if self.type in ('collection','database'):
1137 document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1139 if document and document._table_name == 'ir.attachment':
1140 res = document_obj.unlink(cr, uid, [document.id])
1143 def fix_ppath(self, cr, fbro):
1144 """Sometimes we may init this w/o path, parent.
1145 This function fills the missing path from the file browse object
1147 Note: this may be an expensive operation, do on demand. However,
1148 once caching is in, we might want to do that at init time and keep
1151 if self.path or self.parent:
1154 uid = self.context.uid
1158 dirobj = self.context._dirobj.pool.get('document.directory')
1159 dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1160 if fbro.datas_fname:
1161 dirpath.append(fbro.datas_fname)
1163 dirpath.append(fbro.name)
1168 self.path = dirpath[0]
1170 def get_data(self, cr, fil_obj = None):
1171 """ Retrieve the data for some file.
1172 fil_obj may optionally be specified, and should be a browse object
1173 for the file. This is useful when the caller has already initiated
1174 the browse object. """
1175 # this is where storage kicks in..
1176 stor = self.storage_id
1177 assert stor, "No storage for file #%s" % self.file_id
1178 if not self.check_perms(4):
1179 raise IOError(errno.EPERM, "Permission denied")
1181 # If storage is not set properly, we are just screwed here, don't
1182 # try to get it from default.
1183 stobj = self.context._dirobj.pool.get('document.storage')
1184 return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1186 def get_data_len(self, cr, fil_obj = None):
1187 # TODO: verify with the storage object!
1188 bin_size = self.context.context.get('bin_size', False)
1189 if bin_size and not self.content_length:
1190 self.content_length = fil_obj.db_datas
1191 return self.content_length
1193 def set_data(self, cr, data, fil_obj = None):
1194 """ Store data at some file.
1195 fil_obj may optionally be specified, and should be a browse object
1196 for the file. This is useful when the caller has already initiated
1197 the browse object. """
1198 # this is where storage kicks in..
1199 stor = self.storage_id
1200 assert stor, "No storage for file #%s" % self.file_id
1201 if not self.check_perms(2):
1202 raise IOError(errno.EPERM, "Permission denied")
1204 stobj = self.context._dirobj.pool.get('document.storage')
1205 return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1207 def _get_ttag(self,cr):
1208 return 'file-%d' % self.file_id
1210 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1211 if ndir_node and ndir_node.context != self.context:
1212 raise NotImplementedError("Cannot move files between contexts")
1214 if (not self.check_perms(8)) and ndir_node.check_perms(2):
1215 raise IOError(errno.EPERM, "Permission denied")
1217 doc_obj = self.context._dirobj.pool.get('ir.attachment')
1219 dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1222 assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1225 raise IndexError("Cannot locate doc %d", self.file_id)
1227 if (not self.parent):
1228 # there *must* be a parent node for this one
1229 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1233 if ndir_node and self.parent != ndir_node:
1234 if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1235 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1236 raise NotImplementedError('Cannot move files between dynamic folders')
1239 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1240 ndir_node.dir_id, context=self.context.context)
1242 assert ndir_obj.id == ndir_node.dir_id
1244 stobj = self.context._dirobj.pool.get('document.storage')
1245 r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1246 context=self.context.context)
1249 if new_name and (new_name != dbro.name):
1251 raise NotImplementedError("Cannot rename and move") # TODO
1252 stobj = self.context._dirobj.pool.get('document.storage')
1253 r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1259 # We have to update the data ourselves
1261 ctx = self.context.context.copy()
1262 ctx['__from_node'] = True
1263 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1268 class node_content(node_class):
1269 our_type = 'content'
1270 def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1271 super(node_content,self).__init__(path, parent,context)
1272 self.cnt_id = cnt.id
1273 self.create_date = False
1274 self.write_date = False
1275 self.content_length = False
1276 self.unixperms = 0640
1278 self.uidperms = parent.uidperms & 14
1279 self.uuser = parent.uuser
1280 self.ugroup = parent.ugroup
1282 self.extension = cnt.extension
1283 self.report_id = cnt.report_id and cnt.report_id.id
1284 #self.mimetype = cnt.extension.
1285 self.displayname = path
1287 self.dctx.update(dctx)
1288 self.act_id = act_id
1290 def fill_fields(self, cr, dctx = None):
1291 """ Try to read the object and fill missing fields, like mimetype,
1293 This function must be different from the constructor, because
1294 it uses the db cursor.
1297 cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1300 if res and res[0][0]:
1301 self.mimetype = str(res[0][0])
1304 def get_data(self, cr, fil_obj = None):
1305 cntobj = self.context._dirobj.pool.get('document.directory.content')
1306 if not self.check_perms(4):
1307 raise IOError(errno.EPERM, "Permission denied")
1309 ctx = self.context.context.copy()
1310 ctx.update(self.dctx)
1311 data = cntobj.process_read(cr, self.context.uid, self, ctx)
1313 self.content_length = len(data)
1316 def open_data(self, cr, mode):
1317 if mode.endswith('b'):
1319 if mode in ('r', 'w'):
1321 elif mode in ('r+', 'w+'):
1324 raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1326 if not self.check_perms(cperms):
1327 raise IOError(errno.EPERM, "Permission denied")
1329 ctx = self.context.context.copy()
1330 ctx.update(self.dctx)
1332 return nodefd_content(self, cr, mode, ctx)
1334 def get_data_len(self, cr, fil_obj = None):
1335 # FIXME : here, we actually generate the content twice!!
1336 # we should have cached the generated content, but it is
1337 # not advisable to do keep it in memory, until we have a cache
1339 if not self.content_length:
1340 self.get_data(cr,fil_obj)
1341 return self.content_length
1343 def set_data(self, cr, data, fil_obj = None):
1344 cntobj = self.context._dirobj.pool.get('document.directory.content')
1345 if not self.check_perms(2):
1346 raise IOError(errno.EPERM, "Permission denied")
1348 ctx = self.context.context.copy()
1349 ctx.update(self.dctx)
1350 return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1352 def _get_ttag(self,cr):
1353 return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1355 def get_dav_resourcetype(self, cr):
1358 class nodefd_content(StringIO, node_descriptor):
1359 """ A descriptor to content nodes
1361 def __init__(self, parent, cr, mode, ctx):
1362 node_descriptor.__init__(self, parent)
1365 if mode in ('r', 'r+'):
1366 cntobj = parent.context._dirobj.pool.get('document.directory.content')
1367 data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1369 parent.content_length = len(data)
1370 StringIO.__init__(self, data)
1371 elif mode in ('w', 'w+'):
1372 StringIO.__init__(self, None)
1373 # at write, we start at 0 (= overwrite), but have the original
1374 # data available, in case of a seek()
1376 StringIO.__init__(self, None)
1378 logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1379 raise IOError(errno.EINVAL, "Invalid file mode")
1383 # we now open a *separate* cursor, to update the data.
1384 # FIXME: this may be improved, for concurrency handling
1385 if self.mode == 'r':
1386 StringIO.close(self)
1389 par = self._get_parent()
1390 uid = par.context.uid
1391 cr = pooler.get_db(par.context.dbname).cursor()
1393 if self.mode in ('w', 'w+', 'r+'):
1394 data = self.getvalue()
1395 cntobj = par.context._dirobj.pool.get('document.directory.content')
1396 cntobj.process_write(cr, uid, par, data, par.context.context)
1397 elif self.mode == 'a':
1398 raise NotImplementedError
1401 logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1405 StringIO.close(self)