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 logger.warning("Attempted to create a file under %r, not possible.", self)
370 raise IOError(errno.EPERM, "Not allowed to create files here")
372 def create_child_collection(self, cr, objname):
373 """ Create a child collection (directory) under self
375 logger.warning("Attempted to create a collection under %r, not possible.", self)
376 raise IOError(errno.EPERM, "Not allowed to create folders here")
379 raise NotImplementedError(repr(self))
382 raise NotImplementedError(repr(self))
384 def get_domain(self, cr, filters):
388 def check_perms(self, perms):
389 """ Check the permissions of the current node.
391 @param perms either an integers of the bits to check, or
392 a string with the permission letters
394 Permissions of nodes are (in a unix way):
395 1, x : allow descend into dir
396 2, w : allow write into file, or modification to dir
397 4, r : allow read of file, or listing of dir contents
398 8, u : allow remove (unlink)
401 if isinstance(perms, str):
403 chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 }
407 elif isinstance(perms, int):
408 if perms < 0 or perms > 15:
409 raise ValueError("Invalid permission bits")
411 raise ValueError("Invalid permission attribute")
413 return ((self.uidperms & perms) == perms)
415 class node_database(node_class):
416 """ A node representing the database directory
419 our_type = 'database'
420 def __init__(self, path=[], parent=False, context=None):
421 super(node_database,self).__init__(path, parent, context)
422 self.unixperms = 040750
425 def children(self, cr, domain=None):
426 res = self._child_get(cr, domain=domain) + self._file_get(cr)
429 def child(self, cr, name, domain=None):
430 res = self._child_get(cr, name, domain=None)
433 res = self._file_get(cr,name)
438 def _child_get(self, cr, name=False, domain=None):
439 dirobj = self.context._dirobj
440 uid = self.context.uid
441 ctx = self.context.context.copy()
442 ctx.update(self.dctx)
443 where = [('parent_id','=', False), ('ressource_parent_type_id','=',False)]
445 where.append(('name','=',name))
446 is_allowed = self.check_perms(1)
448 is_allowed = self.check_perms(5)
451 raise IOError(errno.EPERM, "Permission into directory denied")
454 where = where + domain
455 ids = dirobj.search(cr, uid, where, context=ctx)
457 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
458 klass = dirr.get_node_class(dirr, context=ctx)
459 res.append(klass(dirr.name, self, self.context,dirr))
463 def _file_get(self,cr, nodename=False):
467 def _get_ttag(self,cr):
468 return 'db-%s' % cr.dbname
470 def mkdosname(company_name, default='noname'):
471 """ convert a string to a dos-like name"""
474 badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>'
476 for c in company_name[:8]:
477 n += (c in badchars and '_') or c
481 def _uid2unixperms(perms, has_owner):
482 """ Convert the uidperms and the owner flag to full unix bits
485 res |= (perms & 0x07) << 6
486 res |= (perms & 0x05) << 3
491 class node_dir(node_database):
492 our_type = 'collection'
493 def __init__(self, path, parent, context, dirr, dctx=None):
494 super(node_dir,self).__init__(path, parent,context)
495 self.dir_id = dirr and dirr.id or False
496 #todo: more info from dirr
497 self.mimetype = 'application/x-directory'
498 # 'httpd/unix-directory'
499 self.create_date = dirr and dirr.create_date or False
500 self.domain = dirr and dirr.domain or []
501 self.res_model = dirr and dirr.ressource_type_id and dirr.ressource_type_id.model or False
502 # TODO: the write date should be MAX(file.write)..
503 self.write_date = dirr and (dirr.write_date or dirr.create_date) or False
504 self.content_length = 0
506 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
508 self.uuser = 'nobody'
509 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
510 self.uidperms = dirr.get_dir_permissions()
511 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
513 self.dctx.update(dctx)
514 dc2 = self.context.context
515 dc2.update(self.dctx)
516 dc2['dir_id'] = self.dir_id
517 self.displayname = dirr and dirr.name or False
518 if dirr and dirr.dctx_ids:
519 for dfld in dirr.dctx_ids:
521 self.dctx['dctx_' + dfld.field] = safe_eval(dfld.expr,dc2)
523 print "Cannot eval %s" % dfld.expr
527 def __eq__(self, other):
528 if type(self) != type(other):
530 if not self.context == other.context:
532 # Two directory nodes, for the same document.directory, may have a
533 # different context! (dynamic folders)
534 if self.dctx != other.dctx:
536 return self.dir_id == other.dir_id
538 def get_data(self, cr):
540 #for child in self.children(cr):
541 # res += child.get_data(cr)
544 def _file_get(self, cr, nodename=False):
545 res = super(node_dir,self)._file_get(cr, nodename)
547 is_allowed = self.check_perms(nodename and 1 or 5)
549 raise IOError(errno.EPERM, "Permission into directory denied")
551 cntobj = self.context._dirobj.pool.get('document.directory.content')
552 uid = self.context.uid
553 ctx = self.context.context.copy()
554 ctx.update(self.dctx)
555 where = [('directory_id','=',self.dir_id) ]
556 ids = cntobj.search(cr, uid, where, context=ctx)
557 for content in cntobj.browse(cr, uid, ids, context=ctx):
558 res3 = cntobj._file_get(cr, self, nodename, content)
564 def _child_get(self, cr, name=None, domain=None):
565 dirobj = self.context._dirobj
566 uid = self.context.uid
567 ctx = self.context.context.copy()
568 ctx.update(self.dctx)
569 where = [('parent_id','=',self.dir_id)]
571 where.append(('name','=',name))
572 is_allowed = self.check_perms(1)
574 is_allowed = self.check_perms(5)
577 raise IOError(errno.EPERM, "Permission into directory denied")
582 where2 = where + domain + [('ressource_parent_type_id','=',False)]
583 ids = dirobj.search(cr, uid, where2, context=ctx)
585 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
586 klass = dirr.get_node_class(dirr, context=ctx)
587 res.append(klass(dirr.name, self, self.context,dirr))
589 # Static directories should never return files with res_model/res_id
590 # because static dirs are /never/ related to a record.
591 # In fact, files related to some model and parented by the root dir
592 # (the default), will NOT be accessible in the node system unless
593 # a resource folder for that model exists (with resource_find_all=True).
594 # Having resource attachments in a common folder is bad practice,
595 # because they would be visible to all users, and their names may be
596 # the same, conflicting.
597 where += [('res_model', '=', False)]
598 fil_obj = dirobj.pool.get('ir.attachment')
599 ids = fil_obj.search(cr, uid, where, context=ctx)
601 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
602 klass = self.context.node_file_class
603 res.append(klass(fil.name, self, self.context, fil))
607 uid = self.context.uid
608 directory = self.context._dirobj.browse(cr, uid, self.dir_id)
611 raise OSError(2, 'Not such file or directory.')
612 if not self.check_perms('u'):
613 raise IOError(errno.EPERM,"Permission denied")
615 if directory._table_name=='document.directory':
616 if self.children(cr):
617 raise OSError(39, 'Directory not empty.')
618 res = self.context._dirobj.unlink(cr, uid, [directory.id])
620 raise OSError(1, 'Operation not permited.')
623 def create_child_collection(self, cr, objname):
625 if not self.check_perms(2):
626 raise IOError(errno.EPERM,"Permission denied")
628 dirobj = self.context._dirobj
629 uid = self.context.uid
630 ctx = self.context.context.copy()
631 ctx.update(self.dctx)
632 obj = dirobj.browse(cr, uid, self.dir_id)
633 if obj and (obj.type == 'ressource') and not object2:
634 raise OSError(1, 'Operation not permited.')
639 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
640 'ressource_id': object2 and object2.id or False,
641 'parent_id' : obj and obj.id or False
644 return dirobj.create(cr, uid, val)
647 def create_child(self, cr, path, data=None):
648 """ API function to create a child file object and node
649 Return the node_* created
651 if not self.check_perms(2):
652 raise IOError(errno.EPERM,"Permission denied")
654 dirobj = self.context._dirobj
655 uid = self.context.uid
656 ctx = self.context.context.copy()
657 ctx.update(self.dctx)
658 fil_obj=dirobj.pool.get('ir.attachment')
662 'parent_id': self.dir_id,
663 # Datas are not set here
666 fil_id = fil_obj.create(cr, uid, val, context=ctx)
667 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
668 fnode = node_file(path, self, self.context, fil)
670 fnode.set_data(cr, data, fil)
673 def _get_ttag(self,cr):
674 return 'dir-%d' % self.dir_id
676 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
677 """ Move directory. This operation is simple, since the present node is
678 only used for static, simple directories.
679 Note /may/ be called with ndir_node = None, to rename the document root.
681 if ndir_node and (ndir_node.context != self.context):
682 raise NotImplementedError("Cannot move directories between contexts")
684 if (not self.check_perms('u')) or (not ndir_node.check_perms('w')):
685 raise IOError(errno.EPERM,"Permission denied")
687 dir_obj = self.context._dirobj
689 dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context)
692 assert dbro.id == self.dir_id
695 raise IndexError("Cannot locate dir %d", self.dir_id)
697 if (not self.parent) and ndir_node:
698 if not dbro.parent_id:
699 raise IOError(errno.EPERM, "Cannot move the root directory!")
700 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
703 if self.parent != ndir_node:
704 logger.debug('Cannot move dir %r from %r to %r', self, self.parent, ndir_node)
705 raise NotImplementedError('Cannot move dir to another dir')
708 if new_name and (new_name != dbro.name):
709 if ndir_node.child(cr, new_name):
710 raise IOError(errno.EEXIST, "Destination path already exists")
711 ret['name'] = new_name
716 # We have to update the data ourselves
718 ctx = self.context.context.copy()
719 ctx['__from_node'] = True
720 dir_obj.write(cr, self.context.uid, [self.dir_id,], ret, ctx)
725 class node_res_dir(node_class):
726 """ A folder containing dynamic folders
727 A special sibling to node_dir, which does only contain dynamically
728 created folders foreach resource in the foreign model.
729 All folders should be of type node_res_obj and merely behave like
730 node_dirs (with limited domain).
732 our_type = 'collection'
734 def __init__(self, path, parent, context, dirr, dctx=None ):
735 super(node_res_dir,self).__init__(path, parent, context)
736 self.dir_id = dirr.id
737 #todo: more info from dirr
738 self.mimetype = 'application/x-directory'
739 # 'httpd/unix-directory'
740 self.create_date = dirr.create_date
741 # TODO: the write date should be MAX(file.write)..
742 self.write_date = dirr.write_date or dirr.create_date
743 self.content_length = 0
745 self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody'
747 self.uuser = 'nobody'
748 self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup')
749 self.uidperms = dirr.get_dir_permissions()
750 self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id)
751 self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False
752 self.resm_id = dirr.ressource_id
753 self.res_find_all = dirr.resource_find_all
754 self.namefield = dirr.resource_field.name or 'name'
755 self.displayname = dirr.name
756 # Important: the domain is evaluated using the *parent* dctx!
757 self.domain = dirr.domain
758 self.ressource_tree = dirr.ressource_tree
759 # and then, we add our own vars in the dctx:
761 self.dctx.update(dctx)
763 # and then, we prepare a dctx dict, for deferred evaluation:
765 for dfld in dirr.dctx_ids:
766 self.dctx_dict['dctx_' + dfld.field] = dfld.expr
768 def __eq__(self, other):
769 if type(self) != type(other):
771 if not self.context == other.context:
773 # Two nodes, for the same document.directory, may have a
774 # different context! (dynamic folders)
775 if self.dctx != other.dctx:
777 return self.dir_id == other.dir_id
779 def children(self, cr, domain=None):
780 return self._child_get(cr, domain=domain)
782 def child(self,cr, name, domain=None):
783 res = self._child_get(cr, name, domain=domain)
788 def _child_get(self, cr, name = None, domain=None):
789 """ return virtual children of resource, based on the
792 Note that many objects use NULL for a name, so we should
793 better call the name_search(),name_get() set of methods
795 obj = self.context._dirobj.pool.get(self.res_model)
798 dirobj = self.context._dirobj
799 uid = self.context.uid
800 ctx = self.context.context.copy()
801 ctx.update(self.dctx)
804 app = safe_eval(self.domain, ctx)
807 elif isinstance(app, list):
809 elif isinstance(app, tuple):
812 raise RuntimeError("incorrect domain expr: %s" % self.domain)
814 where.append(('id','=',self.resm_id))
817 where.append((self.namefield,'=',name))
818 is_allowed = self.check_perms(1)
820 is_allowed = self.check_perms(5)
823 raise IOError(errno.EPERM,"Permission denied")
825 # print "Where clause for %s" % self.res_model, where
826 if self.ressource_tree:
829 object2 = dirobj.pool.get(self.res_model).browse(cr, uid, self.resm_id) or False
830 if obj._parent_name in obj.fields_get(cr, uid):
831 where.append((obj._parent_name,'=',object2 and object2.id or False))
833 resids = obj.search(cr, uid, where, context=ctx)
835 for bo in obj.browse(cr, uid, resids, context=ctx):
838 name = getattr(bo, self.namefield)
841 # Yes! we can't do better but skip nameless records.
843 res.append(self.res_obj_class(name, self.dir_id, self, self.context, self.res_model, bo))
846 def _get_ttag(self,cr):
847 return 'rdir-%d' % self.dir_id
849 class node_res_obj(node_class):
850 """ A dynamically created folder.
851 A special sibling to node_dir, which does only contain dynamically
852 created folders foreach resource in the foreign model.
853 All folders should be of type node_res_obj and merely behave like
854 node_dirs (with limited domain).
856 our_type = 'collection'
857 def __init__(self, path, dir_id, parent, context, res_model, res_bo, res_id = None):
858 super(node_res_obj,self).__init__(path, parent,context)
860 #todo: more info from dirr
862 self.mimetype = 'application/x-directory'
863 # 'httpd/unix-directory'
864 self.create_date = parent.create_date
865 # TODO: the write date should be MAX(file.write)..
866 self.write_date = parent.write_date
867 self.content_length = 0
868 self.uidperms = parent.uidperms & 15
869 self.unixperms = 040000 | _uid2unixperms(self.uidperms, True)
870 self.uuser = parent.uuser
871 self.ugroup = parent.ugroup
872 self.res_model = res_model
873 self.domain = parent.domain
874 self.displayname = path
875 self.dctx_dict = parent.dctx_dict
876 self.res_find_all = parent.res_find_all
878 self.res_id = res_bo.id
879 dc2 = self.context.context.copy()
880 dc2.update(self.dctx)
881 dc2['res_model'] = res_model
882 dc2['res_id'] = res_bo.id
884 for fld,expr in self.dctx_dict.items():
886 self.dctx[fld] = safe_eval(expr, dc2)
888 print "Cannot eval %s for %s" % (expr, fld)
894 def __eq__(self, other):
895 if type(self) != type(other):
897 if not self.context == other.context:
899 if not self.res_model == other.res_model:
901 if not self.res_id == other.res_id:
903 if self.domain != other.domain:
905 if self.res_find_all != other.res_find_all:
907 if self.dctx != other.dctx:
909 return self.dir_id == other.dir_id
911 def children(self, cr, domain=None):
912 return self._child_get(cr, domain=domain) + self._file_get(cr)
914 def child(self, cr, name, domain=None):
915 res = self._child_get(cr, name, domain=domain)
918 res = self._file_get(cr, name)
923 def _file_get(self,cr, nodename=False):
925 is_allowed = self.check_perms((nodename and 1) or 5)
927 raise IOError(errno.EPERM,"Permission denied")
929 cntobj = self.context._dirobj.pool.get('document.directory.content')
930 uid = self.context.uid
931 ctx = self.context.context.copy()
932 ctx.update(self.dctx)
933 where = [('directory_id','=',self.dir_id) ]
935 # where.extend(self.domain)
936 # print "res_obj file_get clause", where
937 ids = cntobj.search(cr, uid, where, context=ctx)
938 for content in cntobj.browse(cr, uid, ids, context=ctx):
939 res3 = cntobj._file_get(cr, self, nodename, content, context=ctx)
945 def get_dav_props_DEPR(self, cr):
946 # Deprecated! (but document_ics must be cleaned, first)
948 cntobj = self.context._dirobj.pool.get('document.directory.content')
949 uid = self.context.uid
950 ctx = self.context.context.copy()
951 ctx.update(self.dctx)
952 where = [('directory_id','=',self.dir_id) ]
953 ids = cntobj.search(cr, uid, where, context=ctx)
954 for content in cntobj.browse(cr, uid, ids, context=ctx):
955 if content.extension == '.ics': # FIXME: call the content class!
956 res['http://groupdav.org/'] = ('resourcetype',)
959 def get_dav_eprop_DEPR(self, cr, ns, prop):
961 if ns != 'http://groupdav.org/' or prop != 'resourcetype':
962 logger.warning("Who asked for %s:%s?" % (ns, prop))
964 cntobj = self.context._dirobj.pool.get('document.directory.content')
965 uid = self.context.uid
966 ctx = self.context.context.copy()
967 ctx.update(self.dctx)
968 where = [('directory_id','=',self.dir_id) ]
969 ids = cntobj.search(cr,uid,where,context=ctx)
970 for content in cntobj.browse(cr, uid, ids, context=ctx):
971 # TODO: remove relic of GroupDAV
972 if content.extension == '.ics': # FIXME: call the content class!
973 return ('vevent-collection','http://groupdav.org/')
976 def _child_get(self, cr, name=None, domain=None):
977 dirobj = self.context._dirobj
979 is_allowed = self.check_perms((name and 1) or 5)
981 raise IOError(errno.EPERM,"Permission denied")
983 uid = self.context.uid
984 ctx = self.context.context.copy()
985 ctx.update(self.dctx)
986 directory = dirobj.browse(cr, uid, self.dir_id)
987 obj = dirobj.pool.get(self.res_model)
991 where.append(('name','=',name))
993 # Directory Structure display in tree structure
994 if self.res_id and directory.ressource_tree:
996 if obj._parent_name in obj.fields_get(cr, uid):
997 where1 = where + [(obj._parent_name, '=', self.res_id)]
998 namefield = directory.resource_field.name or 'name'
999 resids = obj.search(cr, uid, where1, context=ctx)
1000 for bo in obj.browse(cr, uid, resids, context=ctx):
1003 res_name = getattr(bo, namefield)
1007 klass = directory.get_node_class(directory, dynamic=True, context=ctx)
1008 res.append(klass(res_name, dir_id=self.dir_id, parent=self, context=self.context, res_model=self.res_model, res_bo=bo))
1011 where2 = where + [('parent_id','=',self.dir_id) ]
1012 ids = dirobj.search(cr, uid, where2, context=ctx)
1013 for dirr in dirobj.browse(cr, uid, ids, context=ctx):
1014 if dirr.type == 'directory':
1015 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1016 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1017 elif dirr.type == 'ressource':
1018 # child resources can be controlled by properly set dctx
1019 klass = dirr.get_node_class(dirr, context=ctx)
1020 res.append(klass(dirr.name,self,self.context, dirr, {'active_id': self.res_id}))
1022 fil_obj = dirobj.pool.get('ir.attachment')
1023 if self.res_find_all:
1025 where3 = where2 + [('res_model', '=', self.res_model), ('res_id','=',self.res_id)]
1026 # print "where clause for dir_obj", where2
1027 ids = fil_obj.search(cr, uid, where3, context=ctx)
1029 for fil in fil_obj.browse(cr, uid, ids, context=ctx):
1030 klass = self.context.node_file_class
1031 res.append(klass(fil.name, self, self.context, fil))
1034 # Get Child Ressource Directories
1035 if directory.ressource_type_id and directory.ressource_type_id.id:
1036 where4 = where + [('ressource_parent_type_id','=',directory.ressource_type_id.id)]
1037 where5 = where4 + [('ressource_id','=',0)]
1038 dirids = dirobj.search(cr,uid, where5)
1039 where5 = where4 + [('ressource_id','=',self.res_id)]
1040 dirids = dirids + dirobj.search(cr,uid, where5)
1041 for dirr in dirobj.browse(cr, uid, dirids, context=ctx):
1042 if dirr.type == 'directory' and not dirr.parent_id:
1043 klass = dirr.get_node_class(dirr, dynamic=True, context=ctx)
1044 res.append(klass(dirr.name, dirr.id, self, self.context, self.res_model, res_bo = None, res_id = self.res_id))
1045 if dirr.type == 'ressource':
1046 klass = dirr.get_node_class(dirr, context=ctx)
1047 res.append(klass(dirr.name, self, self.context, dirr, {'active_id': self.res_id}))
1050 def create_child_collection(self, cr, objname):
1051 dirobj = self.context._dirobj
1052 is_allowed = self.check_perms(2)
1054 raise IOError(errno.EPERM,"Permission denied")
1056 uid = self.context.uid
1057 ctx = self.context.context.copy()
1058 ctx.update(self.dctx)
1059 res_obj = dirobj.pool.get(self.res_model)
1061 object2 = res_obj.browse(cr, uid, self.res_id) or False
1063 obj = dirobj.browse(cr, uid, self.dir_id)
1064 if obj and (obj.type == 'ressource') and not object2:
1065 raise OSError(1, 'Operation not permited.')
1070 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False,
1071 'ressource_id': object2 and object2.id or False,
1074 if (obj and (obj.type in ('directory'))) or not object2:
1075 val['parent_id'] = obj and obj.id or False
1077 return dirobj.create(cr, uid, val)
1079 def create_child(self, cr, path, data=None):
1080 """ API function to create a child file object and node
1081 Return the node_* created
1083 is_allowed = self.check_perms(2)
1085 raise IOError(errno.EPERM,"Permission denied")
1087 dirobj = self.context._dirobj
1088 uid = self.context.uid
1089 ctx = self.context.context.copy()
1090 ctx.update(self.dctx)
1091 fil_obj=dirobj.pool.get('ir.attachment')
1094 'datas_fname': path,
1095 'res_model': self.res_model,
1096 'res_id': self.res_id,
1097 # Datas are not set here
1099 if not self.res_find_all:
1100 val['parent_id'] = self.dir_id
1102 fil_id = fil_obj.create(cr, uid, val, context=ctx)
1103 fil = fil_obj.browse(cr, uid, fil_id, context=ctx)
1104 klass = self.context.node_file_class
1105 fnode = klass(path, self, self.context, fil)
1106 if data is not None:
1107 fnode.set_data(cr, data, fil)
1110 def _get_ttag(self,cr):
1111 return 'rodir-%d-%d' % (self.dir_id, self.res_id)
1113 node_res_dir.res_obj_class = node_res_obj
1115 class node_file(node_class):
1117 def __init__(self, path, parent, context, fil):
1118 super(node_file,self).__init__(path, parent,context)
1119 self.file_id = fil.id
1120 #todo: more info from ir_attachment
1121 if fil.file_type and '/' in fil.file_type:
1122 self.mimetype = str(fil.file_type)
1123 self.create_date = fil.create_date
1124 self.write_date = fil.write_date or fil.create_date
1125 self.content_length = fil.file_size
1126 self.displayname = fil.name
1130 if not parent.check_perms('x'):
1132 elif not parent.check_perms('w'):
1136 self.uuser = (fil.user_id and fil.user_id.login) or 'nobody'
1138 self.uuser = 'nobody'
1139 self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup')
1141 # This only propagates the problem to get_data. Better
1142 # fix those files to point to the root dir.
1143 self.storage_id = None
1146 if par.storage_id and par.storage_id.id:
1147 self.storage_id = par.storage_id.id
1151 def __eq__(self, other):
1152 if type(self) != type(other):
1154 if not self.context == other.context:
1156 if self.dctx != other.dctx:
1158 return self.file_id == other.file_id
1161 def open_data(self, cr, mode):
1162 stor = self.storage_id
1163 assert stor, "No storage for file #%s" % self.file_id
1164 if not self.check_perms(4):
1165 raise IOError(errno.EPERM, "Permission denied")
1167 # If storage is not set properly, we are just screwed here, don't
1168 # try to get it from default.
1169 stobj = self.context._dirobj.pool.get('document.storage')
1170 return stobj.get_file(cr, self.context.uid, stor, self, mode=mode, context=self.context.context)
1173 uid = self.context.uid
1174 if not self.check_perms(8):
1175 raise IOError(errno.EPERM, "Permission denied")
1176 document_obj = self.context._dirobj.pool.get('ir.attachment')
1177 if self.type in ('collection','database'):
1179 document = document_obj.browse(cr, uid, self.file_id, context=self.context.context)
1181 if document and document._table_name == 'ir.attachment':
1182 res = document_obj.unlink(cr, uid, [document.id])
1185 def fix_ppath(self, cr, fbro):
1186 """Sometimes we may init this w/o path, parent.
1187 This function fills the missing path from the file browse object
1189 Note: this may be an expensive operation, do on demand. However,
1190 once caching is in, we might want to do that at init time and keep
1193 if self.path or self.parent:
1196 uid = self.context.uid
1200 dirobj = self.context._dirobj.pool.get('document.directory')
1201 dirpath = dirobj.get_full_path(cr, uid, fbro.parent_id.id, context=self.context.context)
1202 if fbro.datas_fname:
1203 dirpath.append(fbro.datas_fname)
1205 dirpath.append(fbro.name)
1210 self.path = dirpath[0]
1212 def get_data(self, cr, fil_obj = None):
1213 """ Retrieve the data for some file.
1214 fil_obj may optionally be specified, and should be a browse object
1215 for the file. This is useful when the caller has already initiated
1216 the browse object. """
1217 # this is where storage kicks in..
1218 stor = self.storage_id
1219 assert stor, "No storage for file #%s" % self.file_id
1220 if not self.check_perms(4):
1221 raise IOError(errno.EPERM, "Permission denied")
1223 # If storage is not set properly, we are just screwed here, don't
1224 # try to get it from default.
1225 stobj = self.context._dirobj.pool.get('document.storage')
1226 return stobj.get_data(cr, self.context.uid,stor, self,self.context.context, fil_obj)
1228 def get_data_len(self, cr, fil_obj = None):
1229 # TODO: verify with the storage object!
1230 bin_size = self.context.context.get('bin_size', False)
1231 if bin_size and not self.content_length:
1232 self.content_length = fil_obj.db_datas
1233 return self.content_length
1235 def set_data(self, cr, data, fil_obj = None):
1236 """ Store data at some file.
1237 fil_obj may optionally be specified, and should be a browse object
1238 for the file. This is useful when the caller has already initiated
1239 the browse object. """
1240 # this is where storage kicks in..
1241 stor = self.storage_id
1242 assert stor, "No storage for file #%s" % self.file_id
1243 if not self.check_perms(2):
1244 raise IOError(errno.EPERM, "Permission denied")
1246 stobj = self.context._dirobj.pool.get('document.storage')
1247 return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj)
1249 def _get_ttag(self,cr):
1250 return 'file-%d' % self.file_id
1252 def move_to(self, cr, ndir_node, new_name=False, fil_obj=None, ndir_obj=None, in_write=False):
1253 if ndir_node and ndir_node.context != self.context:
1254 raise NotImplementedError("Cannot move files between contexts")
1256 if (not self.check_perms(8)) and ndir_node.check_perms(2):
1257 raise IOError(errno.EPERM, "Permission denied")
1259 doc_obj = self.context._dirobj.pool.get('ir.attachment')
1261 dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context)
1264 assert dbro.id == self.file_id, "%s != %s for %r" % (dbro.id, self.file_id, self)
1267 raise IndexError("Cannot locate doc %d", self.file_id)
1269 if (not self.parent):
1270 # there *must* be a parent node for this one
1271 self.parent = self.context.get_dir_node(cr, dbro.parent_id)
1275 if ndir_node and self.parent != ndir_node:
1276 if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)):
1277 logger.debug('Cannot move file %r from %r to %r', self, self.parent, ndir_node)
1278 raise NotImplementedError('Cannot move files between dynamic folders')
1281 ndir_obj = self.context._dirobj.browse(cr, self.context.uid, \
1282 ndir_node.dir_id, context=self.context.context)
1284 assert ndir_obj.id == ndir_node.dir_id
1286 stobj = self.context._dirobj.pool.get('document.storage')
1287 r2 = stobj.simple_move(cr, self.context.uid, self, ndir_obj, \
1288 context=self.context.context)
1291 if new_name and (new_name != dbro.name):
1293 raise NotImplementedError("Cannot rename and move") # TODO
1294 stobj = self.context._dirobj.pool.get('document.storage')
1295 r2 = stobj.simple_rename(cr, self.context.uid, self, new_name, self.context.context)
1301 # We have to update the data ourselves
1303 ctx = self.context.context.copy()
1304 ctx['__from_node'] = True
1305 doc_obj.write(cr, self.context.uid, [self.file_id,], ret, ctx )
1310 class node_content(node_class):
1311 our_type = 'content'
1312 def __init__(self, path, parent, context, cnt, dctx = None, act_id=None):
1313 super(node_content,self).__init__(path, parent,context)
1314 self.cnt_id = cnt.id
1315 self.create_date = False
1316 self.write_date = False
1317 self.content_length = False
1318 self.unixperms = 0640
1320 self.uidperms = parent.uidperms & 14
1321 self.uuser = parent.uuser
1322 self.ugroup = parent.ugroup
1324 self.extension = cnt.extension
1325 self.report_id = cnt.report_id and cnt.report_id.id
1326 #self.mimetype = cnt.extension.
1327 self.displayname = path
1329 self.dctx.update(dctx)
1330 self.act_id = act_id
1332 def fill_fields(self, cr, dctx = None):
1333 """ Try to read the object and fill missing fields, like mimetype,
1335 This function must be different from the constructor, because
1336 it uses the db cursor.
1339 cr.execute('SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s;',
1342 if res and res[0][0]:
1343 self.mimetype = str(res[0][0])
1346 def get_data(self, cr, fil_obj = None):
1347 cntobj = self.context._dirobj.pool.get('document.directory.content')
1348 if not self.check_perms(4):
1349 raise IOError(errno.EPERM, "Permission denied")
1351 ctx = self.context.context.copy()
1352 ctx.update(self.dctx)
1353 data = cntobj.process_read(cr, self.context.uid, self, ctx)
1355 self.content_length = len(data)
1358 def open_data(self, cr, mode):
1359 if mode.endswith('b'):
1361 if mode in ('r', 'w'):
1363 elif mode in ('r+', 'w+'):
1366 raise IOError(errno.EINVAL, "Cannot open at mode %s" % mode)
1368 if not self.check_perms(cperms):
1369 raise IOError(errno.EPERM, "Permission denied")
1371 ctx = self.context.context.copy()
1372 ctx.update(self.dctx)
1374 return nodefd_content(self, cr, mode, ctx)
1376 def get_data_len(self, cr, fil_obj = None):
1377 # FIXME : here, we actually generate the content twice!!
1378 # we should have cached the generated content, but it is
1379 # not advisable to do keep it in memory, until we have a cache
1381 if not self.content_length:
1382 self.get_data(cr,fil_obj)
1383 return self.content_length
1385 def set_data(self, cr, data, fil_obj = None):
1386 cntobj = self.context._dirobj.pool.get('document.directory.content')
1387 if not self.check_perms(2):
1388 raise IOError(errno.EPERM, "Permission denied")
1390 ctx = self.context.context.copy()
1391 ctx.update(self.dctx)
1392 return cntobj.process_write(cr, self.context.uid, self, data, ctx)
1394 def _get_ttag(self,cr):
1395 return 'cnt-%d%s' % (self.cnt_id,(self.act_id and ('-' + str(self.act_id))) or '')
1397 def get_dav_resourcetype(self, cr):
1400 class nodefd_content(StringIO, node_descriptor):
1401 """ A descriptor to content nodes
1403 def __init__(self, parent, cr, mode, ctx):
1404 node_descriptor.__init__(self, parent)
1407 if mode in ('r', 'r+'):
1408 cntobj = parent.context._dirobj.pool.get('document.directory.content')
1409 data = cntobj.process_read(cr, parent.context.uid, parent, ctx)
1411 parent.content_length = len(data)
1412 StringIO.__init__(self, data)
1413 elif mode in ('w', 'w+'):
1414 StringIO.__init__(self, None)
1415 # at write, we start at 0 (= overwrite), but have the original
1416 # data available, in case of a seek()
1418 StringIO.__init__(self, None)
1420 logging.getLogger('document.content').error("Incorrect mode %s specified", mode)
1421 raise IOError(errno.EINVAL, "Invalid file mode")
1425 # we now open a *separate* cursor, to update the data.
1426 # FIXME: this may be improved, for concurrency handling
1427 if self.mode == 'r':
1428 StringIO.close(self)
1431 par = self._get_parent()
1432 uid = par.context.uid
1433 cr = pooler.get_db(par.context.dbname).cursor()
1435 if self.mode in ('w', 'w+', 'r+'):
1436 data = self.getvalue()
1437 cntobj = par.context._dirobj.pool.get('document.directory.content')
1438 cntobj.process_write(cr, uid, par, data, par.context.context)
1439 elif self.mode == 'a':
1440 raise NotImplementedError
1443 logging.getLogger('document.content').exception('Cannot update db content #%d for close:', par.cnt_id)
1447 StringIO.close(self)