[IMP] Calendar view mode does not scroll on overflow
[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',
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:
81             return False
82         return objid.browse(cr, uid, mid, context=context).res_id
83
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'])
88                 if diro.storage_id:
89                         return diro.storage_id.id
90         objid=self.pool.get('ir.model.data')
91         try:
92                 mid =  objid._get_id(cr, uid, 'document', 'storage_default')
93                 return objid.browse(cr, uid, mid, context=context).res_id
94         except Exception:
95                 return None
96
97     _defaults = {
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,
100         'domain': '[]',
101         'type': 'directory',
102         'ressource_id': 0,
103         'storage_id': _get_def_storage, # Still, it is bad practice to set it everywhere.
104         'resource_find_all': True,
105     }
106     _sql_constraints = [
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.')
110     ]
111     def name_get(self, cr, uid, ids, context=None):
112         res = []
113         if not self.search(cr,uid,[('id','in',ids)]):
114             ids = []
115         for d in self.browse(cr, uid, ids, context=context):
116             s = ''
117             d2 = d
118             while d2 and d2.parent_id:
119                 s = d2.name + (s and ('/' + s) or '')
120                 d2 = d2.parent_id
121             res.append((d.id, s or d.name))
122         return res
123
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
126         """
127         if isinstance(dir_id, (tuple, list)):
128             assert len(dir_id) == 1
129             dir_id = dir_id[0]
130
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)
136             else:
137                 path.append(parent.name)
138                 return path
139         path = []
140         _parent(dir_id, path)
141         return path
142
143     def _check_recursion(self, cr, uid, ids, context=None):
144         level = 100
145         while len(ids):
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()))
148             if not level:
149                 return False
150             level -= 1
151         return True
152
153     _constraints = [
154         (_check_recursion, 'Error! You cannot create recursive directories.', ['parent_id'])
155     ]
156
157     def __init__(self, *args, **kwargs):
158         super(document_directory, self).__init__(*args, **kwargs)
159
160     def onchange_content_id(self, cr, uid, ids, ressource_type_id):
161         return {}
162
163     """
164         PRE:
165             uri: of the form "Sales Order/SO001"
166         PORT:
167             uri
168             object: the object.directory or object.directory.content
169             object2: the other object linked (if object.directory.content)
170     """
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
174         """
175
176         return nodes.get_node_context(cr, uid, context).get_uri(cr, uri)
177
178     def get_node_class(self, cr, uid, ids, dbro=None, dynamic=False, context=None):
179         """Retrieve the class of nodes for this directory
180
181            This function can be overriden by inherited classes ;)
182            @param dbro The browse object, if caller already has it
183         """
184         if dbro is None:
185             dbro = self.browse(cr, uid, ids, context=context)
186
187         if dynamic:
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
193         else:
194             raise ValueError("dir node for %s type.", dbro.type)
195
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
200
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).
204
205         Some databases may override this and attach properties to the
206         node_context. See WebDAV, CalDAV.
207         """
208         return
209
210     def get_dir_permissions(self, cr, uid, ids, context=None):
211         """Check what permission user 'uid' has on directory 'id'
212         """
213         assert len(ids) == 1
214
215         res = 0
216         for pperms in [('read', 5), ('write', 2), ('unlink', 8)]:
217             try:
218                 self.check_access_rule(cr, uid, ids, pperms[0], context=context)
219                 res |= pperms[1]
220             except except_orm:
221                 pass
222         return res
223
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)
227         """
228         return (nodes.node_database(context=ncontext), uri)
229
230     def copy(self, cr, uid, id, default=None, context=None):
231         if not default:
232             default ={}
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)
236
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)
242         if op=='write':
243             for directory in self.browse(cr, uid, ids):
244                 if not name:
245                     name=directory.name
246                 if not parent_id:
247                     parent_id=directory.parent_id and directory.parent_id.id or False
248                 # TODO fix algo
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
251                 if not ressource_id:
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)])
254                 if len(res):
255                     return False
256         if op=='create':
257             res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
258             if len(res):
259                 return False
260         return True
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)
265
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)
270         if newname:
271             for illeg in ('/', '@', '$', '#'):
272                 if illeg in newname:
273                     raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
274         return super(document_directory,self).create(cr, uid, vals, context)
275
276     # TODO def unlink(...
277
278 document_directory()
279
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.
288     """
289     _name = 'document.directory.dctx'
290     _description = 'Directory Dynamic Context'
291     _columns = {
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"),
296         }
297
298 document_directory_dctx()
299
300
301 class document_directory_node(osv.osv):
302     _inherit = 'process.node'
303     _columns = {
304         'directory_id':  fields.many2one('document.directory', 'Document directory', ondelete="set null"),
305     }
306 document_directory_node()
307
308 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: