[IMP]: Generalization of tooltips: Added tooltips for active field
[odoo/odoo.git] / addons / document / document.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #    
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 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 import base64
23
24 from osv import osv, fields
25 from osv.orm import except_orm
26 import urlparse
27
28 import os
29
30 import pooler
31 from content_index import content_index
32 import netsvc
33 import StringIO
34
35 import random
36 import string
37 from psycopg2 import Binary
38 from tools import config
39 import tools
40 from tools.translate import _
41
42 def random_name():
43     random.seed()
44     d = [random.choice(string.ascii_letters) for x in xrange(10) ]
45     name = "".join(d)
46     return name
47
48
49 # Unsupported WebDAV Commands:
50 #     label
51 #     search
52 #     checkin
53 #     checkout
54 #     propget
55 #     propset
56
57 #
58 # An object that represent an uri
59 #   path: the uri of the object
60 #   content: the Content it belongs to (_print.pdf)
61 #   type: content or collection
62 #       content: objct = res.partner
63 #       collection: object = directory, object2 = res.partner
64 #       file: objct = ir.attachement
65 #   root: if we are at the first directory of a ressource
66 #
67 INVALID_CHARS={'*':str(hash('*')), '|':str(hash('|')) , "\\":str(hash("\\")), '/':'__', ':':str(hash(':')), '"':str(hash('"')), '<':str(hash('<')) , '>':str(hash('>')) , '?':str(hash('?'))}
68
69
70 class node_class(object):
71     def __init__(self, cr, uid, path, object, object2=False, context={}, content=False, type='collection', root=False):
72         self.cr = cr
73         self.uid = uid
74         self.path = path
75         self.object = object
76         self.object2 = object2
77         self.context = context
78         self.content = content
79         self.type=type
80         self.root=root
81
82     def _file_get(self, nodename=False):
83         if not self.object:
84             return []
85         pool = pooler.get_pool(self.cr.dbname)
86         fobj = pool.get('ir.attachment')
87         res2 = []
88         where = []
89         if self.object2:
90             where.append( ('res_model','=',self.object2._name) )
91             where.append( ('res_id','=',self.object2.id) )
92         else:
93             where.append( ('parent_id','=',self.object.id) )
94             where.append( ('res_id','=',False) )
95         if nodename:
96             where.append( (fobj._rec_name,'=',nodename) )
97         for content in self.object.content_ids:
98             if self.object2 or not content.include_name:
99                 if content.include_name:
100                     content_name = self.object2.name
101                     obj = pool.get(self.object.ressource_type_id.model)
102                     name_for = obj._name.split('.')[-1]            
103                     if content_name  and content_name.find(name_for) == 0  :
104                         content_name = content_name.replace(name_for,'')
105                     test_nodename = content_name + (content.suffix or '') + (content.extension or '')
106                 else:
107                     test_nodename = (content.suffix or '') + (content.extension or '')
108                 if test_nodename.find('/'):
109                     test_nodename=test_nodename.replace('/', '_')
110                 path = self.path+'/'+test_nodename
111                 if not nodename:
112                     n = node_class(self.cr, self.uid,path, self.object2, False, context=self.context, content=content, type='content', root=False)
113                     res2.append( n)
114                 else:
115                     if nodename == test_nodename:
116                         n = node_class(self.cr, self.uid, path, self.object2, False, context=self.context, content=content, type='content', root=False)
117                         res2.append(n)
118
119         ids = fobj.search(self.cr, self.uid, where+[ ('parent_id','=',self.object and self.object.id or False) ])
120         if self.object and self.root and (self.object.type=='ressource'):
121             ids += fobj.search(self.cr, self.uid, where+[ ('parent_id','=',False) ])
122         res = fobj.browse(self.cr, self.uid, ids, context=self.context)
123         return map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=False), res) + res2
124     
125     def get_translation(self,value,lang):
126         result = value
127         #TODO : to get translation term        
128         return result 
129     
130     def directory_list_for_child(self,nodename,parent=False):
131         pool = pooler.get_pool(self.cr.dbname)
132         where = []
133         if nodename:    
134             nodename = self.get_translation(nodename, self.context['lang'])            
135             where.append(('name','=',nodename))
136         if (self.object and self.object.type=='directory') or not self.object2:
137             where.append(('parent_id','=',self.object and self.object.id or False))
138         else:
139             where.append(('parent_id','=',False))
140         if self.object:
141             where.append(('ressource_parent_type_id','=',self.object.ressource_type_id.id))
142         else:
143             where.append(('ressource_parent_type_id','=',False))
144
145         ids = pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',0)])
146         if self.object2:
147             ids += pool.get('document.directory').search(self.cr, self.uid, where+[('ressource_id','=',self.object2.id)])        
148         res = pool.get('document.directory').browse(self.cr, self.uid, ids, self.context)
149         return res
150
151     def _child_get(self, nodename=False):        
152         if self.type not in ('collection','database'):
153             return []
154         res = self.directory_list_for_child(nodename)
155         result= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name, x, x.type=='directory' and self.object2 or False, context=self.context, root=self.root), res)
156         if self.type=='database':
157             pool = pooler.get_pool(self.cr.dbname)
158             fobj = pool.get('ir.attachment')
159             vargs = [('parent_id','=',False),('res_id','=',False)]
160             if nodename:
161                 vargs.append((fobj._rec_name,'=',nodename))
162             file_ids=fobj.search(self.cr,self.uid,vargs)
163
164             res = fobj.browse(self.cr, self.uid, file_ids, context=self.context)
165             result +=map(lambda x: node_class(self.cr, self.uid, self.path+'/'+eval('x.'+fobj._rec_name), x, False, context=self.context, type='file', root=self.root), res)
166         if self.type=='collection' and self.object.type=="ressource":
167             where = self.object.domain and eval(self.object.domain, {'active_id':self.root, 'uid':self.uid}) or []
168             pool = pooler.get_pool(self.cr.dbname)            
169             obj = pool.get(self.object.ressource_type_id.model)
170             _dirname_field = obj._rec_name
171             if len(obj.fields_get(self.cr, self.uid, ['dirname'])):
172                 _dirname_field = 'dirname'            
173
174             name_for = obj._name.split('.')[-1]            
175             if nodename  and nodename.find(name_for) == 0  :
176                 id = int(nodename.replace(name_for,''))
177                 where.append(('id','=',id))
178             elif nodename:
179                 if nodename.find('__') :
180                     nodename=nodename.replace('__','/')
181                 for invalid in INVALID_CHARS:
182                     if nodename.find(INVALID_CHARS[invalid]) :
183                         nodename=nodename.replace(INVALID_CHARS[invalid],invalid)
184                 nodename = self.get_translation(nodename, self.context['lang'])
185                 where.append((_dirname_field,'=',nodename))
186
187             if self.object.ressource_tree:
188                 if obj._parent_name in obj.fields_get(self.cr,self.uid):                    
189                     where.append((obj._parent_name,'=',self.object2 and self.object2.id or False))
190                     ids = obj.search(self.cr, self.uid, where)
191                     res = obj.browse(self.cr, self.uid, ids,self.context)
192                     result+= map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
193                     return result
194                 else :
195                     if self.object2:
196                         return result
197             else:
198                 if self.object2:
199                     return result
200
201             
202             ids = obj.search(self.cr, self.uid, where)
203             res = obj.browse(self.cr, self.uid, ids,self.context)
204             for r in res:                               
205                 if len(obj.fields_get(self.cr, self.uid, [_dirname_field])):
206                     r.name = eval('r.'+_dirname_field)
207                 else:
208                     r.name = False
209                 if not r.name:
210                     r.name = name_for + '%d'%r.id               
211                 for invalid in INVALID_CHARS:
212                     if r.name.find(invalid) :
213                         r.name = r.name.replace(invalid,INVALID_CHARS[invalid])            
214             result2 = map(lambda x: node_class(self.cr, self.uid, self.path+'/'+x.name.replace('/','__'), self.object, x, context=self.context, root=x.id), res)
215             if result2:
216                 if self.object.ressource_tree:
217                     result += result2
218                 else:
219                     result = result2                  
220         return result
221
222     def children(self):
223         return self._child_get() + self._file_get()
224
225     def child(self, name):
226         res = self._child_get(name)
227         if res:
228             return res[0]
229         res = self._file_get(name)
230         if res:
231             return res[0]
232         return None
233
234     def path_get(self):
235         path = self.path
236         if self.path[0]=='/':
237             path = self.path[1:]
238         return path
239
240 class document_directory(osv.osv):
241     _name = 'document.directory'
242     _description = 'Document directory'
243     _columns = {
244         'name': fields.char('Name', size=64, required=True, select=1),
245         'write_date': fields.datetime('Date Modified', readonly=True),
246         'write_uid':  fields.many2one('res.users', 'Last Modification User', readonly=True),
247         'create_date': fields.datetime('Date Created', readonly=True),
248         'create_uid':  fields.many2one('res.users', 'Creator', readonly=True),
249         'file_type': fields.char('Content Type', size=32),
250         'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."),
251         'user_id': fields.many2one('res.users', 'Owner'),
252         'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
253         'parent_id': fields.many2one('document.directory', 'Parent Item'),
254         'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'),
255         'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'),
256         'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'),
257         'type': fields.selection([('directory','Static Directory'),('ressource','Other Resources')], 'Type', required=True),
258         'ressource_type_id': fields.many2one('ir.model', 'Directories Mapped to Objects',
259             help="Select an object here and Open ERP will create a mapping for each of these " \
260                  "objects, using the given domain, when browsing through FTP."),
261         'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model',
262             help="If you put an object here, this directory template will appear bellow all of these objects. " \
263                  "Don't put a parent directory if you select a parent model."),
264         'ressource_id': fields.integer('Resource ID'),
265         'ressource_tree': fields.boolean('Tree Structure',
266             help="Check this if you want to use the same tree structure as the object selected in the system."),
267     }
268     _defaults = {
269         'user_id': lambda self,cr,uid,ctx: uid,
270         'domain': lambda self,cr,uid,ctx: '[]',
271         'type': lambda *args: 'directory',
272         'ressource_id': lambda *a: 0
273     }
274     _sql_constraints = [
275         ('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !')
276     ]
277
278     def get_resource_path(self,cr,uid,dir_id,res_model,res_id):
279         # this method will be used in process module
280         # to be need test and Improvement if resource dir has parent resource (link resource)
281         path=[]
282         def _parent(dir_id,path):
283             parent=self.browse(cr,uid,dir_id)
284             if parent.parent_id and not parent.ressource_parent_type_id:
285                 _parent(parent.parent_id.id,path)
286                 path.append(parent.name)
287             else:
288                 path.append(parent.name)
289                 return path
290
291         directory=self.browse(cr,uid,dir_id)
292         model_ids=self.pool.get('ir.model').search(cr,uid,[('model','=',res_model)])
293         if directory:
294             _parent(dir_id,path)
295             if res_id:
296                 path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
297             user=self.pool.get('res.users').browse(cr,uid,uid)
298             return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
299         return False
300
301     def _check_recursion(self, cr, uid, ids):
302         level = 100
303         while len(ids):
304             cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str, ids))+')')
305             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
306             if not level:
307                 return False
308             level -= 1
309         return True
310
311     _constraints = [
312         (_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
313     ]
314     def __init__(self, *args, **kwargs):
315         res = super(document_directory, self).__init__(*args, **kwargs)
316         self._cache = {}
317
318     def onchange_content_id(self, cr, uid, ids, ressource_type_id):
319         return {}
320
321     def _get_childs(self, cr, uid, node, nodename=False, context={}):
322         where = []
323         if nodename:
324             nodename = self.get_translation(nodename, self.context['lang'])
325             where.append(('name','=',nodename))
326         if object:
327             where.append(('parent_id','=',object.id))
328         ids = self.search(cr, uid, where, context)
329         return self.browse(cr, uid, ids, context), False
330
331     """
332         PRE:
333             uri: of the form "Sales Order/SO001"
334         PORT:
335             uri
336             object: the object.directory or object.directory.content
337             object2: the other object linked (if object.directory.content)
338     """
339     def get_object(self, cr, uid, uri, context={}):        
340         #TODO : set user's context_lang in context  
341         context.update({'lang':False})      
342         if not uri:
343             return node_class(cr, uid, '', False, context=context, type='database')
344         turi = tuple(uri)
345         if False and (turi in self._cache):
346             (path, oo, oo2, context, content,type,root) = self._cache[turi]
347             if oo:
348                 object = self.pool.get(oo[0]).browse(cr, uid, oo[1], context)
349             else:
350                 object = False
351             if oo2:
352                 object2 = self.pool.get(oo2[0]).browse(cr, uid, oo2[1], context)
353             else:
354                 object2 = False
355             node = node_class(cr, uid, '/', False, context=context, type='database')
356             return node
357
358         node = node_class(cr, uid, '/', False, context=context, type='database')
359         for path in uri[:]:
360             if path:
361                 node = node.child(path)
362                 if not node:
363                     return False
364         oo = node.object and (node.object._name, node.object.id) or False
365         oo2 = node.object2 and (node.object2._name, node.object2.id) or False
366         self._cache[turi] = (node.path, oo, oo2, node.context, node.content,node.type,node.root)
367         return node
368
369     def get_childs(self, cr, uid, uri, context={}):
370         node = self.get_object(cr, uid, uri, context)
371         if uri:
372             children = node.children()
373         else:
374             children= [node]
375         result = map(lambda node: node.path_get(), children)
376         #childs,object2 = self._get_childs(cr, uid, object, False, context)
377         #result = map(lambda x: urlparse.urljoin(path+'/',x.name), childs)
378         return result
379
380     def copy(self, cr, uid, id, default=None, context=None):
381         if not default:
382             default ={}
383         name = self.read(cr, uid, [id])[0]['name']
384         default.update({'name': name+ " (copy)"})
385         return super(document_directory,self).copy(cr,uid,id,default,context)
386
387     def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
388         name=vals.get('name',False)
389         parent_id=vals.get('parent_id',False)
390         ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
391         ressource_id=vals.get('ressource_id',0)
392         if op=='write':
393             for directory in self.browse(cr,uid,ids):
394                 if not name:
395                     name=directory.name
396                 if not parent_id:
397                     parent_id=directory.parent_id and directory.parent_id.id or False
398                 if not ressource_parent_type_id:
399                     ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
400                 if not ressource_id:
401                     ressource_id=directory.ressource_id and directory.ressource_id or 0
402                 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)])
403                 if len(res):
404                     return False
405         if op=='create':
406             res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
407             if len(res):
408                 return False
409         return True
410     def write(self, cr, uid, ids, vals, context=None):
411         if not self._check_duplication(cr,uid,vals,ids,op='write'):
412             raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
413         return super(document_directory,self).write(cr,uid,ids,vals,context=context)
414
415     def create(self, cr, uid, vals, context=None):
416         if not self._check_duplication(cr,uid,vals):
417             raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
418         if vals.get('name',False) and (vals.get('name').find('/')+1 or vals.get('name').find('@')+1 or vals.get('name').find('$')+1 or vals.get('name').find('#')+1) :
419             raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
420         return super(document_directory,self).create(cr, uid, vals, context)
421
422 document_directory()
423
424 class document_directory_node(osv.osv):
425     _inherit = 'process.node'
426     _columns = {
427         'directory_id':  fields.many2one('document.directory', 'Document directory', ondelete="set null"),
428     }
429 document_directory_node()
430
431 class document_directory_content_type(osv.osv):
432     _name = 'document.directory.content.type'
433     _description = 'Directory Content Type'
434     _columns = {
435         'name': fields.char('Content Type', size=64, required=True),
436         'code': fields.char('Extension', size=4),
437         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the directory content type without removing it."),
438     }
439     _defaults = {
440         'active': lambda *args: 1
441     }
442 document_directory_content_type()
443
444 class document_directory_content(osv.osv):
445     _name = 'document.directory.content'
446     _description = 'Directory Content'
447     _order = "sequence"
448     def _extension_get(self, cr, uid, context={}):
449         cr.execute('select code,name from document_directory_content_type where active')
450         res = cr.fetchall()
451         return res
452     _columns = {
453         'name': fields.char('Content Name', size=64, required=True),
454         'sequence': fields.integer('Sequence', size=16),
455         'suffix': fields.char('Suffix', size=16),
456         'report_id': fields.many2one('ir.actions.report.xml', 'Report'),
457         'extension': fields.selection(_extension_get, 'Document Type', required=True, size=4),
458         'include_name': fields.boolean('Include Record Name', help="Check this field if you want that the name of the file start by the record name."),
459         'directory_id': fields.many2one('document.directory', 'Directory'),
460     }
461     _defaults = {
462         'extension': lambda *args: '.pdf',
463         'sequence': lambda *args: 1,
464         'include_name': lambda *args: 1,
465     }
466     def process_write_pdf(self, cr, uid, node, context={}):
467         return True
468     def process_read_pdf(self, cr, uid, node, context={}):
469         report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.content.report_id.id)
470         srv = netsvc.LocalService('report.'+report.report_name)
471         pdf,pdftype = srv.create(cr, uid, [node.object.id], {}, {})
472         s = StringIO.StringIO(pdf)
473         s.name = node
474         return s
475 document_directory_content()
476
477 class ir_action_report_xml(osv.osv):
478     _name="ir.actions.report.xml"
479     _inherit ="ir.actions.report.xml"
480
481     def _model_get(self, cr, uid, ids, name, arg, context):
482         res = {}
483         model_pool = self.pool.get('ir.model')
484         for data in self.read(cr,uid,ids,['model']):
485             model = data.get('model',False)
486             if model:
487                 model_id =model_pool.search(cr,uid,[('model','=',model)])
488                 if model_id:
489                     res[data.get('id')] = model_id[0]
490                 else:
491                     res[data.get('id')] = False
492         return res
493
494     def _model_search(self, cr, uid, obj, name, args):
495         if not len(args):
496             return []
497         model_id= args[0][2]
498         if not model_id:
499             return []
500         model = self.pool.get('ir.model').read(cr,uid,[model_id])[0]['model']
501         report_id = self.search(cr,uid,[('model','=',model)])
502         if not report_id:
503             return [('id','=','0')]
504         return [('id','in',report_id)]
505
506     _columns={
507         'model_id' : fields.function(_model_get,fnct_search=_model_search,method=True,string='Model Id'),
508     }
509
510 ir_action_report_xml()
511
512 def create_directory(path):
513     dir_name = random_name()
514     path = os.path.join(path,dir_name)
515     os.makedirs(path)
516     return dir_name
517
518 class document_file(osv.osv):
519     _inherit = 'ir.attachment'
520     _rec_name = 'datas_fname'
521     def _get_filestore(self, cr):
522         return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
523
524     def _data_get(self, cr, uid, ids, name, arg, context):
525         result = {}
526         cr.execute('select id,store_fname,link from ir_attachment where id in ('+','.join(map(str, ids))+')')
527         for id,r,l in cr.fetchall():
528             try:
529                 value = file(os.path.join(self._get_filestore(cr), r), 'rb').read()
530                 result[id] = base64.encodestring(value)
531             except:
532                 result[id]=''
533 #            if context.get('bin_size', False):
534 #                result[id] = tools.human_size(result[id])
535         return result
536
537     #
538     # This code can be improved
539     #
540     def _data_set(self, cr, uid, id, name, value, args=None, context={}):
541         if not value:
542             filename = self.browse(cr, uid, id, context).store_fname
543             try:
544                 os.unlink(os.path.join(self._get_filestore(cr), filename))
545             except:
546                 pass
547             cr.execute('update ir_attachment set store_fname=NULL WHERE id=%s', (id,) )
548             return True
549         #if (not context) or context.get('store_method','fs')=='fs':
550         try:
551             path = self._get_filestore(cr)
552             if not os.path.isdir(path):
553                 try:
554                     os.makedirs(path)
555                 except:
556                     raise except_orm(_('Permission Denied !'), _('You do not permissions to write on the server side.'))
557
558             flag = None
559             # This can be improved
560             for dirs in os.listdir(path):
561                 if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
562                     flag = dirs
563                     break
564             flag = flag or create_directory(path)
565             filename = random_name()
566             fname = os.path.join(path, flag, filename)
567             fp = file(fname,'wb')
568             v = base64.decodestring(value)
569             fp.write(v)
570             filesize = os.stat(fname).st_size
571             cr.execute('update ir_attachment set store_fname=%s,store_method=%s,file_size=%s where id=%s', (os.path.join(flag,filename),'fs',len(v),id))
572             return True
573         except Exception,e :
574             raise except_orm(_('Error!'), str(e))
575
576     _columns = {
577         'user_id': fields.many2one('res.users', 'Owner', select=1),
578         'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
579         'parent_id': fields.many2one('document.directory', 'Directory', select=1),
580         'file_size': fields.integer('File Size', required=True),
581         'file_type': fields.char('Content Type', size=32),
582         'index_content': fields.text('Indexed Content'),
583         'write_date': fields.datetime('Date Modified', readonly=True),
584         'write_uid':  fields.many2one('res.users', 'Last Modification User', readonly=True),
585         'create_date': fields.datetime('Date Created', readonly=True),
586         'create_uid':  fields.many2one('res.users', 'Creator', readonly=True),
587         'store_method': fields.selection([('db','Database'),('fs','Filesystem'),('link','Link')], "Storing Method"),
588         'datas': fields.function(_data_get,method=True,fnct_inv=_data_set,string='File Content',type="binary"),
589         'store_fname': fields.char('Stored Filename', size=200),
590         'res_model': fields.char('Attached Model', size=64), #res_model
591         'res_id': fields.integer('Attached ID'), #res_id
592         'partner_id':fields.many2one('res.partner', 'Partner', select=1),
593         'title': fields.char('Resource Title',size=64),
594     }
595
596     _defaults = {
597         'user_id': lambda self,cr,uid,ctx:uid,
598         'file_size': lambda self,cr,uid,ctx:0,
599         'store_method': lambda *args: 'db'
600     }
601     _sql_constraints = [
602         ('filename_uniq', 'unique (name,parent_id,res_id,res_model)', 'The file name must be unique !')
603     ]
604     def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
605         name=vals.get('name',False)
606         parent_id=vals.get('parent_id',False)
607         res_model=vals.get('res_model',False)
608         res_id=vals.get('res_id',0)
609         if op=='write':
610             for file in self.browse(cr,uid,ids):
611                 if not name:
612                     name=file.name
613                 if not parent_id:
614                     parent_id=file.parent_id and file.parent_id.id or False
615                 if not res_model:
616                     res_model=file.res_model and file.res_model or False
617                 if not res_id:
618                     res_id=file.res_id and file.res_id or 0
619                 res=self.search(cr,uid,[('id','<>',file.id),('name','=',name),('parent_id','=',parent_id),('res_model','=',res_model),('res_id','=',res_id)])
620                 if len(res):
621                     return False
622         if op=='create':
623             res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('res_id','=',res_id),('res_model','=',res_model)])
624             if len(res):
625                 return False
626         return True
627     def copy(self, cr, uid, id, default=None, context=None):
628         if not default:
629             default ={}
630         name = self.read(cr, uid, [id])[0]['name']
631         default.update({'name': name+ " (copy)"})
632         return super(document_file,self).copy(cr,uid,id,default,context)
633     def write(self, cr, uid, ids, vals, context=None):
634         res=self.search(cr,uid,[('id','in',ids)])
635         if not len(res):
636             return False
637         if not self._check_duplication(cr,uid,vals,ids,'write'):
638             raise except_orm(_('ValidateError'), _('File name must be unique!'))
639         result = super(document_file,self).write(cr,uid,ids,vals,context=context)
640         cr.commit()
641         try:
642             for f in self.browse(cr, uid, ids, context=context):
643                 #if 'datas' not in vals:
644                 #    vals['datas']=f.datas
645                 res = content_index(base64.decodestring(vals['datas']), f.datas_fname, f.file_type or None)
646                 super(document_file,self).write(cr, uid, ids, {
647                     'index_content': res
648                 })
649             cr.commit()
650         except:
651             pass
652         return result
653
654     def create(self, cr, uid, vals, context={}):
655         vals['title']=vals['name']
656         vals['parent_id'] = context.get('parent_id',False) or vals.get('parent_id',False)
657         if not vals.get('res_id', False) and context.get('default_res_id',False):
658             vals['res_id']=context.get('default_res_id',False)
659         if not vals.get('res_model', False) and context.get('default_res_model',False):
660             vals['res_model']=context.get('default_res_model',False)
661         if vals.get('res_id', False) and vals.get('res_model',False):
662             obj_model=self.pool.get(vals['res_model'])
663             result = obj_model.read(cr, uid, [vals['res_id']], context=context)
664             if len(result):
665                 obj=result[0]
666                 if obj.get('name',False):
667                     vals['title'] = (obj.get('name',''))[:60]
668                 if obj_model._name=='res.partner':
669                     vals['partner_id']=obj['id']
670                 elif obj.get('address_id',False):
671                     if isinstance(obj['address_id'],tuple) or isinstance(obj['address_id'],list):
672                         address_id=obj['address_id'][0]
673                     else:
674                         address_id=obj['address_id']
675                     address=self.pool.get('res.partner.address').read(cr,uid,[address_id],context=context)
676                     if len(address):
677                         vals['partner_id']=address[0]['partner_id'][0] or False
678                 elif obj.get('partner_id',False):
679                     if isinstance(obj['partner_id'],tuple) or isinstance(obj['partner_id'],list):
680                         vals['partner_id']=obj['partner_id'][0]
681                     else:
682                         vals['partner_id']=obj['partner_id']
683
684         datas=None
685         if vals.get('link',False) :
686             import urllib
687             datas=base64.encodestring(urllib.urlopen(vals['link']).read())
688         else:
689             datas = vals.get('datas',False)
690         
691         vals['file_size']= datas and len(datas) or 0
692         if not self._check_duplication(cr,uid,vals):
693             raise except_orm(_('ValidateError'), _('File name must be unique!'))
694         result = super(document_file,self).create(cr, uid, vals, context)
695         cr.commit()
696         try:
697             res = content_index(base64.decodestring(datas), vals['datas_fname'], vals.get('content_type', None))
698             super(document_file,self).write(cr, uid, [result], {
699                 'index_content': res,
700             })
701             cr.commit()
702         except:
703             pass
704         return result
705
706     def unlink(self,cr, uid, ids, context={}):
707         for f in self.browse(cr, uid, ids, context):
708             #if f.store_method=='fs':
709             try:
710                 os.unlink(os.path.join(self._get_filestore(cr), f.store_fname))
711             except:
712                 pass
713         return super(document_file, self).unlink(cr, uid, ids, context)
714 document_file()
715
716 class document_configuration_wizard(osv.osv_memory):
717     _name='document.configuration.wizard'
718     _rec_name = 'Auto Directory configuration'
719     _columns = {
720         'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
721             "Keep localhost if you don't know what to write.", required=True),
722         'suggested_host': fields.char('Suggested Server Address', size=64, readonly=True, help="This is the guessed server address"),
723         'port': fields.char('Server Port', size=5, help="Put here the server port. " \
724             "Keep 8021 if you don't know what to write.", required=True)
725     }
726
727     def get_ftp_server_address(self, cr, uid, context=None):
728         default_address = config.get('ftp_server_address', None)
729         if default_address is None:
730             default_address = tools.misc.detect_ip_addr()
731
732         return default_address
733
734     def get_ftp_server_port(self, cr, uid, context=None):
735         return config.get('ftp_server_port', 8021)
736
737     def get_suggested_ftp_server_address(self, cr, uid, context=None):
738         return tools.misc.detect_ip_addr()
739
740     _defaults = {
741         'host': get_ftp_server_address,
742         'suggested_host': get_suggested_ftp_server_address,
743         'port': get_ftp_server_port,
744     }
745
746     def action_cancel(self,cr,uid,ids,conect=None):
747         return {
748                 'view_type': 'form',
749                 "view_mode": 'form',
750                 'res_model': 'ir.actions.configuration.wizard',
751                 'type': 'ir.actions.act_window',
752                 'target':'new',
753          }
754
755     def action_config(self, cr, uid, ids, context=None):
756         conf = self.browse(cr, uid, ids[0], context)
757         obj=self.pool.get('document.directory')
758         objid=self.pool.get('ir.model.data')
759
760         if self.pool.get('sale.order'):
761             id = objid._get_id(cr, uid, 'document', 'dir_sale_order_all')
762             id = objid.browse(cr, uid, id, context=context).res_id
763             mid = self.pool.get('ir.model').search(cr, uid, [('model','=','sale.order')])
764             obj.write(cr, uid, [id], {
765                 'type':'ressource',
766                 'ressource_type_id': mid[0],
767                 'domain': '[]',
768             })
769             aid = objid._get_id(cr, uid, 'sale', 'report_sale_order')
770             aid = objid.browse(cr, uid, aid, context=context).res_id
771
772             self.pool.get('document.directory.content').create(cr, uid, {
773                 'name': "Print Order",
774                 'suffix': "_print",
775                 'report_id': aid,
776                 'extension': '.pdf',
777                 'include_name': 1,
778                 'directory_id': id,
779             })
780             id = objid._get_id(cr, uid, 'document', 'dir_sale_order_quote')
781             id = objid.browse(cr, uid, id, context=context).res_id
782             obj.write(cr, uid, [id], {
783                 'type':'ressource',
784                 'ressource_type_id': mid[0],
785                 'domain': "[('state','=','draft')]",
786             })
787
788         if self.pool.get('product.product'):
789             id = objid._get_id(cr, uid, 'document', 'dir_product')
790             id = objid.browse(cr, uid, id, context=context).res_id
791             mid = self.pool.get('ir.model').search(cr, uid, [('model','=','product.product')])
792             obj.write(cr, uid, [id], {
793                 'type':'ressource',
794                 'ressource_type_id': mid[0],
795             })
796
797         if self.pool.get('stock.location'):
798             aid = objid._get_id(cr, uid, 'stock', 'report_product_history')
799             aid = objid.browse(cr, uid, aid, context=context).res_id
800
801             self.pool.get('document.directory.content').create(cr, uid, {
802                 'name': "Product Stock",
803                 'suffix': "_stock_forecast",
804                 'report_id': aid,
805                 'extension': '.pdf',
806                 'include_name': 1,
807                 'directory_id': id,
808             })
809
810         if self.pool.get('account.analytic.account'):
811             id = objid._get_id(cr, uid, 'document', 'dir_project')
812             id = objid.browse(cr, uid, id, context=context).res_id
813             mid = self.pool.get('ir.model').search(cr, uid, [('model','=','account.analytic.account')])
814             obj.write(cr, uid, [id], {
815                 'type':'ressource',
816                 'ressource_type_id': mid[0],
817                 'domain': '[]',
818                 'ressource_tree': 1
819         })
820
821         aid = objid._get_id(cr, uid, 'document', 'action_document_browse')
822         aid = objid.browse(cr, uid, aid, context=context).res_id
823         self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
824
825         config['ftp_server_address'] = conf.host
826         config['ftp_server_port'] = conf.port
827         config.save()
828
829         return {
830                 'view_type': 'form',
831                 "view_mode": 'form',
832                 'res_model': 'ir.actions.configuration.wizard',
833                 'type': 'ir.actions.act_window',
834                 'target': 'new',
835         }
836
837 document_configuration_wizard()
838
839
840 class document_configuration_ftpserver_wizard(osv.osv_memory):
841     _name='document.configuration.ftp_server.wizard'
842     _rec_name = 'Configure FTP server address'
843     _columns = {
844         'host': fields.char('Server Address', size=64, help="Put here the server address or IP. " \
845             "Keep localhost if you don't know what to write.", required=True),
846         'suggested_host': fields.char('Suggested Server Address', size=64, readonly=True, help="This is the guessed server address"),
847         'port': fields.char('Server Port', size=5, help="Put here the server port. " \
848             "Keep 8021 if you don't know what to write.", required=True)
849     }
850
851     def get_ftp_server_address(self, cr, uid, context=None):
852         default_address = tools.misc.detect_ip_addr()
853         return config.get('ftp_server_address', default_address)
854
855     def get_ftp_server_port(self, cr, uid, context=None):
856         return config.get('ftp_server_port', '8021')
857
858     def get_suggested_ftp_server_address(self, cr, uid, context=None):
859         return tools.misc.detect_ip_addr()
860
861     _defaults = {
862         'host': get_ftp_server_address,
863         'suggested_host': get_suggested_ftp_server_address,
864         'port': get_ftp_server_port,
865     }
866
867     def action_cancel(self,cr,uid,ids,conect=None):
868         return {}
869
870     def action_config(self, cr, uid, ids, context=None):
871         conf = self.browse(cr, uid, ids[0], context)
872         obj = self.pool.get('ir.model.data')
873
874         aid = obj._get_id(cr, uid, 'document', 'action_document_browse')
875         aid = obj.browse(cr, uid, aid, context=context).res_id
876         self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
877
878         config['ftp_server_address'] = conf.host
879         config['ftp_server_port'] = conf.port
880         config.save()
881
882         return {}
883
884 document_configuration_ftpserver_wizard()
885