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 ##############################################################################
23 from osv import osv, fields
27 from tools.translate import _
29 class document_directory(osv.osv):
30 _name = 'document.directory'
31 _description = 'Directory'
34 'name': fields.char('Name', size=64, required=True, select=1),
35 'write_date': fields.datetime('Date Modified', readonly=True),
36 'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True),
37 'create_date': fields.datetime('Date Created', readonly=True),
38 'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
39 'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
40 'user_id': fields.many2one('res.users', 'Owner'),
41 'storage_id': fields.many2one('document.storage', 'Storage'),
42 'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
43 'parent_id': fields.many2one('document.directory', 'Parent Directory', select=1),
44 'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
45 'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'),
46 'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'),
47 'type': fields.selection([
48 ('directory','Static Directory'),
49 ('ressource','Folders per resource'),
51 'Type', required=True, select=1,
52 help="Each directory can either have the type Static or be linked to another resource. A static directory, as with Operating Systems, is the classic directory that can contain a set of files. The directories linked to systems resources automatically possess sub-directories for each of resource types defined in the parent directory."),
53 'ressource_type_id': fields.many2one('ir.model', 'Resource model',
54 help="Select an object here and there will be one folder per record of that resource."),
55 'resource_field': fields.many2one('ir.model.fields', 'Name field', help='Field to be used as name on resource directories. If empty, the "name" will be used.'),
56 'resource_find_all': fields.boolean('Find all resources', required=True,
57 help="If true, all attachments that match this resource will " \
58 " be located. If false, only ones that have this as parent." ),
59 'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model',
60 help="If you put an object here, this directory template will appear bellow all of these objects. " \
61 "Don't put a parent directory if you select a parent model."),
62 'ressource_id': fields.integer('Resource ID'),
63 'ressource_tree': fields.boolean('Tree Structure',
64 help="Check this if you want to use the same tree structure as the object selected in the system."),
65 'dctx_ids': fields.one2many('document.directory.dctx', 'dir_id', 'Context fields'),
66 'company_id': fields.many2one('res.company', 'Company'),
70 def _get_root_directory(self, cr,uid, context=None):
71 objid=self.pool.get('ir.model.data')
73 mid = objid._get_id(cr, uid, 'document', 'dir_root')
76 root_id = objid.read(cr, uid, mid, ['res_id'])['res_id']
80 logger = netsvc.Logger()
81 logger.notifyChannel("document", netsvc.LOG_WARNING, 'Cannot set directory root:'+ str(e))
83 return objid.browse(cr, uid, mid, context=context).res_id
85 def _get_def_storage(self, cr, uid, context=None):
86 if context and context.has_key('default_parent_id'):
87 # Use the same storage as the parent..
88 diro = self.browse(cr, uid, context['default_parent_id'])
90 return diro.storage_id.id
91 objid=self.pool.get('ir.model.data')
93 mid = objid._get_id(cr, uid, 'document', 'storage_default')
94 return objid.browse(cr, uid, mid, context=context).res_id
99 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'document.directory', context=c),
100 'user_id': lambda self,cr,uid,ctx: uid,
101 'domain': lambda self,cr,uid,ctx: '[]',
102 'type': lambda *args: 'directory',
103 'ressource_id': lambda *a: 0,
104 'storage_id': _get_def_storage,
105 'resource_find_all': True,
108 ('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !'),
109 ('no_selfparent', 'check(parent_id <> id)', 'Directory cannot be parent of itself!'),
110 ('dir_parented', 'check(parent_id IS NOT NULL OR storage_id IS NOT NULL)', 'Directory must have a parent or a storage')
112 def name_get(self, cr, uid, ids, context=None):
114 if not self.search(cr,uid,[('id','in',ids)]):
116 for d in self.browse(cr, uid, ids, context=context):
119 while d2 and d2.parent_id:
120 s = d2.name + (s and ('/' + s) or '')
122 res.append((d.id, s or d.name))
125 def get_full_path(self, cr, uid, dir_id, context=None):
126 """ Return the full path to this directory, in a list, root first
128 if isinstance(dir_id, (tuple, list)):
129 assert len(dir_id) == 1
132 def _parent(dir_id, path):
133 parent=self.browse(cr, uid, dir_id)
134 if parent.parent_id and not parent.ressource_parent_type_id:
135 _parent(parent.parent_id.id,path)
136 path.append(parent.name)
138 path.append(parent.name)
141 _parent(dir_id, path)
144 def _check_recursion(self, cr, uid, ids):
147 cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str,ids))+')')
148 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
155 (_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
158 def __init__(self, *args, **kwargs):
159 super(document_directory, self).__init__(*args, **kwargs)
161 def onchange_content_id(self, cr, uid, ids, ressource_type_id):
166 uri: of the form "Sales Order/SO001"
169 object: the object.directory or object.directory.content
170 object2: the other object linked (if object.directory.content)
172 def get_object(self, cr, uid, uri, context=None):
173 """ Return a node object for the given uri.
174 This fn merely passes the call to node_context
179 return nodes.get_node_context(cr, uid, context).get_uri(cr, uri)
181 def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None):
182 """Retrieve the class of nodes for this directory
184 This function can be overriden by inherited classes ;)
185 @param dbro The browse object, if caller already has it
188 dbro = self.browse(cr, uid, ids, context=context)
191 assert dbro.type == 'directory'
192 return nodes.node_res_obj
193 elif dbro.type == 'directory':
194 return nodes.node_dir
195 elif dbro.type == 'ressource':
196 return nodes.node_res_dir
198 raise ValueError("dir node for %s type", dbro.type)
200 def _prepare_context(self, cr, uid, nctx, context):
201 """ Fill nctx with properties for this database
202 @param nctx instance of nodes.node_context, to be filled
203 @param context ORM context (dict) for us
205 Note that this function is called *without* a list of ids,
206 it should behave the same for the whole database (based on the
207 ORM instance of document.directory).
209 Some databases may override this and attach properties to the
210 node_context. See WebDAV, CalDAV.
214 def get_dir_permissions(self, cr, uid, ids ):
215 """Check what permission user 'uid' has on directory 'id'
220 cr.execute( "SELECT count(dg.item_id) AS needs, count(ug.uid) AS has " \
221 " FROM document_directory_group_rel dg " \
222 " LEFT OUTER JOIN res_groups_users_rel ug " \
223 " ON (dg.group_id = ug.gid AND ug.uid = %s) " \
224 " WHERE dg.item_id = %s ", (uid, id))
225 needs, has = cr.fetchone()
226 if needs and not has:
227 return 1 # still allow to descend into.
231 def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext):
232 """ try to locate the node in uri,
233 Return a tuple (node_dir, remaining_path)
235 return (nodes.node_database(context=ncontext), uri)
237 def copy(self, cr, uid, id, default=None, context=None):
240 name = self.read(cr, uid, [id])[0]['name']
241 default.update({'name': name+ " (copy)"})
242 return super(document_directory,self).copy(cr, uid, id, default, context)
244 def _check_duplication(self, cr, uid, vals, ids=[], op='create'):
245 name=vals.get('name',False)
246 parent_id=vals.get('parent_id',False)
247 ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
248 ressource_id=vals.get('ressource_id',0)
250 for directory in self.browse(cr, uid, ids):
254 parent_id=directory.parent_id and directory.parent_id.id or False
256 if not ressource_parent_type_id:
257 ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
259 ressource_id=directory.ressource_id and directory.ressource_id or 0
260 res=self.search(cr,uid,[('id','<>',directory.id),('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
264 res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
268 def write(self, cr, uid, ids, vals, context=None):
269 if not self._check_duplication(cr, uid, vals, ids, op='write'):
270 raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
271 return super(document_directory,self).write(cr, uid, ids, vals, context=context)
273 def create(self, cr, uid, vals, context=None):
274 if not self._check_duplication(cr, uid, vals):
275 raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
276 newname = vals.get('name',False)
278 for illeg in ('/', '@', '$', '#'):
280 raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
281 return super(document_directory,self).create(cr, uid, vals, context)
283 # TODO def unlink(...
287 class document_directory_dctx(osv.osv):
288 """ In order to evaluate dynamic folders, child items could have a limiting
289 domain expression. For that, their parents will export a context where useful
290 information will be passed on.
291 If you define sth like "s_id" = "this.id" at a folder iterating over sales, its
292 children could have a domain like [('sale_id', = ,dctx_s_id )]
293 This system should be used recursively, that is, parent dynamic context will be
294 appended to all children down the tree.
296 _name = 'document.directory.dctx'
297 _description = 'Directory Dynamic Context'
299 'dir_id': fields.many2one('document.directory', 'Directory', required=True, ondelete="cascade"),
300 'field': fields.char('Field', size=20, required=True, select=1, help="The name of the field. Note that the prefix \"dctx_\" will be prepended to what is typed here."),
301 'expr': fields.char('Expression', size=64, required=True, help="A python expression used to evaluate the field.\n" + \
302 "You can use 'dir_id' for current dir, 'res_id', 'res_model' as a reference to the current record, in dynamic folders"),
305 document_directory_dctx()
308 class document_directory_node(osv.osv):
309 _inherit = 'process.node'
311 'directory_id': fields.many2one('document.directory', 'Document directory', ondelete="set null"),
313 document_directory_node()