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