Files
tryton/trytond/model/fields/binary.py
T
Cédric Krier 7ca7f4c6f0 Allow to store Binary field in filestore
Two options are added on Binary field to define the column to use to store the
filestore id and the prefix to use.
Those options are used for Attachment and are configurable with conservative
default values.

issue5692
review25471003
2016-07-29 10:51:53 +02:00

137 lines
4.8 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from sql import Query, Expression, Column, Null
from .field import Field, SQLType
from ...transaction import Transaction
from ... import backend
from ...tools import grouped_slice, reduce_ids
from ...filestore import filestore
class Binary(Field):
'''
Define a binary field (``bytes``).
'''
_type = 'binary'
cast = bytearray if bytes == str else bytes
def __init__(self, string='', help='', required=False, readonly=False,
domain=None, states=None, select=False, on_change=None,
on_change_with=None, depends=None, context=None, loading='lazy',
filename=None, file_id=None, store_prefix=None):
if filename is not None:
self.filename = filename
if depends is None:
depends = [filename]
else:
depends.append(filename)
self.file_id = file_id
self.store_prefix = store_prefix
super(Binary, self).__init__(string=string, help=help,
required=required, readonly=readonly, domain=domain, states=states,
select=select, on_change=on_change, on_change_with=on_change_with,
depends=depends, context=context, loading=loading)
def get(self, ids, model, name, values=None):
'''
Convert the binary value into ``bytes``
:param ids: a list of ids
:param model: a string with the name of the model
:param name: a string with the name of the field
:param values: a dictionary with the read values
:return: a dictionary with ids as key and values as value
'''
if values is None:
values = {}
transaction = Transaction()
res = {}
converter = self.cast
default = None
format_ = Transaction().context.get(
'%s.%s' % (model.__name__, name), '')
if format_ == 'size':
converter = len
default = 0
if self.file_id:
table = model.__table__()
cursor = transaction.connection.cursor()
prefix = self.store_prefix
if prefix is None:
prefix = transaction.database.name
if format_ == 'size':
store_func = filestore.size
else:
def store_func(id, prefix):
return self.cast(filestore.get(id, prefix=prefix))
for sub_ids in grouped_slice(ids):
cursor.execute(*table.select(
table.id, Column(table, self.file_id),
where=reduce_ids(table.id, sub_ids)
& (Column(table, self.file_id) != Null)
& (Column(table, self.file_id) != '')))
for record_id, file_id in cursor:
try:
res[record_id] = store_func(file_id, prefix)
except (IOError, OSError):
pass
for i in values:
if i['id'] in res:
continue
value = i[name]
if value:
if isinstance(value, unicode):
value = value.encode('utf-8')
value = converter(value)
else:
value = default
res[i['id']] = value
for i in ids:
res.setdefault(i, default)
return res
def set(self, Model, name, ids, value, *args):
transaction = Transaction()
table = Model.__table__()
cursor = transaction.connection.cursor()
prefix = self.store_prefix
if prefix is None:
prefix = transaction.database.name
args = iter((ids, value) + args)
for ids, value in zip(args, args):
if self.file_id:
columns = [Column(table, self.file_id), Column(table, name)]
values = [
filestore.set(value, prefix) if value else None, None]
else:
columns = [Column(table, name)]
values = [self.sql_format(value)]
cursor.execute(*table.update(columns, values,
where=reduce_ids(table.id, ids)))
@staticmethod
def sql_format(value):
if isinstance(value, (Query, Expression)):
return value
db_type = backend.name()
if db_type == 'postgresql' and value is not None:
import psycopg2
return psycopg2.Binary(value)
return value
def sql_type(self):
db_type = backend.name()
if db_type == 'postgresql':
return SQLType('BYTEA', 'BYTEA')
elif db_type == 'mysql':
return SQLType('LONGBLOB', 'LONGBLOB')
return SQLType('BLOB', 'BLOB')