[FIX] improved the code
[odoo/odoo.git] / addons / document / document_directory.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22
23 from osv import osv, fields
24 from osv.orm import except_orm
25 import logging
26 import nodes
27 from tools.translate import _
28 _logger = logging.getLogger(__name__)
29 class document_directory(osv.osv):
30     _name = 'document.directory'
31     _description = 'Directory'
32     _order = 'name'
33     _columns = {
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'),
50             ],
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),
69     }
70
71
72     def _get_root_directory(self, cr,uid, context=None):
73         objid=self.pool.get('ir.model.data')
74         try:
75             mid = objid._get_id(cr, uid, 'document', 'dir_root')
76             if not mid:
77                 return False
78             root_id = objid.read(cr, uid, mid, ['res_id'])['res_id']
79             return root_id
80         except Exception, e:
81
82             _logger.warning('Cannot set directory root:'+ str(e))
83             return False
84         return objid.browse(cr, uid, mid, context=context).res_id
85
86     def _get_def_storage(self, cr, uid, context=None):
87         if context and context.has_key('default_parent_id'):
88                 # Use the same storage as the parent..
89                 diro = self.browse(cr, uid, context['default_parent_id'])
90                 if diro.storage_id:
91                         return diro.storage_id.id
92         objid=self.pool.get('ir.model.data')
93         try:
94                 mid =  objid._get_id(cr, uid, 'document', 'storage_default')
95                 return objid.browse(cr, uid, mid, context=context).res_id
96         except Exception:
97                 return None
98
99     _defaults = {
100         'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'document.directory', context=c),
101         'user_id': lambda self,cr,uid,ctx: uid,
102         'domain': '[]',
103         'type': 'directory',
104         'ressource_id': 0,
105         'storage_id': _get_def_storage, # Still, it is bad practice to set it everywhere.
106         'resource_find_all': True,
107     }
108     _sql_constraints = [
109         ('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !'),
110         ('no_selfparent', 'check(parent_id <> id)', 'Directory cannot be parent of itself!'),
111         ('dir_parented', 'check(parent_id IS NOT NULL OR storage_id IS NOT NULL)', 'Directory must have a parent or a storage.')
112     ]
113     def name_get(self, cr, uid, ids, context=None):
114         res = []
115         if not self.search(cr,uid,[('id','in',ids)]):
116             ids = []
117         for d in self.browse(cr, uid, ids, context=context):
118             s = ''
119             d2 = d
120             while d2 and d2.parent_id:
121                 s = d2.name + (s and ('/' + s) or '')
122                 d2 = d2.parent_id
123             res.append((d.id, s or d.name))
124         return res
125
126     def get_full_path(self, cr, uid, dir_id, context=None):
127         """ Return the full path to this directory, in a list, root first
128         """
129         if isinstance(dir_id, (tuple, list)):
130             assert len(dir_id) == 1
131             dir_id = dir_id[0]
132
133         def _parent(dir_id, path):
134             parent=self.browse(cr, uid, dir_id)
135             if parent.parent_id and not parent.ressource_parent_type_id:
136                 _parent(parent.parent_id.id,path)
137                 path.append(parent.name)
138             else:
139                 path.append(parent.name)
140                 return path
141         path = []
142         _parent(dir_id, path)
143         return path
144
145     def _check_recursion(self, cr, uid, ids, context=None):
146         level = 100
147         while len(ids):
148             cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str,ids))+')')
149             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
150             if not level:
151                 return False
152             level -= 1
153         return True
154
155     _constraints = [
156         (_check_recursion, 'Error! You cannot create recursive directories.', ['parent_id'])
157     ]
158
159     def __init__(self, *args, **kwargs):
160         super(document_directory, self).__init__(*args, **kwargs)
161
162     def onchange_content_id(self, cr, uid, ids, ressource_type_id):
163         return {}
164
165     """
166         PRE:
167             uri: of the form "Sales Order/SO001"
168         PORT:
169             uri
170             object: the object.directory or object.directory.content
171             object2: the other object linked (if object.directory.content)
172     """
173     def get_object(self, cr, uid, uri, context=None):
174         """ Return a node object for the given uri.
175            This fn merely passes the call to node_context
176         """
177
178         return nodes.get_node_context(cr, uid, context).get_uri(cr, uri)
179
180     def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None):
181         """Retrieve the class of nodes for this directory
182
183            This function can be overriden by inherited classes ;)
184            @param dbro The browse object, if caller already has it
185         """
186         if dbro is None:
187             dbro = self.browse(cr, uid, ids, context=context)
188
189         if dynamic:
190             return nodes.node_res_obj
191         elif dbro.type == 'directory':
192             return nodes.node_dir
193         elif dbro.type == 'ressource':
194             return nodes.node_res_dir
195         else:
196             raise ValueError("dir node for %s type.", dbro.type)
197
198     def _prepare_context(self, cr, uid, nctx, context=None):
199         """ Fill nctx with properties for this database
200         @param nctx instance of nodes.node_context, to be filled
201         @param context ORM context (dict) for us
202
203         Note that this function is called *without* a list of ids,
204         it should behave the same for the whole database (based on the
205         ORM instance of document.directory).
206
207         Some databases may override this and attach properties to the
208         node_context. See WebDAV, CalDAV.
209         """
210         return
211
212     def get_dir_permissions(self, cr, uid, ids, context=None):
213         """Check what permission user 'uid' has on directory 'id'
214         """
215         assert len(ids) == 1
216
217         res = 0
218         for pperms in [('read', 5), ('write', 2), ('unlink', 8)]:
219             try:
220                 self.check_access_rule(cr, uid, ids, pperms[0], context=context)
221                 res |= pperms[1]
222             except except_orm:
223                 pass
224         return res
225
226     def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext):
227         """ try to locate the node in uri,
228             Return a tuple (node_dir, remaining_path)
229         """
230         return (nodes.node_database(context=ncontext), uri)
231
232     def copy(self, cr, uid, id, default=None, context=None):
233         if not default:
234             default ={}
235         name = self.read(cr, uid, [id])[0]['name']
236         default.update({'name': name+ " (copy)"})
237         return super(document_directory,self).copy(cr, uid, id, default, context=context)
238
239     def _check_duplication(self, cr, uid, vals, ids=[], op='create'):
240         name=vals.get('name',False)
241         parent_id=vals.get('parent_id',False)
242         ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
243         ressource_id=vals.get('ressource_id',0)
244         if op=='write':
245             for directory in self.browse(cr, uid, ids):
246                 if not name:
247                     name=directory.name
248                 if not parent_id:
249                     parent_id=directory.parent_id and directory.parent_id.id or False
250                 # TODO fix algo
251                 if not ressource_parent_type_id:
252                     ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
253                 if not ressource_id:
254                     ressource_id=directory.ressource_id and directory.ressource_id or 0
255                 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)])
256                 if len(res):
257                     return False
258         if op=='create':
259             res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
260             if len(res):
261                 return False
262         return True
263     def write(self, cr, uid, ids, vals, context=None):
264         if not self._check_duplication(cr, uid, vals, ids, op='write'):
265             raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
266         return super(document_directory,self).write(cr, uid, ids, vals, context=context)
267
268     def create(self, cr, uid, vals, context=None):
269         if not self._check_duplication(cr, uid, vals):
270             raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
271         newname = vals.get('name',False)
272         if newname:
273             for illeg in ('/', '@', '$', '#'):
274                 if illeg in newname:
275                     raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
276         return super(document_directory,self).create(cr, uid, vals, context)
277
278     # TODO def unlink(...
279
280 document_directory()
281
282 class document_directory_dctx(osv.osv):
283     """ In order to evaluate dynamic folders, child items could have a limiting
284         domain expression. For that, their parents will export a context where useful
285         information will be passed on.
286         If you define sth like "s_id" = "this.id" at a folder iterating over sales, its
287         children could have a domain like [('sale_id', = ,dctx_s_id )]
288         This system should be used recursively, that is, parent dynamic context will be
289         appended to all children down the tree.
290     """
291     _name = 'document.directory.dctx'
292     _description = 'Directory Dynamic Context'
293     _columns = {
294         'dir_id': fields.many2one('document.directory', 'Directory', required=True, ondelete="cascade"),
295         '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."),
296         'expr': fields.char('Expression', size=64, required=True, help="A python expression used to evaluate the field.\n" + \
297                 "You can use 'dir_id' for current dir, 'res_id', 'res_model' as a reference to the current record, in dynamic folders"),
298         }
299
300 document_directory_dctx()
301
302
303 class document_directory_node(osv.osv):
304     _inherit = 'process.node'
305     _columns = {
306         'directory_id':  fields.many2one('document.directory', 'Document directory', ondelete="set null"),
307     }
308 document_directory_node()
309
310 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: