2009-10-13 05:58:37 +00:00
# -*- coding: utf-8 -*-
2008-10-14 13:23:55 +00:00
##############################################################################
2009-11-26 12:39:16 +00:00
#
2009-01-12 11:31:51 +00:00
# OpenERP, Open Source Management Solution
2010-01-12 09:18:39 +00:00
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
2008-10-14 13:23:55 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is free software: you can redistribute it and/or modify
2009-10-14 11:15:34 +00:00
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
2008-09-26 14:23:55 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2009-10-14 11:15:34 +00:00
# GNU Affero General Public License for more details.
2008-09-26 14:23:55 +00:00
#
2009-10-14 11:15:34 +00:00
# You should have received a copy of the GNU Affero General Public License
2009-11-26 12:39:16 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2008-09-26 14:23:55 +00:00
#
##############################################################################
import base64
2012-12-17 01:19:58 +00:00
import errno
import logging
2008-09-26 14:23:55 +00:00
import os
2012-12-17 01:19:58 +00:00
import random
import shutil
import string
import time
from StringIO import StringIO
2008-09-26 14:23:55 +00:00
2012-12-17 01:19:58 +00:00
import psycopg2
2008-09-26 14:23:55 +00:00
2012-12-18 14:03:11 +00:00
import openerp
2012-12-06 14:56:32 +00:00
from openerp import tools
2013-12-16 12:39:54 +00:00
from openerp import SUPERUSER_ID
2012-12-18 14:03:11 +00:00
from openerp . osv import fields , osv
from openerp . osv . orm import except_orm
2013-02-01 11:23:09 +00:00
import openerp . report . interface
2012-12-18 14:03:11 +00:00
from openerp . tools . misc import ustr
2012-12-06 14:56:32 +00:00
from openerp . tools . translate import _
2012-12-18 14:03:11 +00:00
from openerp . tools . safe_eval import safe_eval
2008-09-26 14:23:55 +00:00
2012-12-17 01:19:58 +00:00
from content_index import cntIndex
2008-09-26 14:23:55 +00:00
2012-06-25 09:06:19 +00:00
_logger = logging . getLogger ( __name__ )
2012-06-22 06:48:39 +00:00
2008-09-26 14:23:55 +00:00
class document_file ( osv . osv ) :
2008-10-24 13:33:07 +00:00
_inherit = ' ir.attachment '
2011-02-18 14:10:12 +00:00
2008-10-24 13:33:07 +00:00
_columns = {
2010-06-27 20:18:09 +00:00
# Columns from ir.attachment:
' write_date ' : fields . datetime ( ' Date Modified ' , readonly = True ) ,
' write_uid ' : fields . many2one ( ' res.users ' , ' Last Modification User ' , readonly = True ) ,
# Fields of document:
2008-10-24 13:33:07 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' Owner ' , select = 1 ) ,
2012-12-17 01:19:58 +00:00
' parent_id ' : fields . many2one ( ' document.directory ' , ' Directory ' , select = 1 , change_default = True ) ,
2008-10-24 13:33:07 +00:00
' index_content ' : fields . text ( ' Indexed Content ' ) ,
' partner_id ' : fields . many2one ( ' res.partner ' , ' Partner ' , select = 1 ) ,
2012-12-17 01:19:58 +00:00
' file_type ' : fields . char ( ' Content Type ' ) ,
2008-10-24 13:33:07 +00:00
}
2011-12-11 11:39:18 +00:00
_order = " id desc "
2008-10-24 13:33:07 +00:00
_defaults = {
2010-02-24 08:54:04 +00:00
' user_id ' : lambda self , cr , uid , ctx : uid ,
2008-10-24 13:33:07 +00:00
}
2012-12-17 01:19:58 +00:00
2008-10-24 13:33:07 +00:00
_sql_constraints = [
2012-12-17 01:19:58 +00:00
( ' filename_unique ' , ' unique (name,parent_id) ' , ' The filename must be unique in a directory ! ' ) ,
2009-01-12 11:31:51 +00:00
]
2012-10-03 09:42:42 +00:00
2010-11-23 18:49:38 +00:00
def check ( self , cr , uid , ids , mode , context = None , values = None ) :
2013-07-22 13:27:15 +00:00
""" Overwrite check to verify access on directory to validate specifications of doc/access_permissions.rst """
2013-09-13 17:39:12 +00:00
if not isinstance ( ids , list ) :
ids = [ ids ]
2013-11-28 11:49:58 +00:00
2013-07-22 11:51:06 +00:00
super ( document_file , self ) . check ( cr , uid , ids , mode , context = context , values = values )
2013-11-28 11:49:58 +00:00
2013-07-22 11:51:06 +00:00
if ids :
2013-07-22 13:27:15 +00:00
self . pool . get ( ' ir.model.access ' ) . check ( cr , uid , ' document.directory ' , mode )
2013-07-22 11:51:06 +00:00
2013-07-22 13:27:15 +00:00
# use SQL to avoid recursive loop on read
cr . execute ( ' SELECT DISTINCT parent_id from ir_attachment WHERE id in %s AND parent_id is not NULL ' , ( tuple ( ids ) , ) )
self . pool . get ( ' document.directory ' ) . check_access_rule ( cr , uid , [ parent_id for ( parent_id , ) in cr . fetchall ( ) ] , mode , context = context )
2010-11-23 18:49:38 +00:00
2011-02-18 14:10:12 +00:00
def search ( self , cr , uid , args , offset = 0 , limit = None , order = None , context = None , count = False ) :
# Grab ids, bypassing 'count'
2012-12-17 01:19:58 +00:00
ids = super ( document_file , self ) . search ( cr , uid , args , offset = offset , limit = limit , order = order , context = context , count = False )
2011-02-18 14:10:12 +00:00
if not ids :
return 0 if count else [ ]
2012-12-17 03:09:13 +00:00
2011-02-18 14:10:12 +00:00
# Filter out documents that are in directories that the user is not allowed to read.
# Must use pure SQL to avoid access rules exceptions (we want to remove the records,
# not fail), and the records have been filtered in parent's search() anyway.
2012-12-17 03:09:13 +00:00
cr . execute ( ' SELECT id, parent_id from ir_attachment WHERE id in %s ' , ( tuple ( ids ) , ) )
# cont a dict of parent -> attach
parents = { }
for attach_id , attach_parent in cr . fetchall ( ) :
parents . setdefault ( attach_parent , [ ] ) . append ( attach_id )
parent_ids = parents . keys ( )
# filter parents
2011-02-18 14:10:12 +00:00
visible_parent_ids = self . pool . get ( ' document.directory ' ) . search ( cr , uid , [ ( ' id ' , ' in ' , list ( parent_ids ) ) ] )
2012-12-17 03:09:13 +00:00
# null parents means allowed
2015-01-07 11:01:54 +00:00
orig_ids = ids # save the ids, to keep order
2012-12-17 03:09:13 +00:00
ids = parents . get ( None , [ ] )
for parent_id in visible_parent_ids :
ids . extend ( parents [ parent_id ] )
2015-01-07 11:01:54 +00:00
# sort result according to the original sort ordering
2015-04-20 17:52:06 +00:00
if count :
return len ( ids )
else :
set_ids = set ( ids )
return [ id for id in orig_ids if id in set_ids ]
2011-02-18 14:10:12 +00:00
2008-11-18 13:04:58 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
if not default :
2010-02-24 08:54:04 +00:00
default = { }
2010-01-27 06:57:08 +00:00
if ' name ' not in default :
2011-02-18 14:10:12 +00:00
name = self . read ( cr , uid , [ id ] , [ ' name ' ] ) [ 0 ] [ ' name ' ]
2012-09-24 16:26:45 +00:00
default . update ( name = _ ( " %s (copy) " ) % ( name ) )
2010-11-19 13:48:01 +00:00
return super ( document_file , self ) . copy ( cr , uid , id , default , context = context )
2010-01-27 06:57:08 +00:00
2009-12-02 05:36:57 +00:00
def create ( self , cr , uid , vals , context = None ) :
2010-09-06 10:40:54 +00:00
if context is None :
2009-12-02 05:36:57 +00:00
context = { }
2010-02-24 08:54:04 +00:00
vals [ ' parent_id ' ] = context . get ( ' parent_id ' , False ) or vals . get ( ' parent_id ' , False )
2012-12-17 01:19:58 +00:00
# take partner from uid
if vals . get ( ' res_id ' , False ) and vals . get ( ' res_model ' , False ) and not vals . get ( ' partner_id ' , False ) :
vals [ ' partner_id ' ] = self . __get_partner_id ( cr , uid , vals [ ' res_model ' ] , vals [ ' res_id ' ] , context )
2012-12-24 14:00:02 +00:00
if vals . get ( ' datas ' , False ) :
2015-01-22 15:36:37 +00:00
vals [ ' file_type ' ] , vals [ ' index_content ' ] = self . _index ( cr , uid , vals [ ' datas ' ] . decode ( ' base64 ' ) , vals . get ( ' datas_fname ' , False ) , vals . get ( ' file_type ' , None ) )
2012-10-10 10:15:32 +00:00
return super ( document_file , self ) . create ( cr , uid , vals , context )
2008-10-24 13:33:07 +00:00
2012-12-24 14:00:02 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
if context is None :
context = { }
if vals . get ( ' datas ' , False ) :
2015-01-22 15:36:37 +00:00
vals [ ' file_type ' ] , vals [ ' index_content ' ] = self . _index ( cr , uid , vals [ ' datas ' ] . decode ( ' base64 ' ) , vals . get ( ' datas_fname ' , False ) , vals . get ( ' file_type ' , None ) )
2012-12-24 14:00:02 +00:00
return super ( document_file , self ) . write ( cr , uid , ids , vals , context )
def _index ( self , cr , uid , data , datas_fname , file_type ) :
2015-01-22 15:36:37 +00:00
mime , icont = cntIndex . doIndex ( data , datas_fname , file_type or None , None )
2012-12-24 14:00:02 +00:00
icont_u = ustr ( icont )
return mime , icont_u
2010-11-19 13:48:01 +00:00
def __get_partner_id ( self , cr , uid , res_model , res_id , context = None ) :
2010-07-01 17:51:33 +00:00
""" A helper to retrieve the associated partner from any res_model+id
It is a hack that will try to discover if the mentioned record is
clearly associated with a partner record .
"""
2013-03-29 14:37:20 +00:00
obj_model = self . pool [ res_model ]
2010-07-06 12:10:45 +00:00
if obj_model . _name == ' res.partner ' :
return res_id
elif ' partner_id ' in obj_model . _columns and obj_model . _columns [ ' partner_id ' ] . _obj == ' res.partner ' :
2010-07-16 22:18:34 +00:00
bro = obj_model . browse ( cr , uid , res_id , context = context )
2010-07-06 12:10:45 +00:00
return bro . partner_id . id
2010-07-01 17:51:33 +00:00
return False
2012-12-17 01:19:58 +00:00
class document_directory ( osv . osv ) :
_name = ' document.directory '
_description = ' Directory '
_order = ' name '
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Name ' , required = True , select = 1 ) ,
2012-12-17 01:19:58 +00:00
' write_date ' : fields . datetime ( ' Date Modified ' , readonly = True ) ,
' write_uid ' : fields . many2one ( ' res.users ' , ' Last Modification User ' , readonly = True ) ,
' create_date ' : fields . datetime ( ' Date Created ' , readonly = True ) ,
' create_uid ' : fields . many2one ( ' res.users ' , ' Creator ' , readonly = True ) ,
' user_id ' : fields . many2one ( ' res.users ' , ' Owner ' ) ,
' group_ids ' : fields . many2many ( ' res.groups ' , ' document_directory_group_rel ' , ' item_id ' , ' group_id ' , ' Groups ' ) ,
' parent_id ' : fields . many2one ( ' document.directory ' , ' Parent Directory ' , select = 1 , change_default = True ) ,
' child_ids ' : fields . one2many ( ' document.directory ' , ' parent_id ' , ' Children ' ) ,
' file_ids ' : fields . one2many ( ' ir.attachment ' , ' parent_id ' , ' Files ' ) ,
' content_ids ' : fields . one2many ( ' document.directory.content ' , ' directory_id ' , ' Virtual Files ' ) ,
' type ' : fields . selection ( [ ( ' directory ' , ' Static Directory ' ) , ( ' ressource ' , ' Folders per resource ' ) , ] ,
' Type ' , required = True , select = 1 , change_default = True ,
help = " Each directory can either have the type Static or be linked to another resource. A static directory, as with Operating Systems, is the classic directory that can contain a set of files. The directories linked to systems resources automatically possess sub-directories for each of resource types defined in the parent directory. " ) ,
2014-05-21 09:52:05 +00:00
' domain ' : fields . char ( ' Domain ' , help = " Use a domain if you want to apply an automatic filter on visible resources. " ) ,
2012-12-17 01:19:58 +00:00
' ressource_type_id ' : fields . many2one ( ' ir.model ' , ' Resource model ' , change_default = True ,
help = " Select an object here and there will be one folder per record of that resource. " ) ,
' resource_field ' : fields . many2one ( ' ir.model.fields ' , ' Name field ' , help = ' Field to be used as name on resource directories. If empty, the " name " will be used. ' ) ,
' resource_find_all ' : fields . boolean ( ' Find all resources ' ,
help = " If true, all attachments that match this resource will " \
" be located. If false, only ones that have this as parent. " ) ,
' ressource_parent_type_id ' : fields . many2one ( ' ir.model ' , ' Parent Model ' , change_default = True ,
help = " If you put an object here, this directory template will appear bellow all of these objects. " \
" Such directories are \" attached \" to the specific model or record, just like attachments. " \
" Don ' t put a parent directory if you select a parent model. " ) ,
' ressource_id ' : fields . integer ( ' Resource ID ' ,
help = " Along with Parent Model, this ID attaches this folder to a specific record of Parent Model. " ) ,
' ressource_tree ' : fields . boolean ( ' Tree Structure ' ,
help = " Check this if you want to use the same tree structure as the object selected in the system. " ) ,
' dctx_ids ' : fields . one2many ( ' document.directory.dctx ' , ' dir_id ' , ' Context fields ' ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , change_default = True ) ,
}
_defaults = {
' company_id ' : lambda s , cr , uid , c : s . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' document.directory ' , context = c ) ,
' user_id ' : lambda self , cr , uid , ctx : uid ,
' domain ' : ' [] ' ,
' type ' : ' directory ' ,
' ressource_id ' : 0 ,
' resource_find_all ' : True ,
}
_sql_constraints = [
( ' dirname_uniq ' , ' unique (name,parent_id,ressource_id,ressource_parent_type_id) ' , ' The directory name must be unique ! ' ) ,
( ' no_selfparent ' , ' check(parent_id <> id) ' , ' Directory cannot be parent of itself! ' ) ,
]
def name_get ( self , cr , uid , ids , context = None ) :
res = [ ]
if not self . search ( cr , uid , [ ( ' id ' , ' in ' , ids ) ] ) :
ids = [ ]
for d in self . browse ( cr , uid , ids , context = context ) :
s = ' '
d2 = d
while d2 and d2 . parent_id :
s = d2 . name + ( s and ( ' / ' + s ) or ' ' )
d2 = d2 . parent_id
res . append ( ( d . id , s or d . name ) )
return res
def get_full_path ( self , cr , uid , dir_id , context = None ) :
""" Return the full path to this directory, in a list, root first
"""
if isinstance ( dir_id , ( tuple , list ) ) :
assert len ( dir_id ) == 1
dir_id = dir_id [ 0 ]
def _parent ( dir_id , path ) :
parent = self . browse ( cr , uid , dir_id )
if parent . parent_id and not parent . ressource_parent_type_id :
_parent ( parent . parent_id . id , path )
path . append ( parent . name )
else :
path . append ( parent . name )
return path
path = [ ]
_parent ( dir_id , path )
return path
_constraints = [
2014-07-04 14:53:26 +00:00
( osv . osv . _check_recursion , ' Error! You cannot create recursive directories. ' , [ ' parent_id ' ] )
2012-12-17 01:19:58 +00:00
]
def onchange_content_id ( self , cr , uid , ids , ressource_type_id ) :
return { }
def get_object ( self , cr , uid , uri , context = None ) :
""" Return a node object for the given uri.
This fn merely passes the call to node_context
"""
return get_node_context ( cr , uid , context ) . get_uri ( cr , uri )
def get_node_class ( self , cr , uid , ids , dbro = None , dynamic = False , context = None ) :
""" Retrieve the class of nodes for this directory
This function can be overriden by inherited classes ; )
@param dbro The browse object , if caller already has it
"""
if dbro is None :
dbro = self . browse ( cr , uid , ids , context = context )
if dynamic :
return node_res_obj
elif dbro . type == ' directory ' :
return node_dir
elif dbro . type == ' ressource ' :
return node_res_dir
else :
raise ValueError ( " dir node for %s type. " , dbro . type )
def _prepare_context ( self , cr , uid , nctx , context = None ) :
""" Fill nctx with properties for this database
@param nctx instance of nodes . node_context , to be filled
@param context ORM context ( dict ) for us
Note that this function is called * without * a list of ids ,
it should behave the same for the whole database ( based on the
ORM instance of document . directory ) .
Some databases may override this and attach properties to the
node_context . See WebDAV , CalDAV .
"""
return
def get_dir_permissions ( self , cr , uid , ids , context = None ) :
""" Check what permission user ' uid ' has on directory ' id '
"""
assert len ( ids ) == 1
res = 0
for pperms in [ ( ' read ' , 5 ) , ( ' write ' , 2 ) , ( ' unlink ' , 8 ) ] :
try :
self . check_access_rule ( cr , uid , ids , pperms [ 0 ] , context = context )
res | = pperms [ 1 ]
except except_orm :
pass
return res
def _locate_child ( self , cr , uid , root_id , uri , nparent , ncontext ) :
""" try to locate the node in uri,
Return a tuple ( node_dir , remaining_path )
"""
return ( node_database ( context = ncontext ) , uri )
def copy ( self , cr , uid , id , default = None , context = None ) :
if not default :
default = { }
name = self . read ( cr , uid , [ id ] ) [ 0 ] [ ' name ' ]
default . update ( name = _ ( " %s (copy) " ) % ( name ) )
return super ( document_directory , self ) . copy ( cr , uid , id , default , context = context )
def _check_duplication ( self , cr , uid , vals , ids = None , op = ' create ' ) :
name = vals . get ( ' name ' , False )
parent_id = vals . get ( ' parent_id ' , False )
ressource_parent_type_id = vals . get ( ' ressource_parent_type_id ' , False )
ressource_id = vals . get ( ' ressource_id ' , 0 )
if op == ' write ' :
2013-12-16 12:39:54 +00:00
for directory in self . browse ( cr , SUPERUSER_ID , ids ) :
2012-12-17 01:19:58 +00:00
if not name :
name = directory . name
if not parent_id :
parent_id = directory . parent_id and directory . parent_id . id or False
# TODO fix algo
if not ressource_parent_type_id :
ressource_parent_type_id = directory . ressource_parent_type_id and directory . ressource_parent_type_id . id or False
if not ressource_id :
ressource_id = directory . ressource_id and directory . ressource_id or 0
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 ) ] )
if len ( res ) :
return False
if op == ' create ' :
2013-12-16 12:39:54 +00:00
res = self . search ( cr , SUPERUSER_ID , [ ( ' name ' , ' = ' , name ) , ( ' parent_id ' , ' = ' , parent_id ) , ( ' ressource_parent_type_id ' , ' = ' , ressource_parent_type_id ) , ( ' ressource_id ' , ' = ' , ressource_id ) ] )
2012-12-17 01:19:58 +00:00
if len ( res ) :
return False
return True
def write ( self , cr , uid , ids , vals , context = None ) :
if not self . _check_duplication ( cr , uid , vals , ids , op = ' write ' ) :
raise osv . except_osv ( _ ( ' ValidateError ' ) , _ ( ' Directory name must be unique! ' ) )
return super ( document_directory , self ) . write ( cr , uid , ids , vals , context = context )
def create ( self , cr , uid , vals , context = None ) :
if not self . _check_duplication ( cr , uid , vals ) :
raise osv . except_osv ( _ ( ' ValidateError ' ) , _ ( ' Directory name must be unique! ' ) )
newname = vals . get ( ' name ' , False )
if newname :
for illeg in ( ' / ' , ' @ ' , ' $ ' , ' # ' ) :
if illeg in newname :
raise osv . except_osv ( _ ( ' ValidateError ' ) , _ ( ' Directory name contains special characters! ' ) )
return super ( document_directory , self ) . create ( cr , uid , vals , context )
class document_directory_dctx ( osv . osv ) :
""" In order to evaluate dynamic folders, child items could have a limiting
domain expression . For that , their parents will export a context where useful
information will be passed on .
If you define sth like " s_id " = " this.id " at a folder iterating over sales , its
2012-12-18 16:58:39 +00:00
children could have a domain like [ ( ' sale_id ' , = , s_id ) ]
2012-12-17 01:19:58 +00:00
This system should be used recursively , that is , parent dynamic context will be
appended to all children down the tree .
"""
_name = ' document.directory.dctx '
_description = ' Directory Dynamic Context '
_columns = {
' dir_id ' : fields . many2one ( ' document.directory ' , ' Directory ' , required = True , ondelete = " cascade " ) ,
2014-05-21 09:52:05 +00:00
' field ' : fields . char ( ' Field ' , required = True , select = 1 , help = " The name of the field. " ) ,
' expr ' : fields . char ( ' Expression ' , required = True , help = " A python expression used to evaluate the field. \n " + \
2012-12-17 01:19:58 +00:00
" You can use ' dir_id ' for current dir, ' res_id ' , ' res_model ' as a reference to the current record, in dynamic folders " ) ,
}
class document_directory_content_type ( osv . osv ) :
_name = ' document.directory.content.type '
_description = ' Directory Content Type '
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Content Type ' , required = True ) ,
2012-12-17 01:19:58 +00:00
' code ' : fields . char ( ' Extension ' , size = 4 ) ,
' active ' : fields . boolean ( ' Active ' ) ,
2014-05-21 09:52:05 +00:00
' mimetype ' : fields . char ( ' Mime Type ' )
2012-12-17 01:19:58 +00:00
}
_defaults = {
' active ' : lambda * args : 1
}
class document_directory_content ( osv . osv ) :
_name = ' document.directory.content '
_description = ' Directory Content '
_order = " sequence "
def _extension_get ( self , cr , uid , context = None ) :
cr . execute ( ' select code,name from document_directory_content_type where active ' )
res = cr . fetchall ( )
return res
_columns = {
2014-05-21 09:52:05 +00:00
' name ' : fields . char ( ' Content Name ' , required = True ) ,
2012-12-17 01:19:58 +00:00
' sequence ' : fields . integer ( ' Sequence ' , size = 16 ) ,
' prefix ' : fields . char ( ' Prefix ' , size = 16 ) ,
' suffix ' : fields . char ( ' Suffix ' , size = 16 ) ,
' report_id ' : fields . many2one ( ' ir.actions.report.xml ' , ' Report ' ) ,
' extension ' : fields . selection ( _extension_get , ' Document Type ' , required = True , size = 4 ) ,
' include_name ' : fields . boolean ( ' Include Record Name ' ,
help = " Check this field if you want that the name of the file to contain the record name. " \
" \n If set, the directory will have to be a resource one. " ) ,
' directory_id ' : fields . many2one ( ' document.directory ' , ' Directory ' ) ,
}
_defaults = {
' extension ' : lambda * args : ' .pdf ' ,
' sequence ' : lambda * args : 1 ,
' include_name ' : lambda * args : 1 ,
}
def _file_get ( self , cr , node , nodename , content , context = None ) :
""" return the nodes of a <node> parent having a <content> content
The return value MUST be false or a list of node_class objects .
"""
# TODO: respect the context!
model = node . res_model
if content . include_name and not model :
return False
res2 = [ ]
tname = ' '
if content . include_name :
2012-12-18 16:37:24 +00:00
record_name = node . displayname or ' '
if record_name :
tname = ( content . prefix or ' ' ) + record_name + ( content . suffix or ' ' ) + ( content . extension or ' ' )
2012-12-17 01:19:58 +00:00
else :
2012-12-18 16:37:24 +00:00
tname = ( content . prefix or ' ' ) + ( content . name or ' ' ) + ( content . suffix or ' ' ) + ( content . extension or ' ' )
2012-12-17 01:19:58 +00:00
if tname . find ( ' / ' ) :
tname = tname . replace ( ' / ' , ' _ ' )
act_id = False
if ' dctx_res_id ' in node . dctx :
2012-12-18 16:58:39 +00:00
act_id = node . dctx [ ' res_id ' ]
2012-12-17 01:19:58 +00:00
elif hasattr ( node , ' res_id ' ) :
act_id = node . res_id
else :
act_id = node . context . context . get ( ' res_id ' , False )
if not nodename :
n = node_content ( tname , node , node . context , content , act_id = act_id )
res2 . append ( n )
else :
if nodename == tname :
n = node_content ( tname , node , node . context , content , act_id = act_id )
n . fill_fields ( cr )
res2 . append ( n )
return res2
def process_write ( self , cr , uid , node , data , context = None ) :
if node . extension != ' .pdf ' :
raise Exception ( " Invalid content: %s " % node . extension )
return True
def process_read ( self , cr , uid , node , context = None ) :
if node . extension != ' .pdf ' :
raise Exception ( " Invalid content: %s " % node . extension )
report = self . pool . get ( ' ir.actions.report.xml ' ) . browse ( cr , uid , node . report_id , context = context )
2013-02-01 11:23:09 +00:00
srv = openerp . report . interface . report_int . _reports [ ' report. ' + report . report_name ]
2012-12-17 01:19:58 +00:00
ctx = node . context . context . copy ( )
ctx . update ( node . dctx )
pdf , pdftype = srv . create ( cr , uid , [ node . act_id , ] , { } , context = ctx )
return pdf
class ir_action_report_xml ( osv . osv ) :
_name = " ir.actions.report.xml "
_inherit = " ir.actions.report.xml "
def _model_get ( self , cr , uid , ids , name , arg , context = None ) :
res = { }
model_pool = self . pool . get ( ' ir.model ' )
for data in self . read ( cr , uid , ids , [ ' model ' ] ) :
model = data . get ( ' model ' , False )
if model :
model_id = model_pool . search ( cr , uid , [ ( ' model ' , ' = ' , model ) ] )
if model_id :
res [ data . get ( ' id ' ) ] = model_id [ 0 ]
else :
res [ data . get ( ' id ' ) ] = False
return res
def _model_search ( self , cr , uid , obj , name , args , context = None ) :
if not len ( args ) :
return [ ]
assert len ( args ) == 1 and args [ 0 ] [ 1 ] == ' = ' , ' expression is not what we expect: %r ' % args
model_id = args [ 0 ] [ 2 ]
if not model_id :
# a deviation from standard behavior: when searching model_id = False
# we return *all* reports, not just ones with empty model.
# One reason is that 'model' is a required field so far
return [ ]
model = self . pool . get ( ' ir.model ' ) . read ( cr , uid , [ model_id ] ) [ 0 ] [ ' model ' ]
report_id = self . search ( cr , uid , [ ( ' model ' , ' = ' , model ) ] )
if not report_id :
return [ ( ' id ' , ' = ' , ' 0 ' ) ]
return [ ( ' id ' , ' in ' , report_id ) ]
_columns = {
' model_id ' : fields . function ( _model_get , fnct_search = _model_search , string = ' Model Id ' ) ,
}
class document_storage ( osv . osv ) :
""" The primary object for data storage. Deprecated. """
_name = ' document.storage '
_description = ' Storage Media '
def get_data ( self , cr , uid , id , file_node , context = None , fil_obj = None ) :
""" retrieve the contents of some file_node having storage_id = id
optionally , fil_obj could point to the browse object of the file
( ir . attachment )
"""
boo = self . browse ( cr , uid , id , context = context )
if fil_obj :
ira = fil_obj
else :
ira = self . pool . get ( ' ir.attachment ' ) . browse ( cr , uid , file_node . file_id , context = context )
data = ira . datas
if data :
out = data . decode ( ' base64 ' )
else :
out = ' '
return out
def get_file ( self , cr , uid , id , file_node , mode , context = None ) :
""" Return a file-like object for the contents of some node
"""
if context is None :
context = { }
boo = self . browse ( cr , uid , id , context = context )
ira = self . pool . get ( ' ir.attachment ' ) . browse ( cr , uid , file_node . file_id , context = context )
return nodefd_db ( file_node , ira_browse = ira , mode = mode )
def set_data ( self , cr , uid , id , file_node , data , context = None , fil_obj = None ) :
""" store the data.
This function MUST be used from an ir . attachment . It wouldn ' t make sense
to store things persistently for other types ( dynamic ) .
"""
boo = self . browse ( cr , uid , id , context = context )
if fil_obj :
ira = fil_obj
else :
ira = self . pool . get ( ' ir.attachment ' ) . browse ( cr , uid , file_node . file_id , context = context )
_logger . debug ( " Store data for ir.attachment # %d . " % ira . id )
store_fname = None
fname = None
filesize = len ( data )
self . pool . get ( ' ir.attachment ' ) . write ( cr , uid , [ file_node . file_id ] , { ' datas ' : data . encode ( ' base64 ' ) } , context = context )
# 2nd phase: store the metadata
try :
icont = ' '
mime = ira . file_type
if not mime :
mime = " "
try :
mime , icont = cntIndex . doIndex ( data , ira . datas_fname , ira . file_type or None , fname )
except Exception :
_logger . debug ( ' Cannot index file. ' , exc_info = True )
pass
try :
icont_u = ustr ( icont )
except UnicodeError :
icont_u = ' '
# a hack: /assume/ that the calling write operation will not try
# to write the fname and size, and update them in the db concurrently.
# We cannot use a write() here, because we are already in one.
2012-12-19 17:00:05 +00:00
cr . execute ( ' UPDATE ir_attachment SET file_size = %s , index_content = %s , file_type = %s WHERE id = %s ' , ( filesize , icont_u , mime , file_node . file_id ) )
2014-07-06 14:44:26 +00:00
self . pool . get ( ' ir.attachment ' ) . invalidate_cache ( cr , uid , [ ' file_size ' , ' index_content ' , ' file_type ' ] , [ file_node . file_id ] , context = context )
2012-12-17 01:19:58 +00:00
file_node . content_length = filesize
file_node . content_type = mime
return True
except Exception , e :
2012-12-18 16:37:24 +00:00
_logger . warning ( " Cannot save data. " , exc_info = True )
2012-12-17 01:19:58 +00:00
# should we really rollback once we have written the actual data?
# at the db case (only), that rollback would be safe
raise except_orm ( _ ( ' Error at doc write! ' ) , str ( e ) )
def _str2time ( cre ) :
""" Convert a string with time representation (from db) into time (float)
Note : a place to fix if datetime is used in db .
"""
if not cre :
return time . time ( )
frac = 0.0
if isinstance ( cre , basestring ) and ' . ' in cre :
fdot = cre . find ( ' . ' )
frac = float ( cre [ fdot : ] )
cre = cre [ : fdot ]
return time . mktime ( time . strptime ( cre , ' % Y- % m- %d % H: % M: % S ' ) ) + frac
def get_node_context ( cr , uid , context ) :
return node_context ( cr , uid , context )
#
# An object that represent an uri
# path: the uri of the object
# content: the Content it belongs to (_print.pdf)
# type: content or collection
# content: objct = res.partner
# collection: object = directory, object2 = res.partner
# file: objct = ir.attachement
# root: if we are at the first directory of a ressource
#
class node_context ( object ) :
""" This is the root node, representing access to some particular context
A context is a set of persistent data , which may influence the structure
of the nodes . All other transient information during a data query should
be passed down with function arguments .
"""
cached_roots = { }
node_file_class = None
def __init__ ( self , cr , uid , context = None ) :
self . dbname = cr . dbname
self . uid = uid
self . context = context
if context is None :
context = { }
context [ ' uid ' ] = uid
2013-03-27 14:01:49 +00:00
self . _dirobj = openerp . registry ( cr . dbname ) . get ( ' document.directory ' )
2012-12-17 01:19:58 +00:00
self . node_file_class = node_file
self . extra_ctx = { } # Extra keys for context, that do _not_ trigger inequality
assert self . _dirobj
self . _dirobj . _prepare_context ( cr , uid , self , context = context )
self . rootdir = False #self._dirobj._get_root_directory(cr,uid,context)
def __eq__ ( self , other ) :
if not type ( other ) == node_context :
return False
if self . dbname != other . dbname :
return False
if self . uid != other . uid :
return False
if self . context != other . context :
return False
if self . rootdir != other . rootdir :
return False
return True
def __ne__ ( self , other ) :
return not self . __eq__ ( other )
def get ( self , name , default = None ) :
return self . context . get ( name , default )
def get_uri ( self , cr , uri ) :
""" Although this fn passes back to doc.dir, it is needed since
it is a potential caching point .
"""
( ndir , duri ) = self . _dirobj . _locate_child ( cr , self . uid , self . rootdir , uri , None , self )
while duri :
ndir = ndir . child ( cr , duri [ 0 ] )
if not ndir :
return False
duri = duri [ 1 : ]
return ndir
def get_dir_node ( self , cr , dbro ) :
""" Create (or locate) a node for a directory
@param dbro a browse object of document . directory
"""
fullpath = dbro . get_full_path ( context = self . context )
klass = dbro . get_node_class ( dbro , context = self . context )
return klass ( fullpath , None , self , dbro )
def get_file_node ( self , cr , fbro ) :
""" Create or locate a node for a static file
@param fbro a browse object of an ir . attachment
"""
parent = None
if fbro . parent_id :
parent = self . get_dir_node ( cr , fbro . parent_id )
return self . node_file_class ( fbro . name , parent , self , fbro )
class node_class ( object ) :
""" this is a superclass for our inodes
It is an API for all code that wants to access the document files .
Nodes have attributes which contain usual file properties
"""
our_type = ' baseclass '
DAV_PROPS = None
DAV_M_NS = None
def __init__ ( self , path , parent , context ) :
assert isinstance ( context , node_context )
assert ( not parent ) or isinstance ( parent , node_class )
self . path = path
self . context = context
self . type = self . our_type
self . parent = parent
self . uidperms = 5 # computed permissions for our uid, in unix bits
self . mimetype = ' application/octet-stream '
self . create_date = None
self . write_date = None
self . unixperms = 0660
self . uuser = ' user '
self . ugroup = ' group '
self . content_length = 0
# dynamic context:
self . dctx = { }
if parent :
self . dctx = parent . dctx . copy ( )
self . displayname = ' Object '
def __eq__ ( self , other ) :
return NotImplemented
def __ne__ ( self , other ) :
return not self . __eq__ ( other )
def full_path ( self ) :
""" Return the components of the full path for some
node .
The returned list only contains the names of nodes .
"""
if self . parent :
s = self . parent . full_path ( )
else :
s = [ ]
if isinstance ( self . path , list ) :
s + = self . path
elif self . path is None :
s . append ( ' ' )
else :
s . append ( self . path )
return s #map(lambda x: '/' +x, s)
def __repr__ ( self ) :
return " %s @/ %s " % ( self . our_type , ' / ' . join ( self . full_path ( ) ) )
def children ( self , cr , domain = None ) :
print " node_class.children() "
return [ ] #stub
def child ( self , cr , name , domain = None ) :
print " node_class.child() "
return None
def get_uri ( self , cr , uri ) :
duri = uri
ndir = self
while duri :
ndir = ndir . child ( cr , duri [ 0 ] )
if not ndir :
return False
duri = duri [ 1 : ]
return ndir
def path_get ( self ) :
print " node_class.path_get() "
return False
def get_data ( self , cr ) :
raise TypeError ( ' No data for %s . ' % self . type )
def open_data ( self , cr , mode ) :
""" Open a node_descriptor object for this node.
@param the mode of open , eg ' r ' , ' w ' , ' a ' , like file . open ( )
This operation may lock the data for this node ( and accross
other node hierarchies ) , until the descriptor is close ( ) d . If
the node is locked , subsequent opens ( depending on mode ) may
immediately fail with an exception ( which ? ) .
For this class , there is no data , so no implementation . Each
child class that has data should override this .
"""
raise TypeError ( ' No data for %s . ' % self . type )
def get_etag ( self , cr ) :
""" Get a tag, unique per object + modification.
see . http : / / tools . ietf . org / html / rfc2616 #section-13.3.3 """
return ' " %s - %s " ' % ( self . _get_ttag ( cr ) , self . _get_wtag ( cr ) )
def _get_wtag ( self , cr ) :
""" Return the modification time as a unique, compact string """
return str ( _str2time ( self . write_date ) ) . replace ( ' . ' , ' ' )
def _get_ttag ( self , cr ) :
""" Get a unique tag for this type/id of object.
Must be overriden , so that each node is uniquely identified .
"""
print " node_class.get_ttag() " , self
raise NotImplementedError ( " get_ttag stub() " )
def get_dav_props ( self , cr ) :
""" If this class has special behaviour for GroupDAV etc, export
its capabilities """
# This fn is placed here rather than WebDAV, because we want the
# baseclass methods to apply to all node subclasses
return self . DAV_PROPS or { }
def match_dav_eprop ( self , cr , match , ns , prop ) :
res = self . get_dav_eprop ( cr , ns , prop )
if res == match :
return True
return False
def get_dav_eprop ( self , cr , ns , prop ) :
if not self . DAV_M_NS :
return None
if self . DAV_M_NS . has_key ( ns ) :
prefix = self . DAV_M_NS [ ns ]
else :
_logger . debug ( ' No namespace: %s ( " %s " ). ' , ns , prop )
return None
mname = prefix + " _ " + prop . replace ( ' - ' , ' _ ' )
if not hasattr ( self , mname ) :
return None
try :
m = getattr ( self , mname )
r = m ( cr )
return r
except AttributeError :
_logger . debug ( ' The property %s is not supported. ' % prop , exc_info = True )
return None
def get_dav_resourcetype ( self , cr ) :
""" Get the DAV resource type.
Is here because some nodes may exhibit special behaviour , like
CalDAV / GroupDAV collections
"""
raise NotImplementedError
def move_to ( self , cr , ndir_node , new_name = False , fil_obj = None , ndir_obj = None , in_write = False ) :
""" Move this node to a new parent directory.
@param ndir_node the collection that this node should be moved under
@param new_name a name to rename this node to . If omitted , the old
name is preserved
@param fil_obj , can be None , is the browse object for the file ,
if already available .
@param ndir_obj must be the browse object to the new doc . directory
location , where this node should be moved to .
in_write : When called by write ( ) , we shouldn ' t attempt to write the
object , but instead return the dict of vals ( avoid re - entrance ) .
If false , we should write all data to the object , here , as if the
caller won ' t do anything after calling move_to()
Return value :
True : the node is moved , the caller can update other values , too .
False : the node is either removed or fully updated , the caller
must discard the fil_obj , not attempt to write any more to it .
dict : values to write back to the object . * May * contain a new id !
Depending on src and target storage , implementations of this function
could do various things .
Should also consider node < - > content , dir < - > dir moves etc .
Move operations , as instructed from APIs ( e . g . request from DAV ) could
use this function .
"""
raise NotImplementedError ( repr ( self ) )
def create_child ( self , cr , path , data = None ) :
""" Create a regular file under this node
"""
_logger . warning ( " Attempted to create a file under %r , not possible. " , self )
raise IOError ( errno . EPERM , " Not allowed to create file(s) here. " )
def create_child_collection ( self , cr , objname ) :
""" Create a child collection (directory) under self
"""
_logger . warning ( " Attempted to create a collection under %r , not possible. " , self )
raise IOError ( errno . EPERM , " Not allowed to create folder(s) here. " )
def rm ( self , cr ) :
raise NotImplementedError ( repr ( self ) )
def rmcol ( self , cr ) :
raise NotImplementedError ( repr ( self ) )
def get_domain ( self , cr , filters ) :
# TODO Document
return [ ]
def check_perms ( self , perms ) :
""" Check the permissions of the current node.
@param perms either an integers of the bits to check , or
a string with the permission letters
Permissions of nodes are ( in a unix way ) :
1 , x : allow descend into dir
2 , w : allow write into file , or modification to dir
4 , r : allow read of file , or listing of dir contents
8 , u : allow remove ( unlink )
"""
if isinstance ( perms , str ) :
pe2 = 0
chars = { ' x ' : 1 , ' w ' : 2 , ' r ' : 4 , ' u ' : 8 }
for c in perms :
pe2 = pe2 | chars [ c ]
perms = pe2
elif isinstance ( perms , int ) :
if perms < 0 or perms > 15 :
raise ValueError ( " Invalid permission bits. " )
else :
raise ValueError ( " Invalid permission attribute. " )
return ( ( self . uidperms & perms ) == perms )
class node_database ( node_class ) :
""" A node representing the database directory
"""
our_type = ' database '
def __init__ ( self , path = None , parent = False , context = None ) :
if path is None :
path = [ ]
super ( node_database , self ) . __init__ ( path , parent , context )
self . unixperms = 040750
self . uidperms = 5
def children ( self , cr , domain = None ) :
res = self . _child_get ( cr , domain = domain ) + self . _file_get ( cr )
return res
def child ( self , cr , name , domain = None ) :
res = self . _child_get ( cr , name , domain = None )
if res :
return res [ 0 ]
res = self . _file_get ( cr , name )
if res :
return res [ 0 ]
return None
def _child_get ( self , cr , name = False , domain = None ) :
dirobj = self . context . _dirobj
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
where = [ ( ' parent_id ' , ' = ' , False ) , ( ' ressource_parent_type_id ' , ' = ' , False ) ]
if name :
where . append ( ( ' name ' , ' = ' , name ) )
is_allowed = self . check_perms ( 1 )
else :
is_allowed = self . check_perms ( 5 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission into directory denied. " )
if domain :
where = where + domain
ids = dirobj . search ( cr , uid , where , context = ctx )
res = [ ]
for dirr in dirobj . browse ( cr , uid , ids , context = ctx ) :
klass = dirr . get_node_class ( dirr , context = ctx )
res . append ( klass ( dirr . name , self , self . context , dirr ) )
return res
def _file_get ( self , cr , nodename = False ) :
res = [ ]
return res
def _get_ttag ( self , cr ) :
return ' db- %s ' % cr . dbname
def mkdosname ( company_name , default = ' noname ' ) :
""" convert a string to a dos-like name """
if not company_name :
return default
badchars = ' !@#$ % ^`~*()+= {} [];: \' " /?.<> '
n = ' '
for c in company_name [ : 8 ] :
n + = ( c in badchars and ' _ ' ) or c
return n
def _uid2unixperms ( perms , has_owner ) :
""" Convert the uidperms and the owner flag to full unix bits
"""
res = 0
if has_owner :
res | = ( perms & 0x07 ) << 6
res | = ( perms & 0x05 ) << 3
elif perms & 0x02 :
res | = ( perms & 0x07 ) << 6
res | = ( perms & 0x07 ) << 3
else :
res | = ( perms & 0x07 ) << 6
res | = ( perms & 0x05 ) << 3
res | = 0x05
return res
class node_dir ( node_database ) :
our_type = ' collection '
def __init__ ( self , path , parent , context , dirr , dctx = None ) :
super ( node_dir , self ) . __init__ ( path , parent , context )
self . dir_id = dirr and dirr . id or False
#todo: more info from dirr
self . mimetype = ' application/x-directory '
# 'httpd/unix-directory'
self . create_date = dirr and dirr . create_date or False
self . domain = dirr and dirr . domain or [ ]
self . res_model = dirr and dirr . ressource_type_id and dirr . ressource_type_id . model or False
# TODO: the write date should be MAX(file.write)..
self . write_date = dirr and ( dirr . write_date or dirr . create_date ) or False
self . content_length = 0
try :
self . uuser = ( dirr . user_id and dirr . user_id . login ) or ' nobody '
except Exception :
self . uuser = ' nobody '
self . ugroup = mkdosname ( dirr . company_id and dirr . company_id . name , default = ' nogroup ' )
self . uidperms = dirr . get_dir_permissions ( )
self . unixperms = 040000 | _uid2unixperms ( self . uidperms , dirr and dirr . user_id )
if dctx :
self . dctx . update ( dctx )
dc2 = self . context . context
dc2 . update ( self . dctx )
dc2 [ ' dir_id ' ] = self . dir_id
self . displayname = dirr and dirr . name or False
if dirr and dirr . dctx_ids :
for dfld in dirr . dctx_ids :
try :
2012-12-18 16:58:39 +00:00
self . dctx [ dfld . field ] = safe_eval ( dfld . expr , dc2 )
2012-12-17 01:19:58 +00:00
except Exception , e :
print " Cannot eval %s . " % dfld . expr
print e
pass
def __eq__ ( self , other ) :
if type ( self ) != type ( other ) :
return False
if not self . context == other . context :
return False
# Two directory nodes, for the same document.directory, may have a
# different context! (dynamic folders)
if self . dctx != other . dctx :
return False
return self . dir_id == other . dir_id
def get_data ( self , cr ) :
#res = ''
#for child in self.children(cr):
# res += child.get_data(cr)
return None
def _file_get ( self , cr , nodename = False ) :
res = super ( node_dir , self ) . _file_get ( cr , nodename )
is_allowed = self . check_perms ( nodename and 1 or 5 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission into directory denied. " )
cntobj = self . context . _dirobj . pool . get ( ' document.directory.content ' )
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
where = [ ( ' directory_id ' , ' = ' , self . dir_id ) ]
ids = cntobj . search ( cr , uid , where , context = ctx )
for content in cntobj . browse ( cr , uid , ids , context = ctx ) :
res3 = cntobj . _file_get ( cr , self , nodename , content )
if res3 :
res . extend ( res3 )
return res
def _child_get ( self , cr , name = None , domain = None ) :
dirobj = self . context . _dirobj
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
where = [ ( ' parent_id ' , ' = ' , self . dir_id ) ]
if name :
where . append ( ( ' name ' , ' = ' , name ) )
is_allowed = self . check_perms ( 1 )
else :
is_allowed = self . check_perms ( 5 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission into directory denied. " )
if not domain :
domain = [ ]
where2 = where + domain + [ ( ' ressource_parent_type_id ' , ' = ' , False ) ]
ids = dirobj . search ( cr , uid , where2 , context = ctx )
res = [ ]
for dirr in dirobj . browse ( cr , uid , ids , context = ctx ) :
klass = dirr . get_node_class ( dirr , context = ctx )
res . append ( klass ( dirr . name , self , self . context , dirr ) )
# Static directories should never return files with res_model/res_id
# because static dirs are /never/ related to a record.
# In fact, files related to some model and parented by the root dir
# (the default), will NOT be accessible in the node system unless
# a resource folder for that model exists (with resource_find_all=True).
# Having resource attachments in a common folder is bad practice,
# because they would be visible to all users, and their names may be
# the same, conflicting.
where + = [ ( ' res_model ' , ' = ' , False ) ]
fil_obj = dirobj . pool . get ( ' ir.attachment ' )
ids = fil_obj . search ( cr , uid , where , context = ctx )
if ids :
for fil in fil_obj . browse ( cr , uid , ids , context = ctx ) :
klass = self . context . node_file_class
res . append ( klass ( fil . name , self , self . context , fil ) )
return res
def rmcol ( self , cr ) :
uid = self . context . uid
directory = self . context . _dirobj . browse ( cr , uid , self . dir_id )
res = False
if not directory :
raise OSError ( 2 , ' Not such file or directory. ' )
if not self . check_perms ( ' u ' ) :
raise IOError ( errno . EPERM , " Permission denied. " )
2014-07-06 14:44:26 +00:00
if directory . _name == ' document.directory ' :
2012-12-17 01:19:58 +00:00
if self . children ( cr ) :
raise OSError ( 39 , ' Directory not empty. ' )
res = self . context . _dirobj . unlink ( cr , uid , [ directory . id ] )
else :
raise OSError ( 1 , ' Operation is not permitted. ' )
return res
def create_child_collection ( self , cr , objname ) :
object2 = False
if not self . check_perms ( 2 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
dirobj = self . context . _dirobj
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
obj = dirobj . browse ( cr , uid , self . dir_id )
if obj and ( obj . type == ' ressource ' ) and not object2 :
raise OSError ( 1 , ' Operation is not permitted. ' )
#objname = uri2[-1]
val = {
' name ' : objname ,
' ressource_parent_type_id ' : obj and obj . ressource_type_id . id or False ,
' ressource_id ' : object2 and object2 . id or False ,
' parent_id ' : obj and obj . id or False
}
return dirobj . create ( cr , uid , val )
def create_child ( self , cr , path , data = None ) :
""" API function to create a child file object and node
Return the node_ * created
"""
if not self . check_perms ( 2 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
dirobj = self . context . _dirobj
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
fil_obj = dirobj . pool . get ( ' ir.attachment ' )
val = {
' name ' : path ,
' datas_fname ' : path ,
' parent_id ' : self . dir_id ,
# Datas are not set here
}
fil_id = fil_obj . create ( cr , uid , val , context = ctx )
fil = fil_obj . browse ( cr , uid , fil_id , context = ctx )
fnode = node_file ( path , self , self . context , fil )
if data is not None :
fnode . set_data ( cr , data , fil )
return fnode
def _get_ttag ( self , cr ) :
return ' dir- %d ' % self . dir_id
def move_to ( self , cr , ndir_node , new_name = False , fil_obj = None , ndir_obj = None , in_write = False ) :
""" Move directory. This operation is simple, since the present node is
only used for static , simple directories .
Note / may / be called with ndir_node = None , to rename the document root .
"""
if ndir_node and ( ndir_node . context != self . context ) :
raise NotImplementedError ( " Cannot move directories between contexts. " )
if ( not self . check_perms ( ' u ' ) ) or ( not ndir_node . check_perms ( ' w ' ) ) :
raise IOError ( errno . EPERM , " Permission denied. " )
dir_obj = self . context . _dirobj
if not fil_obj :
dbro = dir_obj . browse ( cr , self . context . uid , self . dir_id , context = self . context . context )
else :
dbro = dir_obj
assert dbro . id == self . dir_id
if not dbro :
raise IndexError ( " Cannot locate dir %d " , self . dir_id )
if ( not self . parent ) and ndir_node :
if not dbro . parent_id :
raise IOError ( errno . EPERM , " Cannot move the root directory! " )
self . parent = self . context . get_dir_node ( cr , dbro . parent_id )
assert self . parent
if self . parent != ndir_node :
_logger . debug ( ' Cannot move dir %r from %r to %r . ' , self , self . parent , ndir_node )
raise NotImplementedError ( ' Cannot move dir to another dir. ' )
ret = { }
if new_name and ( new_name != dbro . name ) :
if ndir_node . child ( cr , new_name ) :
raise IOError ( errno . EEXIST , " Destination path already exists. " )
ret [ ' name ' ] = new_name
del dbro
if not in_write :
# We have to update the data ourselves
if ret :
ctx = self . context . context . copy ( )
ctx [ ' __from_node ' ] = True
dir_obj . write ( cr , self . context . uid , [ self . dir_id , ] , ret , ctx )
ret = True
return ret
class node_res_dir ( node_class ) :
""" A folder containing dynamic folders
A special sibling to node_dir , which does only contain dynamically
created folders foreach resource in the foreign model .
All folders should be of type node_res_obj and merely behave like
node_dirs ( with limited domain ) .
"""
our_type = ' collection '
res_obj_class = None
def __init__ ( self , path , parent , context , dirr , dctx = None ) :
super ( node_res_dir , self ) . __init__ ( path , parent , context )
self . dir_id = dirr . id
#todo: more info from dirr
self . mimetype = ' application/x-directory '
# 'httpd/unix-directory'
self . create_date = dirr . create_date
# TODO: the write date should be MAX(file.write)..
self . write_date = dirr . write_date or dirr . create_date
self . content_length = 0
try :
self . uuser = ( dirr . user_id and dirr . user_id . login ) or ' nobody '
except Exception :
self . uuser = ' nobody '
self . ugroup = mkdosname ( dirr . company_id and dirr . company_id . name , default = ' nogroup ' )
self . uidperms = dirr . get_dir_permissions ( )
self . unixperms = 040000 | _uid2unixperms ( self . uidperms , dirr and dirr . user_id )
self . res_model = dirr . ressource_type_id and dirr . ressource_type_id . model or False
self . resm_id = dirr . ressource_id
self . res_find_all = dirr . resource_find_all
self . namefield = dirr . resource_field . name or ' name '
self . displayname = dirr . name
# Important: the domain is evaluated using the *parent* dctx!
self . domain = dirr . domain
self . ressource_tree = dirr . ressource_tree
# and then, we add our own vars in the dctx:
if dctx :
self . dctx . update ( dctx )
# and then, we prepare a dctx dict, for deferred evaluation:
self . dctx_dict = { }
for dfld in dirr . dctx_ids :
2012-12-18 16:58:39 +00:00
self . dctx_dict [ dfld . field ] = dfld . expr
2012-12-17 01:19:58 +00:00
def __eq__ ( self , other ) :
if type ( self ) != type ( other ) :
return False
if not self . context == other . context :
return False
# Two nodes, for the same document.directory, may have a
# different context! (dynamic folders)
if self . dctx != other . dctx :
return False
return self . dir_id == other . dir_id
def children ( self , cr , domain = None ) :
return self . _child_get ( cr , domain = domain )
def child ( self , cr , name , domain = None ) :
res = self . _child_get ( cr , name , domain = domain )
if res :
return res [ 0 ]
return None
def _child_get ( self , cr , name = None , domain = None ) :
""" return virtual children of resource, based on the
foreign object .
Note that many objects use NULL for a name , so we should
better call the name_search ( ) , name_get ( ) set of methods
"""
2013-03-29 14:37:20 +00:00
if self . res_model not in self . context . _dirobj . pool :
2012-12-17 01:19:58 +00:00
return [ ]
2013-03-29 14:37:20 +00:00
obj = self . context . _dirobj . pool [ self . res_model ]
2012-12-17 01:19:58 +00:00
dirobj = self . context . _dirobj
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
ctx . update ( self . context . extra_ctx )
where = [ ]
if self . domain :
app = safe_eval ( self . domain , ctx )
if not app :
pass
elif isinstance ( app , list ) :
where . extend ( app )
elif isinstance ( app , tuple ) :
where . append ( app )
else :
raise RuntimeError ( " Incorrect domain expr: %s . " % self . domain )
if self . resm_id :
where . append ( ( ' id ' , ' = ' , self . resm_id ) )
if name :
# The =like character will match underscores against any characters
# including the special ones that couldn't exist in a FTP/DAV request
where . append ( ( self . namefield , ' =like ' , name . replace ( ' \\ ' , ' \\ \\ ' ) ) )
is_allowed = self . check_perms ( 1 )
else :
is_allowed = self . check_perms ( 5 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission denied. " )
# print "Where clause for %s" % self.res_model, where
if self . ressource_tree :
object2 = False
if self . resm_id :
2013-03-29 14:37:20 +00:00
object2 = dirobj . pool [ self . res_model ] . browse ( cr , uid , self . resm_id ) or False
2012-12-17 01:19:58 +00:00
if obj . _parent_name in obj . fields_get ( cr , uid ) :
where . append ( ( obj . _parent_name , ' = ' , object2 and object2 . id or False ) )
resids = obj . search ( cr , uid , where , context = ctx )
res = [ ]
for bo in obj . browse ( cr , uid , resids , context = ctx ) :
if not bo :
continue
res_name = getattr ( bo , self . namefield )
if not res_name :
continue
# Yes! we can't do better but skip nameless records.
# Escape the name for characters not supported in filenames
res_name = res_name . replace ( ' / ' , ' _ ' ) # any other weird char?
if name and ( res_name != ustr ( name ) ) :
# we have matched _ to any character, but we only meant to match
# the special ones.
# Eg. 'a_c' will find 'abc', 'a/c', 'a_c', may only
# return 'a/c' and 'a_c'
continue
res . append ( self . res_obj_class ( res_name , self . dir_id , self , self . context , self . res_model , bo ) )
return res
def _get_ttag ( self , cr ) :
return ' rdir- %d ' % self . dir_id
class node_res_obj ( node_class ) :
""" A dynamically created folder.
A special sibling to node_dir , which does only contain dynamically
created folders foreach resource in the foreign model .
All folders should be of type node_res_obj and merely behave like
node_dirs ( with limited domain ) .
"""
our_type = ' collection '
def __init__ ( self , path , dir_id , parent , context , res_model , res_bo , res_id = None ) :
super ( node_res_obj , self ) . __init__ ( path , parent , context )
assert parent
#todo: more info from dirr
self . dir_id = dir_id
self . mimetype = ' application/x-directory '
# 'httpd/unix-directory'
self . create_date = parent . create_date
# TODO: the write date should be MAX(file.write)..
self . write_date = parent . write_date
self . content_length = 0
self . uidperms = parent . uidperms & 15
self . unixperms = 040000 | _uid2unixperms ( self . uidperms , True )
self . uuser = parent . uuser
self . ugroup = parent . ugroup
self . res_model = res_model
self . domain = parent . domain
self . displayname = path
self . dctx_dict = parent . dctx_dict
if isinstance ( parent , node_res_dir ) :
self . res_find_all = parent . res_find_all
else :
self . res_find_all = False
if res_bo :
self . res_id = res_bo . id
dc2 = self . context . context . copy ( )
dc2 . update ( self . dctx )
dc2 [ ' res_model ' ] = res_model
dc2 [ ' res_id ' ] = res_bo . id
dc2 [ ' this ' ] = res_bo
for fld , expr in self . dctx_dict . items ( ) :
try :
self . dctx [ fld ] = safe_eval ( expr , dc2 )
except Exception , e :
print " Cannot eval %s for %s . " % ( expr , fld )
print e
pass
else :
self . res_id = res_id
def __eq__ ( self , other ) :
if type ( self ) != type ( other ) :
return False
if not self . context == other . context :
return False
if not self . res_model == other . res_model :
return False
if not self . res_id == other . res_id :
return False
if self . domain != other . domain :
return False
if self . res_find_all != other . res_find_all :
return False
if self . dctx != other . dctx :
return False
return self . dir_id == other . dir_id
def children ( self , cr , domain = None ) :
return self . _child_get ( cr , domain = domain ) + self . _file_get ( cr )
def child ( self , cr , name , domain = None ) :
res = self . _child_get ( cr , name , domain = domain )
if res :
return res [ 0 ]
res = self . _file_get ( cr , name )
if res :
return res [ 0 ]
return None
def _file_get ( self , cr , nodename = False ) :
res = [ ]
is_allowed = self . check_perms ( ( nodename and 1 ) or 5 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission denied. " )
cntobj = self . context . _dirobj . pool . get ( ' document.directory.content ' )
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
where = [ ( ' directory_id ' , ' = ' , self . dir_id ) ]
#if self.domain:
# where.extend(self.domain)
# print "res_obj file_get clause", where
ids = cntobj . search ( cr , uid , where , context = ctx )
for content in cntobj . browse ( cr , uid , ids , context = ctx ) :
res3 = cntobj . _file_get ( cr , self , nodename , content , context = ctx )
if res3 :
res . extend ( res3 )
return res
def get_dav_props_DEPR ( self , cr ) :
# Deprecated! (but document_ics must be cleaned, first)
res = { }
cntobj = self . context . _dirobj . pool . get ( ' document.directory.content ' )
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
where = [ ( ' directory_id ' , ' = ' , self . dir_id ) ]
ids = cntobj . search ( cr , uid , where , context = ctx )
for content in cntobj . browse ( cr , uid , ids , context = ctx ) :
if content . extension == ' .ics ' : # FIXME: call the content class!
res [ ' http://groupdav.org/ ' ] = ( ' resourcetype ' , )
return res
def get_dav_eprop_DEPR ( self , cr , ns , prop ) :
# Deprecated!
if ns != ' http://groupdav.org/ ' or prop != ' resourcetype ' :
_logger . warning ( " Who asks for %s : %s ? " % ( ns , prop ) )
return None
cntobj = self . context . _dirobj . pool . get ( ' document.directory.content ' )
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
where = [ ( ' directory_id ' , ' = ' , self . dir_id ) ]
ids = cntobj . search ( cr , uid , where , context = ctx )
for content in cntobj . browse ( cr , uid , ids , context = ctx ) :
# TODO: remove relic of GroupDAV
if content . extension == ' .ics ' : # FIXME: call the content class!
return ( ' vevent-collection ' , ' http://groupdav.org/ ' )
return None
def _child_get ( self , cr , name = None , domain = None ) :
dirobj = self . context . _dirobj
is_allowed = self . check_perms ( ( name and 1 ) or 5 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission denied. " )
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
directory = dirobj . browse ( cr , uid , self . dir_id )
2013-03-29 14:37:20 +00:00
obj = dirobj . pool [ self . res_model ]
2012-12-17 01:19:58 +00:00
where = [ ]
res = [ ]
if name :
where . append ( ( ' name ' , ' = ' , name ) )
# Directory Structure display in tree structure
if self . res_id and directory . ressource_tree :
where1 = [ ]
if name :
where1 . append ( ( ' name ' , ' =like ' , name . replace ( ' \\ ' , ' \\ \\ ' ) ) )
if obj . _parent_name in obj . fields_get ( cr , uid ) :
where1 . append ( ( obj . _parent_name , ' = ' , self . res_id ) )
namefield = directory . resource_field . name or ' name '
resids = obj . search ( cr , uid , where1 , context = ctx )
for bo in obj . browse ( cr , uid , resids , context = ctx ) :
if not bo :
continue
res_name = getattr ( bo , namefield )
if not res_name :
continue
res_name = res_name . replace ( ' / ' , ' _ ' )
if name and ( res_name != ustr ( name ) ) :
continue
# TODO Revise
klass = directory . get_node_class ( directory , dynamic = True , context = ctx )
rnode = klass ( res_name , dir_id = self . dir_id , parent = self , context = self . context ,
res_model = self . res_model , res_bo = bo )
rnode . res_find_all = self . res_find_all
res . append ( rnode )
where2 = where + [ ( ' parent_id ' , ' = ' , self . dir_id ) ]
ids = dirobj . search ( cr , uid , where2 , context = ctx )
bo = obj . browse ( cr , uid , self . res_id , context = ctx )
for dirr in dirobj . browse ( cr , uid , ids , context = ctx ) :
if name and ( name != dirr . name ) :
continue
if dirr . type == ' directory ' :
klass = dirr . get_node_class ( dirr , dynamic = True , context = ctx )
res . append ( klass ( dirr . name , dirr . id , self , self . context , self . res_model , res_bo = bo , res_id = self . res_id ) )
elif dirr . type == ' ressource ' :
# child resources can be controlled by properly set dctx
klass = dirr . get_node_class ( dirr , context = ctx )
res . append ( klass ( dirr . name , self , self . context , dirr , { ' active_id ' : self . res_id } ) ) # bo?
fil_obj = dirobj . pool . get ( ' ir.attachment ' )
if self . res_find_all :
where2 = where
where3 = where2 + [ ( ' res_model ' , ' = ' , self . res_model ) , ( ' res_id ' , ' = ' , self . res_id ) ]
# print "where clause for dir_obj", where3
ids = fil_obj . search ( cr , uid , where3 , context = ctx )
if ids :
for fil in fil_obj . browse ( cr , uid , ids , context = ctx ) :
klass = self . context . node_file_class
res . append ( klass ( fil . name , self , self . context , fil ) )
# Get Child Ressource Directories
if directory . ressource_type_id and directory . ressource_type_id . id :
where4 = where + [ ( ' ressource_parent_type_id ' , ' = ' , directory . ressource_type_id . id ) ]
where5 = where4 + [ ' | ' , ( ' ressource_id ' , ' = ' , 0 ) , ( ' ressource_id ' , ' = ' , self . res_id ) ]
dirids = dirobj . search ( cr , uid , where5 )
for dirr in dirobj . browse ( cr , uid , dirids , context = ctx ) :
if dirr . type == ' directory ' and not dirr . parent_id :
klass = dirr . get_node_class ( dirr , dynamic = True , context = ctx )
rnode = klass ( dirr . name , dirr . id , self , self . context , self . res_model , res_bo = bo , res_id = self . res_id )
rnode . res_find_all = dirr . resource_find_all
res . append ( rnode )
if dirr . type == ' ressource ' :
klass = dirr . get_node_class ( dirr , context = ctx )
rnode = klass ( dirr . name , self , self . context , dirr , { ' active_id ' : self . res_id } )
rnode . res_find_all = dirr . resource_find_all
res . append ( rnode )
return res
def create_child_collection ( self , cr , objname ) :
dirobj = self . context . _dirobj
is_allowed = self . check_perms ( 2 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission denied. " )
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
2013-03-29 14:37:20 +00:00
res_obj = dirobj . pool [ self . res_model ]
2012-12-17 01:19:58 +00:00
object2 = res_obj . browse ( cr , uid , self . res_id ) or False
obj = dirobj . browse ( cr , uid , self . dir_id )
if obj and ( obj . type == ' ressource ' ) and not object2 :
raise OSError ( 1 , ' Operation is not permitted. ' )
val = {
' name ' : objname ,
' ressource_parent_type_id ' : obj and obj . ressource_type_id . id or False ,
' ressource_id ' : object2 and object2 . id or False ,
' parent_id ' : False ,
' resource_find_all ' : False ,
}
if ( obj and ( obj . type in ( ' directory ' ) ) ) or not object2 :
val [ ' parent_id ' ] = obj and obj . id or False
return dirobj . create ( cr , uid , val )
def create_child ( self , cr , path , data = None ) :
""" API function to create a child file object and node
Return the node_ * created
"""
is_allowed = self . check_perms ( 2 )
if not is_allowed :
raise IOError ( errno . EPERM , " Permission denied. " )
dirobj = self . context . _dirobj
uid = self . context . uid
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
fil_obj = dirobj . pool . get ( ' ir.attachment ' )
val = {
' name ' : path ,
' datas_fname ' : path ,
' res_model ' : self . res_model ,
' res_id ' : self . res_id ,
# Datas are not set here
}
if not self . res_find_all :
val [ ' parent_id ' ] = self . dir_id
fil_id = fil_obj . create ( cr , uid , val , context = ctx )
fil = fil_obj . browse ( cr , uid , fil_id , context = ctx )
klass = self . context . node_file_class
fnode = klass ( path , self , self . context , fil )
if data is not None :
fnode . set_data ( cr , data , fil )
return fnode
def _get_ttag ( self , cr ) :
return ' rodir- %d - %d ' % ( self . dir_id , self . res_id )
node_res_dir . res_obj_class = node_res_obj
class node_file ( node_class ) :
our_type = ' file '
def __init__ ( self , path , parent , context , fil ) :
super ( node_file , self ) . __init__ ( path , parent , context )
self . file_id = fil . id
#todo: more info from ir_attachment
if fil . file_type and ' / ' in fil . file_type :
self . mimetype = str ( fil . file_type )
self . create_date = fil . create_date
self . write_date = fil . write_date or fil . create_date
self . content_length = fil . file_size
self . displayname = fil . name
self . uidperms = 14
if parent :
if not parent . check_perms ( ' x ' ) :
self . uidperms = 0
elif not parent . check_perms ( ' w ' ) :
self . uidperms = 4
try :
self . uuser = ( fil . user_id and fil . user_id . login ) or ' nobody '
except Exception :
self . uuser = ' nobody '
self . ugroup = mkdosname ( fil . company_id and fil . company_id . name , default = ' nogroup ' )
def __eq__ ( self , other ) :
if type ( self ) != type ( other ) :
return False
if not self . context == other . context :
return False
if self . dctx != other . dctx :
return False
return self . file_id == other . file_id
def open_data ( self , cr , mode ) :
if not self . check_perms ( 4 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
stobj = self . context . _dirobj . pool . get ( ' document.storage ' )
return stobj . get_file ( cr , self . context . uid , None , self , mode = mode , context = self . context . context )
def rm ( self , cr ) :
uid = self . context . uid
if not self . check_perms ( 8 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
document_obj = self . context . _dirobj . pool . get ( ' ir.attachment ' )
if self . type in ( ' collection ' , ' database ' ) :
return False
document = document_obj . browse ( cr , uid , self . file_id , context = self . context . context )
res = False
2014-07-06 14:44:26 +00:00
if document and document . _name == ' ir.attachment ' :
2012-12-17 01:19:58 +00:00
res = document_obj . unlink ( cr , uid , [ document . id ] )
return res
def fix_ppath ( self , cr , fbro ) :
""" Sometimes we may init this w/o path, parent.
This function fills the missing path from the file browse object
Note : this may be an expensive operation , do on demand . However ,
once caching is in , we might want to do that at init time and keep
this object anyway
"""
if self . path or self . parent :
return
assert fbro
uid = self . context . uid
dirpath = [ ]
if fbro . parent_id :
dirobj = self . context . _dirobj . pool . get ( ' document.directory ' )
dirpath = dirobj . get_full_path ( cr , uid , fbro . parent_id . id , context = self . context . context )
if fbro . datas_fname :
dirpath . append ( fbro . datas_fname )
else :
dirpath . append ( fbro . name )
if len ( dirpath ) > 1 :
self . path = dirpath
else :
self . path = dirpath [ 0 ]
def get_data ( self , cr , fil_obj = None ) :
""" Retrieve the data for some file.
fil_obj may optionally be specified , and should be a browse object
for the file . This is useful when the caller has already initiated
the browse object . """
if not self . check_perms ( 4 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
stobj = self . context . _dirobj . pool . get ( ' document.storage ' )
return stobj . get_data ( cr , self . context . uid , None , self , self . context . context , fil_obj )
def get_data_len ( self , cr , fil_obj = None ) :
bin_size = self . context . context . get ( ' bin_size ' , False )
if bin_size and not self . content_length :
self . content_length = fil_obj . db_datas
return self . content_length
def set_data ( self , cr , data , fil_obj = None ) :
""" Store data at some file.
fil_obj may optionally be specified , and should be a browse object
for the file . This is useful when the caller has already initiated
the browse object . """
if not self . check_perms ( 2 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
stobj = self . context . _dirobj . pool . get ( ' document.storage ' )
return stobj . set_data ( cr , self . context . uid , None , self , data , self . context . context , fil_obj )
def _get_ttag ( self , cr ) :
return ' file- %d ' % self . file_id
def move_to ( self , cr , ndir_node , new_name = False , fil_obj = None , ndir_obj = None , in_write = False ) :
if ndir_node and ndir_node . context != self . context :
raise NotImplementedError ( " Cannot move files between contexts. " )
if ( not self . check_perms ( 8 ) ) and ndir_node . check_perms ( 2 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
doc_obj = self . context . _dirobj . pool . get ( ' ir.attachment ' )
if not fil_obj :
dbro = doc_obj . browse ( cr , self . context . uid , self . file_id , context = self . context . context )
else :
dbro = fil_obj
assert dbro . id == self . file_id , " %s != %s for %r . " % ( dbro . id , self . file_id , self )
if not dbro :
raise IndexError ( " Cannot locate doc %d . " , self . file_id )
if ( not self . parent ) :
# there *must* be a parent node for this one
self . parent = self . context . get_dir_node ( cr , dbro . parent_id )
assert self . parent
ret = { }
if ndir_node and self . parent != ndir_node :
if not ( isinstance ( self . parent , node_dir ) and isinstance ( ndir_node , node_dir ) ) :
_logger . debug ( ' Cannot move file %r from %r to %r . ' , self , self . parent , ndir_node )
raise NotImplementedError ( ' Cannot move files between dynamic folders. ' )
if not ndir_obj :
ndir_obj = self . context . _dirobj . browse ( cr , self . context . uid , \
ndir_node . dir_id , context = self . context . context )
assert ndir_obj . id == ndir_node . dir_id
r2 = { ' parent_id ' : ndir_obj . id }
ret . update ( r2 )
if new_name and ( new_name != dbro . name ) :
if len ( ret ) :
raise NotImplementedError ( " Cannot rename and move. " ) # TODO
r2 = { ' name ' : new_name , ' datas_fname ' : new_name }
ret . update ( r2 )
del dbro
if not in_write :
# We have to update the data ourselves
if ret :
ctx = self . context . context . copy ( )
ctx [ ' __from_node ' ] = True
doc_obj . write ( cr , self . context . uid , [ self . file_id , ] , ret , ctx )
ret = True
return ret
class node_content ( node_class ) :
our_type = ' content '
def __init__ ( self , path , parent , context , cnt , dctx = None , act_id = None ) :
super ( node_content , self ) . __init__ ( path , parent , context )
self . cnt_id = cnt . id
self . create_date = False
self . write_date = False
self . content_length = False
self . unixperms = 0640
if parent :
self . uidperms = parent . uidperms & 14
self . uuser = parent . uuser
self . ugroup = parent . ugroup
self . extension = cnt . extension
self . report_id = cnt . report_id and cnt . report_id . id
#self.mimetype = cnt.extension.
self . displayname = path
if dctx :
self . dctx . update ( dctx )
self . act_id = act_id
def fill_fields ( self , cr , dctx = None ) :
""" Try to read the object and fill missing fields, like mimetype,
dates etc .
This function must be different from the constructor , because
it uses the db cursor .
"""
cr . execute ( ' SELECT DISTINCT mimetype FROM document_directory_content_type WHERE active AND code = %s ; ' ,
( self . extension , ) )
res = cr . fetchall ( )
if res and res [ 0 ] [ 0 ] :
self . mimetype = str ( res [ 0 ] [ 0 ] )
def get_data ( self , cr , fil_obj = None ) :
cntobj = self . context . _dirobj . pool . get ( ' document.directory.content ' )
if not self . check_perms ( 4 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
data = cntobj . process_read ( cr , self . context . uid , self , ctx )
if data :
self . content_length = len ( data )
return data
def open_data ( self , cr , mode ) :
if mode . endswith ( ' b ' ) :
mode = mode [ : - 1 ]
if mode in ( ' r ' , ' w ' ) :
cperms = mode [ : 1 ]
elif mode in ( ' r+ ' , ' w+ ' ) :
cperms = ' rw '
else :
raise IOError ( errno . EINVAL , " Cannot open at mode %s . " % mode )
if not self . check_perms ( cperms ) :
raise IOError ( errno . EPERM , " Permission denied. " )
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
return nodefd_content ( self , cr , mode , ctx )
def get_data_len ( self , cr , fil_obj = None ) :
# FIXME : here, we actually generate the content twice!!
# we should have cached the generated content, but it is
# not advisable to do keep it in memory, until we have a cache
# expiration logic.
if not self . content_length :
self . get_data ( cr , fil_obj )
return self . content_length
def set_data ( self , cr , data , fil_obj = None ) :
cntobj = self . context . _dirobj . pool . get ( ' document.directory.content ' )
if not self . check_perms ( 2 ) :
raise IOError ( errno . EPERM , " Permission denied. " )
ctx = self . context . context . copy ( )
ctx . update ( self . dctx )
return cntobj . process_write ( cr , self . context . uid , self , data , ctx )
def _get_ttag ( self , cr ) :
return ' cnt- %d %s ' % ( self . cnt_id , ( self . act_id and ( ' - ' + str ( self . act_id ) ) ) or ' ' )
def get_dav_resourcetype ( self , cr ) :
return ' '
class node_descriptor ( object ) :
""" A file-like interface to the data contents of a node.
This class is NOT a node , but an / open descriptor / for some
node . It can hold references to a cursor or a file object ,
because the life of a node_descriptor will be the open period
of the data .
It should also take care of locking , with any native mechanism
or using the db .
For the implementation , it would be OK just to wrap around file ,
StringIO or similar class . The node_descriptor is only needed to
provide the link to the parent / node / object .
"""
def __init__ ( self , parent ) :
assert isinstance ( parent , node_class )
self . name = parent . displayname
self . __parent = parent
def _get_parent ( self ) :
return self . __parent
def open ( self , * * kwargs ) :
raise NotImplementedError
def close ( self ) :
raise NotImplementedError
def read ( self , size = None ) :
raise NotImplementedError
def seek ( self , offset , whence = None ) :
raise NotImplementedError
def tell ( self ) :
raise NotImplementedError
def write ( self , str ) :
raise NotImplementedError
def size ( self ) :
raise NotImplementedError
def __len__ ( self ) :
return self . size ( )
def __nonzero__ ( self ) :
""" Ensure that a node_descriptor will never equal False
Since we do define __len__ and __iter__ for us , we must avoid
being regarded as non - true objects .
"""
return True
def next ( self , str ) :
raise NotImplementedError
class nodefd_content ( StringIO , node_descriptor ) :
""" A descriptor to content nodes
"""
def __init__ ( self , parent , cr , mode , ctx ) :
node_descriptor . __init__ ( self , parent )
self . _context = ctx
self . _size = 0 L
if mode in ( ' r ' , ' r+ ' ) :
cntobj = parent . context . _dirobj . pool . get ( ' document.directory.content ' )
data = cntobj . process_read ( cr , parent . context . uid , parent , ctx )
if data :
self . _size = len ( data )
parent . content_length = len ( data )
StringIO . __init__ ( self , data )
elif mode in ( ' w ' , ' w+ ' ) :
StringIO . __init__ ( self , None )
# at write, we start at 0 (= overwrite), but have the original
# data available, in case of a seek()
elif mode == ' a ' :
StringIO . __init__ ( self , None )
else :
_logger . error ( " Incorrect mode %s is specified. " , mode )
raise IOError ( errno . EINVAL , " Invalid file mode. " )
self . mode = mode
def size ( self ) :
return self . _size
def close ( self ) :
# we now open a *separate* cursor, to update the data.
# FIXME: this may be improved, for concurrency handling
if self . mode == ' r ' :
StringIO . close ( self )
return
par = self . _get_parent ( )
uid = par . context . uid
2014-04-09 09:56:59 +00:00
cr = openerp . registry ( par . context . dbname ) . cursor ( )
2012-12-17 01:19:58 +00:00
try :
if self . mode in ( ' w ' , ' w+ ' , ' r+ ' ) :
data = self . getvalue ( )
cntobj = par . context . _dirobj . pool . get ( ' document.directory.content ' )
cntobj . process_write ( cr , uid , par , data , par . context . context )
elif self . mode == ' a ' :
raise NotImplementedError
cr . commit ( )
except Exception :
_logger . exception ( ' Cannot update db content # %d for close. ' , par . cnt_id )
raise
finally :
cr . close ( )
StringIO . close ( self )
class nodefd_static ( StringIO , node_descriptor ) :
""" A descriptor to nodes with static data.
"""
def __init__ ( self , parent , cr , mode , ctx = None ) :
node_descriptor . __init__ ( self , parent )
self . _context = ctx
self . _size = 0 L
if mode in ( ' r ' , ' r+ ' ) :
data = parent . get_data ( cr )
if data :
self . _size = len ( data )
parent . content_length = len ( data )
StringIO . __init__ ( self , data )
elif mode in ( ' w ' , ' w+ ' ) :
StringIO . __init__ ( self , None )
# at write, we start at 0 (= overwrite), but have the original
# data available, in case of a seek()
elif mode == ' a ' :
StringIO . __init__ ( self , None )
else :
_logger . error ( " Incorrect mode %s is specified. " , mode )
raise IOError ( errno . EINVAL , " Invalid file mode. " )
self . mode = mode
def size ( self ) :
return self . _size
def close ( self ) :
# we now open a *separate* cursor, to update the data.
# FIXME: this may be improved, for concurrency handling
if self . mode == ' r ' :
StringIO . close ( self )
return
par = self . _get_parent ( )
# uid = par.context.uid
2014-04-09 09:56:59 +00:00
cr = openerp . registry ( par . context . dbname ) . cursor ( )
2012-12-17 01:19:58 +00:00
try :
if self . mode in ( ' w ' , ' w+ ' , ' r+ ' ) :
data = self . getvalue ( )
par . set_data ( cr , data )
elif self . mode == ' a ' :
raise NotImplementedError
cr . commit ( )
except Exception :
_logger . exception ( ' Cannot update db content # %d for close. ' , par . cnt_id )
raise
finally :
cr . close ( )
StringIO . close ( self )
class nodefd_db ( StringIO , node_descriptor ) :
""" A descriptor to db data
"""
def __init__ ( self , parent , ira_browse , mode ) :
node_descriptor . __init__ ( self , parent )
self . _size = 0 L
if mode . endswith ( ' b ' ) :
mode = mode [ : - 1 ]
if mode in ( ' r ' , ' r+ ' ) :
data = ira_browse . datas
if data :
data = data . decode ( ' base64 ' )
self . _size = len ( data )
StringIO . __init__ ( self , data )
elif mode in ( ' w ' , ' w+ ' ) :
StringIO . __init__ ( self , None )
# at write, we start at 0 (= overwrite), but have the original
# data available, in case of a seek()
elif mode == ' a ' :
StringIO . __init__ ( self , None )
else :
_logger . error ( " Incorrect mode %s is specified. " , mode )
raise IOError ( errno . EINVAL , " Invalid file mode. " )
self . mode = mode
def size ( self ) :
return self . _size
def close ( self ) :
# we now open a *separate* cursor, to update the data.
# FIXME: this may be improved, for concurrency handling
par = self . _get_parent ( )
# uid = par.context.uid
registry = openerp . modules . registry . RegistryManager . get ( par . context . dbname )
with registry . cursor ( ) as cr :
data = self . getvalue ( ) . encode ( ' base64 ' )
if self . mode in ( ' w ' , ' w+ ' , ' r+ ' ) :
registry . get ( ' ir.attachment ' ) . write ( cr , 1 , par . file_id , { ' datas ' : data } )
cr . commit ( )
StringIO . close ( self )
2011-11-22 08:51:38 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: