# 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')