[imp] simplify interface of config screen for DMS ftp server
[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         'company_id': fields.many2one('res.company', 'Company'),
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         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'document.directory', c),
274         'ressource_id': lambda *a: 0
275     }
276     _sql_constraints = [
277         ('dirname_uniq', 'unique (name,parent_id,ressource_id,ressource_parent_type_id)', 'The directory name must be unique !')
278     ]
279
280     def get_resource_path(self,cr,uid,dir_id,res_model,res_id):
281         # this method will be used in process module
282         # to be need test and Improvement if resource dir has parent resource (link resource)
283         path=[]
284         def _parent(dir_id,path):
285             parent=self.browse(cr,uid,dir_id)
286             if parent.parent_id and not parent.ressource_parent_type_id:
287                 _parent(parent.parent_id.id,path)
288                 path.append(parent.name)
289             else:
290                 path.append(parent.name)
291                 return path
292
293         directory=self.browse(cr,uid,dir_id)
294         model_ids=self.pool.get('ir.model').search(cr,uid,[('model','=',res_model)])
295         if directory:
296             _parent(dir_id,path)
297             if res_id:
298                 path.append(self.pool.get(directory.ressource_type_id.model).browse(cr,uid,res_id).name)
299             user=self.pool.get('res.users').browse(cr,uid,uid)
300             return "ftp://%s:%s@localhost:%s/%s/%s"%(user.login,user.password,config.get('ftp_server_port',8021),cr.dbname,'/'.join(path))
301         return False
302
303     def _check_recursion(self, cr, uid, ids):
304         level = 100
305         while len(ids):
306             cr.execute('select distinct parent_id from document_directory where id in ('+','.join(map(str, ids))+')')
307             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
308             if not level:
309                 return False
310             level -= 1
311         return True
312
313     _constraints = [
314         (_check_recursion, 'Error! You can not create recursive Directories.', ['parent_id'])
315     ]
316     def __init__(self, *args, **kwargs):
317         res = super(document_directory, self).__init__(*args, **kwargs)
318         self._cache = {}
319
320     def onchange_content_id(self, cr, uid, ids, ressource_type_id):
321         return {}
322
323     def _get_childs(self, cr, uid, node, nodename=False, context={}):
324         where = []
325         if nodename:
326             nodename = self.get_translation(nodename, self.context['lang'])
327             where.append(('name','=',nodename))
328         if object:
329             where.append(('parent_id','=',object.id))
330         ids = self.search(cr, uid, where, context)
331         return self.browse(cr, uid, ids, context), False
332
333     """
334         PRE:
335             uri: of the form "Sales Order/SO001"
336         PORT:
337             uri
338             object: the object.directory or object.directory.content
339             object2: the other object linked (if object.directory.content)
340     """
341     def get_object(self, cr, uid, uri, context={}):        
342         #TODO : set user's context_lang in context  
343         context.update({'lang':False})      
344         if not uri:
345             return node_class(cr, uid, '', False, context=context, type='database')
346         turi = tuple(uri)
347         if False and (turi in self._cache):
348             (path, oo, oo2, context, content,type,root) = self._cache[turi]
349             if oo:
350                 object = self.pool.get(oo[0]).browse(cr, uid, oo[1], context)
351             else:
352                 object = False
353             if oo2:
354                 object2 = self.pool.get(oo2[0]).browse(cr, uid, oo2[1], context)
355             else:
356                 object2 = False
357             node = node_class(cr, uid, '/', False, context=context, type='database')
358             return node
359
360         node = node_class(cr, uid, '/', False, context=context, type='database')
361         for path in uri[:]:
362             if path:
363                 node = node.child(path)
364                 if not node:
365                     return False
366         oo = node.object and (node.object._name, node.object.id) or False
367         oo2 = node.object2 and (node.object2._name, node.object2.id) or False
368         self._cache[turi] = (node.path, oo, oo2, node.context, node.content,node.type,node.root)
369         return node
370
371     def get_childs(self, cr, uid, uri, context={}):
372         node = self.get_object(cr, uid, uri, context)
373         if uri:
374             children = node.children()
375         else:
376             children= [node]
377         result = map(lambda node: node.path_get(), children)
378         #childs,object2 = self._get_childs(cr, uid, object, False, context)
379         #result = map(lambda x: urlparse.urljoin(path+'/',x.name), childs)
380         return result
381
382     def copy(self, cr, uid, id, default=None, context=None):
383         if not default:
384             default ={}
385         name = self.read(cr, uid, [id])[0]['name']
386         default.update({'name': name+ " (copy)"})
387         return super(document_directory,self).copy(cr,uid,id,default,context)
388
389     def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
390         name=vals.get('name',False)
391         parent_id=vals.get('parent_id',False)
392         ressource_parent_type_id=vals.get('ressource_parent_type_id',False)
393         ressource_id=vals.get('ressource_id',0)
394         if op=='write':
395             for directory in self.browse(cr,uid,ids):
396                 if not name:
397                     name=directory.name
398                 if not parent_id:
399                     parent_id=directory.parent_id and directory.parent_id.id or False
400                 if not ressource_parent_type_id:
401                     ressource_parent_type_id=directory.ressource_parent_type_id and directory.ressource_parent_type_id.id or False
402                 if not ressource_id:
403                     ressource_id=directory.ressource_id and directory.ressource_id or 0
404                 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)])
405                 if len(res):
406                     return False
407         if op=='create':
408             res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('ressource_parent_type_id','=',ressource_parent_type_id),('ressource_id','=',ressource_id)])
409             if len(res):
410                 return False
411         return True
412     def write(self, cr, uid, ids, vals, context=None):
413         if not self._check_duplication(cr,uid,vals,ids,op='write'):
414             raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
415         return super(document_directory,self).write(cr,uid,ids,vals,context=context)
416
417     def create(self, cr, uid, vals, context=None):
418         if not self._check_duplication(cr,uid,vals):
419             raise osv.except_osv(_('ValidateError'), _('Directory name must be unique!'))
420         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) :
421             raise osv.except_osv(_('ValidateError'), _('Directory name contains special characters!'))
422         return super(document_directory,self).create(cr, uid, vals, context)
423
424 document_directory()
425
426 class document_directory_node(osv.osv):
427     _inherit = 'process.node'
428     _columns = {
429         'directory_id':  fields.many2one('document.directory', 'Document directory', ondelete="set null"),
430     }
431 document_directory_node()
432
433 class document_directory_content_type(osv.osv):
434     _name = 'document.directory.content.type'
435     _description = 'Directory Content Type'
436     _columns = {
437         'name': fields.char('Content Type', size=64, required=True),
438         'code': fields.char('Extension', size=4),
439         'active': fields.boolean('Active'),
440     }
441     _defaults = {
442         'active': lambda *args: 1
443     }
444 document_directory_content_type()
445
446 class document_directory_content(osv.osv):
447     _name = 'document.directory.content'
448     _description = 'Directory Content'
449     _order = "sequence"
450     def _extension_get(self, cr, uid, context={}):
451         cr.execute('select code,name from document_directory_content_type where active')
452         res = cr.fetchall()
453         return res
454     _columns = {
455         'name': fields.char('Content Name', size=64, required=True),
456         'sequence': fields.integer('Sequence', size=16),
457         'suffix': fields.char('Suffix', size=16),
458         'report_id': fields.many2one('ir.actions.report.xml', 'Report'),
459         'extension': fields.selection(_extension_get, 'Document Type', required=True, size=4),
460         '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."),
461         'directory_id': fields.many2one('document.directory', 'Directory'),
462     }
463     _defaults = {
464         'extension': lambda *args: '.pdf',
465         'sequence': lambda *args: 1,
466         'include_name': lambda *args: 1,
467     }
468     def process_write_pdf(self, cr, uid, node, context={}):
469         return True
470     def process_read_pdf(self, cr, uid, node, context={}):
471         report = self.pool.get('ir.actions.report.xml').browse(cr, uid, node.content.report_id.id)
472         srv = netsvc.LocalService('report.'+report.report_name)
473         pdf,pdftype = srv.create(cr, uid, [node.object.id], {}, {})
474         s = StringIO.StringIO(pdf)
475         s.name = node
476         return s
477 document_directory_content()
478
479 class ir_action_report_xml(osv.osv):
480     _name="ir.actions.report.xml"
481     _inherit ="ir.actions.report.xml"
482
483     def _model_get(self, cr, uid, ids, name, arg, context):
484         res = {}
485         model_pool = self.pool.get('ir.model')
486         for data in self.read(cr,uid,ids,['model']):
487             model = data.get('model',False)
488             if model:
489                 model_id =model_pool.search(cr,uid,[('model','=',model)])
490                 if model_id:
491                     res[data.get('id')] = model_id[0]
492                 else:
493                     res[data.get('id')] = False
494         return res
495
496     def _model_search(self, cr, uid, obj, name, args):
497         if not len(args):
498             return []
499         model_id= args[0][2]
500         if not model_id:
501             return []
502         model = self.pool.get('ir.model').read(cr,uid,[model_id])[0]['model']
503         report_id = self.search(cr,uid,[('model','=',model)])
504         if not report_id:
505             return [('id','=','0')]
506         return [('id','in',report_id)]
507
508     _columns={
509         'model_id' : fields.function(_model_get,fnct_search=_model_search,method=True,string='Model Id'),
510     }
511
512 ir_action_report_xml()
513
514 def create_directory(path):
515     dir_name = random_name()
516     path = os.path.join(path,dir_name)
517     os.makedirs(path)
518     return dir_name
519
520 class document_file(osv.osv):
521     _inherit = 'ir.attachment'
522     _rec_name = 'datas_fname'
523     def _get_filestore(self, cr):
524         return os.path.join(tools.config['root_path'], 'filestore', cr.dbname)
525
526     def _data_get(self, cr, uid, ids, name, arg, context):
527         result = {}
528         cr.execute('select id,store_fname,link from ir_attachment where id in ('+','.join(map(str, ids))+')')
529         for id,r,l in cr.fetchall():
530             try:
531                 value = file(os.path.join(self._get_filestore(cr), r), 'rb').read()
532                 result[id] = base64.encodestring(value)
533             except:
534                 result[id]=''
535 #            if context.get('bin_size', False):
536 #                result[id] = tools.human_size(result[id])
537         return result
538
539     #
540     # This code can be improved
541     #
542     def _data_set(self, cr, uid, id, name, value, args=None, context={}):
543         if not value:
544             filename = self.browse(cr, uid, id, context).store_fname
545             try:
546                 os.unlink(os.path.join(self._get_filestore(cr), filename))
547             except:
548                 pass
549             cr.execute('update ir_attachment set store_fname=NULL WHERE id=%s', (id,) )
550             return True
551         #if (not context) or context.get('store_method','fs')=='fs':
552         try:
553             path = self._get_filestore(cr)
554             if not os.path.isdir(path):
555                 try:
556                     os.makedirs(path)
557                 except:
558                     raise except_orm(_('Permission Denied !'), _('You do not permissions to write on the server side.'))
559
560             flag = None
561             # This can be improved
562             for dirs in os.listdir(path):
563                 if os.path.isdir(os.path.join(path,dirs)) and len(os.listdir(os.path.join(path,dirs)))<4000:
564                     flag = dirs
565                     break
566             flag = flag or create_directory(path)
567             filename = random_name()
568             fname = os.path.join(path, flag, filename)
569             fp = file(fname,'wb')
570             v = base64.decodestring(value)
571             fp.write(v)
572             filesize = os.stat(fname).st_size
573             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))
574             return True
575         except Exception,e :
576             raise except_orm(_('Error!'), str(e))
577
578     _columns = {
579         'user_id': fields.many2one('res.users', 'Owner', select=1),
580         'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'),
581         'parent_id': fields.many2one('document.directory', 'Directory', select=1),
582         'file_size': fields.integer('File Size', required=True),
583         'file_type': fields.char('Content Type', size=32),
584         'index_content': fields.text('Indexed Content'),
585         'write_date': fields.datetime('Date Modified', readonly=True),
586         'write_uid':  fields.many2one('res.users', 'Last Modification User', readonly=True),
587         'create_date': fields.datetime('Date Created', readonly=True),
588         'create_uid':  fields.many2one('res.users', 'Creator', readonly=True),
589         'store_method': fields.selection([('db','Database'),('fs','Filesystem'),('link','Link')], "Storing Method"),
590         'datas': fields.function(_data_get,method=True,fnct_inv=_data_set,string='File Content',type="binary"),
591         'store_fname': fields.char('Stored Filename', size=200),
592         'res_model': fields.char('Attached Model', size=64), #res_model
593         'res_id': fields.integer('Attached ID'), #res_id
594         'partner_id':fields.many2one('res.partner', 'Partner', select=1),
595         'title': fields.char('Resource Title',size=64),
596     }
597
598     _defaults = {
599         'user_id': lambda self,cr,uid,ctx:uid,
600         'file_size': lambda self,cr,uid,ctx:0,
601         'store_method': lambda *args: 'db'
602     }
603     _sql_constraints = [
604         ('filename_uniq', 'unique (name,parent_id,res_id,res_model)', 'The file name must be unique !')
605     ]
606     def _check_duplication(self, cr, uid,vals,ids=[],op='create'):
607         name=vals.get('name',False)
608         parent_id=vals.get('parent_id',False)
609         res_model=vals.get('res_model',False)
610         res_id=vals.get('res_id',0)
611         if op=='write':
612             for file in self.browse(cr,uid,ids):
613                 if not name:
614                     name=file.name
615                 if not parent_id:
616                     parent_id=file.parent_id and file.parent_id.id or False
617                 if not res_model:
618                     res_model=file.res_model and file.res_model or False
619                 if not res_id:
620                     res_id=file.res_id and file.res_id or 0
621                 res=self.search(cr,uid,[('id','<>',file.id),('name','=',name),('parent_id','=',parent_id),('res_model','=',res_model),('res_id','=',res_id)])
622                 if len(res):
623                     return False
624         if op=='create':
625             res=self.search(cr,uid,[('name','=',name),('parent_id','=',parent_id),('res_id','=',res_id),('res_model','=',res_model)])
626             if len(res):
627                 return False
628         return True
629     def copy(self, cr, uid, id, default=None, context=None):
630         if not default:
631             default ={}
632         name = self.read(cr, uid, [id])[0]['name']
633         default.update({'name': name+ " (copy)"})
634         return super(document_file,self).copy(cr,uid,id,default,context)
635     def write(self, cr, uid, ids, vals, context=None):
636         res=self.search(cr,uid,[('id','in',ids)])
637         if not len(res):
638             return False
639         if not self._check_duplication(cr,uid,vals,ids,'write'):
640             raise except_orm(_('ValidateError'), _('File name must be unique!'))
641         result = super(document_file,self).write(cr,uid,ids,vals,context=context)
642         cr.commit()
643         try:
644             for f in self.browse(cr, uid, ids, context=context):
645                 #if 'datas' not in vals:
646                 #    vals['datas']=f.datas
647                 res = content_index(base64.decodestring(vals['datas']), f.datas_fname, f.file_type or None)
648                 super(document_file,self).write(cr, uid, ids, {
649                     'index_content': res
650                 })
651             cr.commit()
652         except:
653             pass
654         return result
655
656     def create(self, cr, uid, vals, context={}):
657         vals['title']=vals['name']
658         vals['parent_id'] = context.get('parent_id',False) or vals.get('parent_id',False)
659         if not vals.get('res_id', False) and context.get('default_res_id',False):
660             vals['res_id']=context.get('default_res_id',False)
661         if not vals.get('res_model', False) and context.get('default_res_model',False):
662             vals['res_model']=context.get('default_res_model',False)
663         if vals.get('res_id', False) and vals.get('res_model',False):
664             obj_model=self.pool.get(vals['res_model'])
665             result = obj_model.read(cr, uid, [vals['res_id']], context=context)
666             if len(result):
667                 obj=result[0]
668                 if obj.get('name',False):
669                     vals['title'] = (obj.get('name',''))[:60]
670                 if obj_model._name=='res.partner':
671                     vals['partner_id']=obj['id']
672                 elif obj.get('address_id',False):
673                     if isinstance(obj['address_id'],tuple) or isinstance(obj['address_id'],list):
674                         address_id=obj['address_id'][0]
675                     else:
676                         address_id=obj['address_id']
677                     address=self.pool.get('res.partner.address').read(cr,uid,[address_id],context=context)
678                     if len(address):
679                         vals['partner_id']=address[0]['partner_id'][0] or False
680                 elif obj.get('partner_id',False):
681                     if isinstance(obj['partner_id'],tuple) or isinstance(obj['partner_id'],list):
682                         vals['partner_id']=obj['partner_id'][0]
683                     else:
684                         vals['partner_id']=obj['partner_id']
685
686         datas=None
687         if vals.get('link',False) :
688             import urllib
689             datas=base64.encodestring(urllib.urlopen(vals['link']).read())
690         else:
691             datas = vals.get('datas',False)
692         
693         vals['file_size']= datas and len(datas) or 0
694         if not self._check_duplication(cr,uid,vals):
695             raise except_orm(_('ValidateError'), _('File name must be unique!'))
696         result = super(document_file,self).create(cr, uid, vals, context)
697         cr.commit()
698         try:
699             res = content_index(base64.decodestring(datas), vals['datas_fname'], vals.get('content_type', None))
700             super(document_file,self).write(cr, uid, [result], {
701                 'index_content': res,
702             })
703             cr.commit()
704         except:
705             pass
706         return result
707
708     def unlink(self,cr, uid, ids, context={}):
709         for f in self.browse(cr, uid, ids, context):
710             #if f.store_method=='fs':
711             try:
712                 os.unlink(os.path.join(self._get_filestore(cr), f.store_fname))
713             except:
714                 pass
715         return super(document_file, self).unlink(cr, uid, ids, context)
716 document_file()
717
718 class document_configuration_wizard(osv.osv_memory):
719     _name='document.configuration.wizard'
720     _description = 'Auto Directory configuration'
721     _inherit = 'res.config'
722     _rec_name = 'host'
723     _columns = {
724         'host': fields.char('Address', size=64,
725                             help="Server address or IP.", required=True),
726         'port': fields.char('Port', size=5, help="Server port. " \
727             "Leave 8021 if you don't know what to write.", required=True)
728     }
729
730     def get_ftp_server_address(self, cr, uid, context=None):
731         default_address = config.get('ftp_server_address', None)
732         if default_address is None:
733             default_address = tools.misc.detect_ip_addr()
734
735         return default_address
736
737     def get_ftp_server_port(self, cr, uid, context=None):
738         return config.get('ftp_server_port', 8021)
739
740     _defaults = {
741         'host': get_ftp_server_address,
742         'port': get_ftp_server_port,
743     }
744
745     def execute(self, cr, uid, ids, context=None):
746         conf = self.browse(cr, uid, ids[0], context)
747         obj=self.pool.get('document.directory')
748         objid=self.pool.get('ir.model.data')
749
750         if self.pool.get('sale.order'):
751             id = objid._get_id(cr, uid, 'document', 'dir_sale_order_all')
752             id = objid.browse(cr, uid, id, context=context).res_id
753             mid = self.pool.get('ir.model').search(cr, uid, [('model','=','sale.order')])
754             obj.write(cr, uid, [id], {
755                 'type':'ressource',
756                 'ressource_type_id': mid[0],
757                 'domain': '[]',
758             })
759             aid = objid._get_id(cr, uid, 'sale', 'report_sale_order')
760             aid = objid.browse(cr, uid, aid, context=context).res_id
761
762             self.pool.get('document.directory.content').create(cr, uid, {
763                 'name': "Print Order",
764                 'suffix': "_print",
765                 'report_id': aid,
766                 'extension': '.pdf',
767                 'include_name': 1,
768                 'directory_id': id,
769             })
770             id = objid._get_id(cr, uid, 'document', 'dir_sale_order_quote')
771             id = objid.browse(cr, uid, id, context=context).res_id
772             obj.write(cr, uid, [id], {
773                 'type':'ressource',
774                 'ressource_type_id': mid[0],
775                 'domain': "[('state','=','draft')]",
776             })
777
778         if self.pool.get('product.product'):
779             id = objid._get_id(cr, uid, 'document', 'dir_product')
780             id = objid.browse(cr, uid, id, context=context).res_id
781             mid = self.pool.get('ir.model').search(cr, uid, [('model','=','product.product')])
782             obj.write(cr, uid, [id], {
783                 'type':'ressource',
784                 'ressource_type_id': mid[0],
785             })
786
787         if self.pool.get('stock.location'):
788             aid = objid._get_id(cr, uid, 'stock', 'report_product_history')
789             aid = objid.browse(cr, uid, aid, context=context).res_id
790
791             self.pool.get('document.directory.content').create(cr, uid, {
792                 'name': "Product Stock",
793                 'suffix': "_stock_forecast",
794                 'report_id': aid,
795                 'extension': '.pdf',
796                 'include_name': 1,
797                 'directory_id': id,
798             })
799
800         if self.pool.get('account.analytic.account'):
801             id = objid._get_id(cr, uid, 'document', 'dir_project')
802             id = objid.browse(cr, uid, id, context=context).res_id
803             mid = self.pool.get('ir.model').search(cr, uid, [('model','=','account.analytic.account')])
804             obj.write(cr, uid, [id], {
805                 'type':'ressource',
806                 'ressource_type_id': mid[0],
807                 'domain': '[]',
808                 'ressource_tree': 1
809         })
810
811         aid = objid._get_id(cr, uid, 'document', 'action_document_browse')
812         aid = objid.browse(cr, uid, aid, context=context).res_id
813         self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
814
815         config['ftp_server_address'] = conf.host
816         config['ftp_server_port'] = conf.port
817         config.save()
818 document_configuration_wizard()
819
820
821 class document_configuration_ftpserver_wizard(osv.osv_memory):
822     _name='document.configuration.ftp_server.wizard'
823     _rec_name = 'Configure FTP server address'
824     _columns = {
825         'host': fields.char('Address', size=64, help="Put here the server address or IP. " \
826             "Keep localhost if you don't know what to write.", required=True),
827         'port': fields.char('Port', size=5, help="Put here the server port. " \
828             "Keep 8021 if you don't know what to write.", required=True)
829     }
830
831     def get_ftp_server_address(self, cr, uid, context=None):
832         default_address = tools.misc.detect_ip_addr()
833         return config.get('ftp_server_address', default_address)
834
835     def get_ftp_server_port(self, cr, uid, context=None):
836         return config.get('ftp_server_port', '8021')
837
838     _defaults = {
839         'host': get_ftp_server_address,
840         'port': get_ftp_server_port,
841     }
842
843     def action_cancel(self,cr,uid,ids,conect=None):
844         return {}
845
846     def action_config(self, cr, uid, ids, context=None):
847         conf = self.browse(cr, uid, ids[0], context)
848         obj = self.pool.get('ir.model.data')
849
850         aid = obj._get_id(cr, uid, 'document', 'action_document_browse')
851         aid = obj.browse(cr, uid, aid, context=context).res_id
852         self.pool.get('ir.actions.url').write(cr, uid, [aid], {'url': 'ftp://'+(conf.host or 'localhost')+':'+conf.port+'/'})
853
854         config['ftp_server_address'] = conf.host
855         config['ftp_server_port'] = conf.port
856         config.save()
857
858         return {}
859
860 document_configuration_ftpserver_wizard()
861