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
24 from osv.orm import except_orm
27 from tools.translate import _
28 _logger = logging.getLogger(__name__)
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', change_default=True),
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, change_default=True),
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, change_default=True,
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', change_default=True,
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', change_default=True,
60 help="If you put an object here, this directory template will appear bellow all of these objects. " \
61 "Such directories are \"attached\" to the specific model or record, just like attachments. " \
62 "Don't put a parent directory if you select a parent model."),
63 'ressource_id': fields.integer('Resource ID',
64 help="Along with Parent Model, this ID attaches this folder to a specific record of Parent Model."),
65 'ressource_tree': fields.boolean('Tree Structure',
66 help="Check this if you want to use the same tree structure as the object selected in the system."),
67 'dctx_ids': fields.one2many('document.directory.dctx', 'dir_id', 'Context fields'),
68 'company_id': fields.many2one('res.company', 'Company', change_default=True),
72 def _get_root_directory(self, cr, uid, context=None):
73 objid=self.pool.get('ir.model.data')
75 mid = objid._get_id(cr, uid, 'document', 'dir_root')
78 root_id = objid.read(cr, uid, mid, ['res_id'])['res_id']
82 return objid.browse(cr, uid, mid, context=context).res_id
84 def _get_def_storage(self, cr, uid, context=None):
85 if context and context.has_key('default_parent_id'):
86 # Use the same storage as the parent..
87 diro = self.browse(cr, uid, context['default_parent_id'])
89 return diro.storage_id.id
90 objid=self.pool.get('ir.model.data')
92 mid = objid._get_id(cr, uid, 'document', 'storage_default')
93 return objid.browse(cr, uid, mid, context=context).res_id
98 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'document.directory', context=c),
99 'user_id': lambda self,cr,uid,ctx: uid,
103 'storage_id': _get_def_storage, # Still, it is bad practice to set it everywhere.
104 'resource_find_all': True,
107 ('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !'),
108 ('no_selfparent', 'check(parent_id <> id)', 'Directory cannot be parent of itself!'),
109 ('dir_parented', 'check(parent_id IS NOT NULL OR storage_id IS NOT NULL)', 'Directory must have a parent or a storage.')
111 def name_get(self, cr, uid, ids, context=None):
113 if not self.search(cr,uid,[('id','in',ids)]):
115 for d in self.browse(cr, uid, ids, context=context):
118 while d2 and d2.parent_id:
119 s = d2.name + (s and ('/' + s) or '')
121 res.append((d.id, s or d.name))
124 def get_full_path(self, cr, uid, dir_id, context=None):
125 """ Return the full path to this directory, in a list, root first
127 if isinstance(dir_id, (tuple, list)):
128 assert len(dir_id) == 1
131 def _parent(dir_id, path):
132 parent=self.browse(cr, uid, dir_id)
133 if parent.parent_id and not parent.ressource_parent_type_id:
134 _parent(parent.parent_id.id,path)
135 path.append(parent.name)
137 path.append(parent.name)
140 _parent(dir_id, path)
143 def _check_recursion(self, cr, uid, ids, context=None):
146 cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str,ids))+')')
147 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
154 (_check_recursion, 'Error! You cannot create recursive directories.', ['parent_id'])
157 def __init__(self, *args, **kwargs):
158 super(document_directory, self).__init__(*args, **kwargs)
160 def onchange_content_id(self, cr, uid, ids, ressource_type_id):
165 uri: of the form "Sales Order/SO001"
168 object: the object.directory or object.directory.content
169 object2: the other object linked (if object.directory.content)
171 def get_object(self, cr, uid, uri, context=None):
172 """ Return a node object for the given uri.
173 This fn merely passes the call to node_context
176 return nodes.get_node_context(cr, uid, context).get_uri(cr, uri)
178 def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None):
179 """Retrieve the class of nodes for this directory
181 This function can be overriden by inherited classes ;)
182 @param dbro The browse object, if caller already has it
185 dbro = self.browse(cr, uid, ids, context=context)
188 return nodes.node_res_obj
189 elif dbro.type == 'directory':
190 return nodes.node_dir
191 elif dbro.type == 'ressource':
192 return nodes.node_res_dir
194 raise ValueError("dir node for %s type.", dbro.type)
196 def _prepare_context(self, cr, uid, nctx, context=None):
197 """ Fill nctx with properties for this database
198 @param nctx instance of nodes.node_context, to be filled
199 @param context ORM context (dict) for us
201 Note that this function is called *without* a list of ids,
202 it should behave the same for the whole database (based on the
203 ORM instance of document.directory).
205 Some databases may override this and attach properties to the
206 node_context. See WebDAV, CalDAV.
210 def get_dir_permissions(self, cr, uid, ids, context=None):
211 """Check what permission user 'uid' has on directory 'id'
216 for pperms in [('read', 5), ('write', 2), ('unlink', 8)]:
218 self.check_access_rule(cr, uid, ids, pperms[0], context=context)
224 def _locate_child(self, cr, uid, root_id, uri, nparent, ncontext):
225 """ try to locate the node in uri,
226 Return a tuple (node_dir, remaining_path)
228 return (nodes.node_database(context=ncontext), uri)
230 def copy(self, cr, uid, id, default=None, context=None):
233 name = self.read(cr, uid, [id])[0]['name']
234 default.update(name=_("%s (copy)") % (name))
235 return super(document_directory,self).copy(cr, uid, id, default, context=context)
237 def _check_duplication(self, cr, uid, vals, ids=None, op='create'):
238 name=vals.get('name',False)
239 parent_id=vals.get('parent_id',False)
240 ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
241 ressource_id=vals.get('ressource_id',0)
243 for directory in self.browse(cr, uid, ids):
247 parent_id=directory.parent_id and directory.parent_id.id or False
249 if not ressource_parent_type_id:
250 ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
252 ressource_id=directory.ressource_id and directory.ressource_id or 0
253 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)])
257 res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
261 def write(self, cr, uid, ids, vals, context=None):
262 if not self._check_duplication(cr, uid, vals, ids, op='write'):
263 raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
264 return super(document_directory,self).write(cr, uid, ids, vals, context=context)
266 def create(self, cr, uid, vals, context=None):
267 if not self._check_duplication(cr, uid, vals):
268 raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
269 newname = vals.get('name',False)
271 for illeg in ('/', '@', '$', '#'):
273 raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
274 return super(document_directory,self).create(cr, uid, vals, context)
276 # TODO def unlink(...
280 class document_directory_dctx(osv.osv):
281 """ In order to evaluate dynamic folders, child items could have a limiting
282 domain expression. For that, their parents will export a context where useful
283 information will be passed on.
284 If you define sth like "s_id" = "this.id" at a folder iterating over sales, its
285 children could have a domain like [('sale_id', = ,dctx_s_id )]
286 This system should be used recursively, that is, parent dynamic context will be
287 appended to all children down the tree.
289 _name = 'document.directory.dctx'
290 _description = 'Directory Dynamic Context'
292 'dir_id': fields.many2one('document.directory', 'Directory', required=True, ondelete="cascade"),
293 '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."),
294 'expr': fields.char('Expression', size=64, required=True, help="A python expression used to evaluate the field.\n" + \
295 "You can use 'dir_id' for current dir, 'res_id', 'res_model' as a reference to the current record, in dynamic folders"),
298 document_directory_dctx()
301 class document_directory_node(osv.osv):
302 _inherit = 'process.node'
304 'directory_id': fields.many2one('document.directory', 'Document directory', ondelete="set null"),
306 document_directory_node()
308 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: