You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
jam-py/jam/dataset.py

2022 lines
75 KiB

import datetime
import traceback
from werkzeug._compat import iteritems, iterkeys, text_type, string_types, to_unicode
from werkzeug.utils import cached_property
from .common import consts, error_message
FIELD_DEF = FIELD_ID, FIELD_NAME, FIELD_CAPTION, FIELD_DATA_TYPE, FIELD_SIZE, REQUIRED, LOOKUP_ITEM, \
LOOKUP_FIELD, LOOKUP_FIELD1, LOOKUP_FIELD2, FIELD_VISIBLE, \
FIELD_READ_ONLY, FIELD_DEFAULT, FIELD_DEFAULT_VALUE, MASTER_FIELD, FIELD_ALIGNMENT, \
FIELD_LOOKUP_VALUES, FIELD_MULTI_SELECT, FIELD_MULTI_SELECT_ALL, \
FIELD_ENABLE_TYPEAHEAD, FIELD_HELP, FIELD_PLACEHOLDER, FIELD_INTERFACE, \
FIELD_IMAGE, FIELD_FILE, DB_FIELD_NAME = range(26)
FILTER_DEF = FILTER_OBJ_NAME, FILTER_NAME, FILTER_FIELD_NAME, FILTER_TYPE, \
FILTER_MULTI_SELECT, FILTER_DATA_TYPE, FILTER_VISIBLE, FILTER_HELP, \
FILTER_PLACEHOLDER, FILTER_ID = range(10)
class DatasetException(Exception):
pass
class DatasetEmpty(Exception):
pass
class DatasetInvalidState(Exception):
pass
class FieldInvalidValue(Exception):
pass
class FieldInvalidLength(Exception):
pass
class FieldValueRequired(Exception):
pass
class FieldValidateError(Exception):
pass
class DBField(object):
def __init__(self, owner, field_def):
self.owner = owner
self.field_def = field_def
self.field_kind = consts.ITEM_FIELD
self.ID = field_def[FIELD_ID]
self.field_name = field_def[FIELD_NAME]
self.field_caption = field_def[FIELD_CAPTION]
self.data_type = field_def[FIELD_DATA_TYPE]
self.required = field_def[REQUIRED]
self.lookup_item = field_def[LOOKUP_ITEM]
self.master_field = field_def[MASTER_FIELD]
self.lookup_field = field_def[LOOKUP_FIELD]
self.lookup_db_field = None
self.lookup_item1 = None
self.lookup_field1 = field_def[LOOKUP_FIELD1]
self.lookup_db_field1 = None
self.lookup_item2 = None
self.lookup_field2 = field_def[LOOKUP_FIELD2]
self.lookup_db_field2 = None
self.read_only = field_def[FIELD_READ_ONLY]
self.view_visible = field_def[FIELD_VISIBLE]
self.field_size = field_def[FIELD_SIZE]
self.default = field_def[FIELD_DEFAULT]
self.default_value = field_def[FIELD_DEFAULT_VALUE]
self.alignment = field_def[FIELD_ALIGNMENT]
self.lookup_values = field_def[FIELD_LOOKUP_VALUES]
self.multi_select = field_def[FIELD_MULTI_SELECT]
self.multi_select_all = field_def[FIELD_MULTI_SELECT_ALL]
self.enable_typeahead = field_def[FIELD_ENABLE_TYPEAHEAD]
self.field_help = field_def[FIELD_HELP]
self.field_placeholder = field_def[FIELD_PLACEHOLDER]
self.field_mask = field_def[FIELD_INTERFACE]
self.field_image = field_def[FIELD_IMAGE]
self.field_file = field_def[FIELD_FILE]
self.db_field_name = field_def[DB_FIELD_NAME]
self.field_type = consts.FIELD_TYPE_NAMES[self.data_type]
self.filter = None
self.on_field_get_text_called = None
def __setattr__(self, name, value):
if name != 'owner' and self.owner and self.owner.task_locked():
raise Exception(consts.language('server_tree_immutable') + \
' Item: "%s", Field: "%s", Attribute: "%s"' % (self.owner.item_name, self.field_name, name))
super(DBField, self).__setattr__(name, value)
@property
def row(self):
if self.owner._dataset:
return self.owner._dataset[self.owner.rec_no]
else:
raise DatasetEmpty(consts.language('value_in_empty_dataset') % self.owner.item_name)
@property
def data(self):
if self.row and self.bind_index >= 0:
result = self.row[self.bind_index]
if self.data_type == consts.DATE:
if type(result) in string_types:
result = consts.convert_date(result)
elif self.data_type == consts.DATETIME:
if type(result) in string_types:
result = consts.convert_date_time(result)
return result
@data.setter
def data(self, value):
if self.row and (self.bind_index >= 0):
self.row[self.bind_index] = value
@property
def lookup_data(self):
if self.lookup_index:
if self.row and (self.lookup_index >= 0):
result = self.row[self.lookup_index]
if self.data_type == consts.DATETIME and result:
if type(result) in string_types:
result = result.replace('T', ' ')
return result
@lookup_data.setter
def lookup_data(self, value):
if self.lookup_index:
if self.row and (self.lookup_index >= 0):
self.row[self.lookup_index] = value
@property
def text(self):
result = self.value
if not result is None:
if self.data_type == consts.INTEGER:
result = str(result)
elif self.data_type == consts.FLOAT:
result = self.float_to_str(result)
elif self.data_type == consts.CURRENCY:
result = self.float_to_str(result)
elif self.data_type == consts.DATE:
result = self.date_to_str(result)
elif self.data_type == consts.DATETIME:
result = self.datetime_to_str(result)
elif self.data_type == consts.TEXT:
result = text_type(result)
elif self.data_type == consts.KEYS:
if len(result):
result = consts.language('items_selected') % len(result)
else:
result = text_type(result)
else:
result = ''
if self.data_type == consts.BOOLEAN:
if self.value:
result = consts.language('true')
else:
result = consts.language('false')
return result
@text.setter
def text(self, value):
if value != self.text:
if self.data_type == consts.TEXT:
self.value = text_type(value)
elif self.data_type == consts.INTEGER:
self.value = int(value)
if self.data_type == consts.FLOAT:
self.value = consts.str_to_float(value)
elif self.data_type == consts.CURRENCY:
self.value = consts.str_to_cur(value)
elif self.data_type == consts.DATE:
self.value = consts.str_to_date(value)
elif self.data_type == consts.DATETIME:
self.value = consts.str_to_datetime(value)
elif self.data_type == consts.BOOLEAN:
if value.upper() == consts.language('yes').upper():
self.value = True
else:
self.value = False
elif self.data_type == consts.KEYS:
pass
else:
self.value = value
@property
def raw_value(self): # depricated
return self.data
@property
def value(self):
result = self.data
if result is None:
if self.field_kind == consts.ITEM_FIELD:
if self.data_type in (consts.FLOAT, consts.INTEGER, consts.CURRENCY):
result = 0
elif self.data_type == consts.BOOLEAN:
result = False
elif self.data_type in [consts.TEXT, consts.LONGTEXT]:
result = ''
elif self.data_type == consts.KEYS:
result = [];
else:
if self.data_type == consts.TEXT:
if not isinstance(result, text_type):
result = to_unicode(result, 'utf-8')
elif self.data_type in (consts.FLOAT, consts.CURRENCY):
result = float(result)
elif self.data_type == consts.DATE:
result = consts.convert_date(result)
elif self.data_type == consts.DATETIME:
result = consts.convert_date_time(result)
elif self.data_type == consts.BOOLEAN:
if result:
result = True
else:
result = False
elif self.data_type == consts.KEYS:
if self.lookup_data:
result = self.lookup_data
else:
if isinstance(result, text_type):
result = result.split(';')
else:
result = [int(val) for val in result.split(';')]
self.lookup_data = result
return result
@value.setter
def value(self, value):
self.set_value(value)
def _change_lookup_field(self, lookup_value=None):
if self.lookup_item:
if self.owner:
master_field = self
if self.master_field:
master_field = self.master_field
master_field.lookup_value = None
for field in self.owner.fields:
if field.master_field == master_field:
field.lookup_value = None
if lookup_value:
self.lookup_value = lookup_value
def _do_before_changed(self):
if self.owner and not self.owner.is_changing():
raise DatasetInvalidState(consts.language('not_edit_insert_state') % self.owner.item_name)
def _check_system_field_value(self, value):
if self.field_kind == consts.ITEM_FIELD:
if self.field_name == self.owner._primary_key and self.value and self.value != value:
raise DatasetException(consts.language('no_primary_field_changing') % self.owner.item_name)
if self.field_name == self.owner._deleted_flag and self.value != value:
raise DatasetException(consts.language('no_deleted_field_changing') % self.owner.item_name)
def set_value(self, value, lookup_value=None):
self._check_system_field_value(value)
self.new_value = None
if not value is None:
self.new_value = value
try:
if self.data_type == consts.BOOLEAN:
if bool(value):
self.new_value = 1
else:
self.new_value = 0
elif self.data_type == consts.FLOAT:
self.new_value = float(value)
elif self.data_type == consts.CURRENCY:
self.new_value = consts.round(value, consts.FRAC_DIGITS)
elif self.data_type == consts.INTEGER:
self.new_value = int(value)
elif self.data_type == consts.KEYS:
self.new_value = ';'.join([str(v) for v in value])
lookup_value = value
except TypeError as e:
raise FieldInvalidValue(self.type_error(value))
except ValueError as e:
raise FieldInvalidValue(self.type_error(value))
if self.data != self.new_value:
self._do_before_changed()
self.data = self.new_value
self._change_lookup_field(lookup_value)
self._set_modified(True)
def type_error(self, value):
if self.data_type == consts.INTEGER:
mess = consts.language('invalid_int')
elif self.data_type == consts.FLOAT:
mess = consts.language('invalid_float')
elif self.data_type == consts.CURRENCY:
mess = consts.language('invalid_cur')
elif (self.data_type == consts.DATE) or (self.data_type == consts.DATE):
mess = consts.language('invalid_date')
elif self.data_type == consts.BOOLEAN:
mess = consts.language('invalid_bool')
else:
mess = consts.language('invalid_value')
try:
val = to_unicode(value)
mess = mess % val
except:
mess = mess.replace('%s', '')
return mess
@property
def old_value(self):
if self.owner._is_delta:
if self.row and self.bind_index >= 0:
try:
rec_info = self.row[len(self.row) - 1]
old_row = rec_info[consts.REC_OLD_REC]
if old_row:
result = old_row[self.bind_index]
elif self.owner.rec_deleted():
result = self.value
return result
except:
pass
else:
raise Exception('Only delta can have old value property.')
def _set_modified(self, value):
if self.owner:
self.owner._set_modified(value)
@property
def lookup_data_type(self):
if self.lookup_item:
return self.lookup_item._field_by_name(self.lookup_field).data_type;
def _get_value_in_list(self, value=None):
result = '';
if type(value) in string_types:
return value
for val, str_val in self.lookup_values:
if val == value:
result = str_val
return result
@property
def lookup_value(self):
value = None
if self.lookup_values and self.value:
try:
value = self._get_value_in_list(self.data)
except:
pass
elif self.lookup_item:
if self.value:
value = self.lookup_data
data_type = self.lookup_data_type
if data_type == consts.DATE:
if isinstance(value, text_type):
value = consts.convert_date(value)
elif data_type == consts.DATETIME:
if isinstance(value, text_type):
value = consts.convert_date_time(value)
elif self.data_type == consts.BOOLEAN:
value = bool(value)
return value
@lookup_value.setter
def lookup_value(self, value):
if self.lookup_item:
self.lookup_data = value
@property
def lookup_text(self):
result = ''
try:
if self.lookup_values and self.value:
result = self.lookup_value
elif self.lookup_item:
if self.value:
result = self.lookup_value
if result is None:
result = ''
else:
data_type = self.lookup_data_type
if data_type:
if data_type == consts.DATE:
result = self.date_to_str(result)
elif data_type == consts.DATETIME:
result = self.datetime_to_str(result)
elif data_type == consts.FLOAT:
result = self.float_to_str(result)
elif data_type == consts.CURRENCY:
result = self.cur_to_str(result)
result = text_type(result)
except Exception as e:
traceback.print_exc()
return result
@property
def display_text(self):
result = ''
if self.filter and self.filter.filter_type == consts.FILTER_IN \
and self.filter.field.lookup_item and self.filter.value:
result = consts.language('items_selected') % len(self.filter.value)
elif self.lookup_item:
result = self.lookup_text
elif self.lookup_values:
result = self.lookup_text
else:
if self.data_type == consts.CURRENCY:
if not self.data is None:
result = self.cur_to_str(self.value)
else:
result = self.text
if self.owner and not self.filter:
if self.owner.on_field_get_text:
if not self.on_field_get_text_called:
self.on_field_get_text_called = True
try:
res = self.owner.on_field_get_text(self)
if not res is None:
result = res
finally:
self.on_field_get_text_called = False
return result
def assign_default_value(self):
if self.default_value:
try:
if self.data_type == consts.INTEGER:
self.value = int(self.default_value)
elif self.data_type in [consts.FLOAT, consts.CURRENCY]:
self.value = float(self.default_value)
elif self.data_type == consts.DATE:
if self.default_value == 'current date':
self.value = datetime.date.today()
elif self.data_type == consts.DATETIME:
if self.default_value == 'current datetime':
self.value = datetime.datetime.now()
elif self.data_type == consts.BOOLEAN:
if self.default_value == 'true':
self.value = True
elif self.default_value == 'false':
self.value = False
elif self.data_type in [consts.TEXT, consts.LONGTEXT]:
self.value = self.default_value
except Exception as e:
self.log.exception(error_message(e))
def check_type(self):
if (self.data_type == consts.TEXT) and (self.field_size != 0) and \
(len(self.text) > self.field_size):
raise FieldInvalidLength(consts.language('invalid_length') % self.field_size)
return True
def check_reqired(self):
if self.required and self.data is None:
raise FieldValueRequired('%s "%s" - %s' % (consts.language('field'), self.field_name, consts.language('value_required')))
return True
def check_valid(self):
if self.check_reqired():
self.check_type()
return True
def system_field(self):
if self.field_name and self.field_name in (self.owner._primary_key, \
self.owner._deleted_flag, self.owner._master_id, self.owner._master_rec_id):
return True
def float_to_str(self, value):
return consts.float_to_str(value)
def cur_to_str(self, value):
return consts.cur_to_str(value)
def date_to_str(self, value):
return consts.date_to_str(value)
def datetime_to_str(self, value):
return consts.datetime_to_str(value)
def str_to_date(self, value):
return consts.str_to_date(value)
def str_to_datetime(self, value):
return consts.str_to_datetime(value)
def str_to_float(self, value):
return consts.str_to_float(value)
def str_to_cur(self, value):
return consts.str_to_cur(value)
class FilterField(DBField):
def __init__(self, fltr, field, owner):
DBField.__init__(self, owner, field.field_def)
self.field_kind = consts.FILTER_FIELD
self.filter = fltr
self.lookup_item = None
self._value = None
self._lookup_value = None
def _do_before_changed(self):
pass
@property
def data(self):
return self._value
@data.setter
def data(self, value):
self._value = value
def get_lookup_data(self):
return self._lookup_value
def set_lookup_data(self, value):
self._lookup_value = value
def check_reqired(self, value):
return True
def _set_modified(self, value):
pass
def _set_record_status(self, value):
pass
class DBFilter(object):
def __init__(self, owner, filter_def):
self.owner = owner
self.filter_def = filter_def
self.filter_name = filter_def[FILTER_OBJ_NAME]
self.filter_caption = filter_def[FILTER_NAME]
self.field_name = filter_def[FILTER_FIELD_NAME]
self.filter_type = filter_def[FILTER_TYPE]
self.data_type = filter_def[FILTER_DATA_TYPE]
self.multi_select_all = filter_def[FILTER_MULTI_SELECT]
self.visible = filter_def[FILTER_VISIBLE]
self.filter_help = filter_def[FILTER_HELP]
if type(self.field_name) == int:
self.field_name = owner._field_by_ID(self.field_name).field_name
field = self.owner._field_by_name(self.field_name)
if self.field_name:
self.field = FilterField(self, field, self.owner)
setattr(self, self.field_name, self.field)
if self.filter_type in (consts.FILTER_IN, consts.FILTER_NOT_IN):
self.field.multi_select = True;
if self.filter_type == consts.FILTER_RANGE:
self.field1 = FilterField(self, field, self.owner)
def __setattr__(self, name, value):
if name != 'owner' and self.owner and self.owner.task_locked():
raise Exception(consts.language('server_tree_immutable') + \
' Item: "%s", Filter: "%s", attribute: "%s"' % (self.owner.item_name, self.filter_name, name))
super(DBFilter, self).__setattr__(name, value)
@property
def value(self):
return self.field.data
@value.setter
def value(self, value):
if not isinstance(value, FilterField):
if self.filter_type == consts.FILTER_RANGE:
if value is None:
self.field.value = None;
self.field1.value = None;
else:
self.field.value = value[0];
self.field1.value = value[1];
else:
self.field.value = value
class DBList(list):
pass
class ChangeLog(object):
def __init__(self, item):
self.item = item
self._change_id = 0
self.records = []
self.logs = {}
self.fields = []
self.expanded = True
def get_change_id(self):
self._change_id += 1
return str(self._change_id)
def log_changes(self):
if self.item.master:
return self.item.master.change_log.log_changes()
else:
return self.item.log_changes;
def find_record_log(self):
result = None
if self.item.master:
record_log = self.item.master.change_log.find_record_log()
if record_log:
details = record_log['details']
detail = details.get(str(self.item.ID))
if not detail:
detail = {
'logs': {},
'records': self.item._dataset,
'fields': [field.field_name for field in self.item.fields],
'expanded': self.item.expanded
}
details[str(self.item.ID)] = detail
self.logs = detail['logs']
self.records = detail['records']
self.fields = detail['fields']
self.expanded = detail['expanded']
if self.item.record_count():
change_id = self.item.rec_change_id
if not change_id:
change_id = self.get_change_id()
self.item.rec_change_id = change_id;
result = self.logs.get(change_id)
if not result:
result = {
'old_record': None,
'record': self.cur_record(),
'details': {}
}
self.logs[change_id] = result
return result
def get_detail_log(self, detail_ID):
result = None
record_log = self.find_record_log()
details = record_log['details']
if details:
result = details.get(detail_ID)
if result is None and self.item._is_delta:
result = {'records': [], 'fields': [], 'expanded': False, 'logs': {}}
return result
def remove_record_log(self):
change_id = self.item.rec_change_id
if change_id:
self.find_record_log()
del self.logs[self.item.rec_change_id]
self.item.rec_change_id = None
self.item.record_status = consts.RECORD_UNCHANGED
def cur_record(self):
return self.item._dataset[self.item.rec_no]
def record_modified(self, record_log):
modified = False
old_rec = record_log['old_record']
cur_rec = record_log['record']
for i in range(self.item._record_lookup_index):
if old_rec[i] != cur_rec[i]:
modified = True
break
return modified
def copy_record(self, record, expanded=True):
result = None
if record:
if expanded:
result = record[0:self.item._record_info_index]
else:
result = record[0:self.item._record_lookup_index]
info = self.item.get_rec_info(record=record)
result.append([info[0], {}, info[2]])
return result
def log_change(self):
if self.log_changes():
record_log = self.find_record_log()
if self.item.item_state == consts.STATE_BROWSE:
if (self.item.record_status == consts.RECORD_UNCHANGED) or \
(self.item.record_status == consts.RECORD_DETAILS_MODIFIED and record_log['old_record'] is None):
record_log['old_record'] = self.copy_record(self.cur_record(), False)
return
elif self.item.item_state == consts.STATE_INSERT:
self.item.record_status = consts.RECORD_INSERTED
elif self.item.item_state == consts.STATE_EDIT:
if self.item.record_status == consts.RECORD_UNCHANGED:
self.item.record_status = consts.RECORD_MODIFIED
elif self.item.record_status == consts.RECORD_DETAILS_MODIFIED:
if self.record_modified(record_log):
self.item.record_status = consts.RECORD_MODIFIED
elif self.item.item_state == consts.STATE_DELETE:
if self.item.record_status == consts.RECORD_INSERTED:
self.remove_record_log()
else:
self.item.record_status = consts.RECORD_DELETED
else:
raise Exception('Item %s: change log invalid records state' % self.item.item_name)
if self.item.master:
if self.item.master.record_status == consts.RECORD_UNCHANGED:
self.item.master.record_status = consts.RECORD_DETAILS_MODIFIED
def get_changes(self, result):
data = {}
result['fields'] = self.fields
result['expanded'] = False
result['data'] = data
for key, record_log in iteritems(self.logs):
record = record_log['record']
info = self.item.get_rec_info(record=record)
if info[consts.REC_STATUS] != consts.RECORD_UNCHANGED:
old_record = record_log['old_record']
new_record = self.copy_record(record_log['record'], expanded=False)
new_details = {}
for detail_id, detail in iteritems(record_log['details']):
new_detail = {}
detail_item = self.item.item_by_ID(int(detail_id))
detail_item.change_log.logs = detail['logs']
detail_item.change_log.fields = detail['fields']
detail_item.change_log.expanded = detail['expanded']
detail_item.change_log.get_changes(new_detail)
new_details[detail_id] = new_detail
data[key] = {
'old_record': old_record,
'record': new_record,
'details': new_details
}
def set_changes(self, changes):
new_records = []
self.records = []
self.logs = {}
self.fields = changes['fields']
self.expanded = changes['expanded']
data = changes['data']
self._change_id = 0
for key, record_log in iteritems(data):
if self._change_id < int(key):
self._change_id = int(key)
record = record_log['record']
record[len(record) - 1].append(record_log['old_record'])
new_records.append([int(key), record])
details = {}
self.logs[key] = {
'old_record': None,
'record': record,
'details': details
}
for detail_id, detail in iteritems(record_log['details']):
detail_item = self.item.item_by_ID(int(detail_id))
detail_item.change_log.set_changes(detail)
details[detail_id] = {
'logs': detail_item.change_log.logs,
'records': detail_item.change_log.records,
'fields': detail_item.change_log.fields,
'expanded': detail_item.change_log.expanded
}
new_records.sort(key=lambda x: x[0])
self.records = [rec for key, rec in new_records]
def copy_records(self, records):
result = []
for rec in records:
result.append(list(rec))
return result
def store_details(self, source, dest):
for detail_item in self.item.details:
detail_id = str(detail_item.ID)
detail = source.get(detail_id)
logs = {}
records = []
fields = []
expanded = True
if detail:
cur_logs = detail['logs']
cur_records = detail['records']
fields = detail['fields']
expanded = detail['expanded']
records = self.copy_records(cur_records)
for key, record_log in iteritems(cur_logs):
cur_record = record_log['record']
record = detail_item.change_log.copy_record(cur_record)
index = None
try:
index = cur_records.index(cur_record)
except:
pass
if not index is None:
records[index] = record
details = {}
detail_item.change_log.store_details(record_log['details'], details)
logs[key] = {
'old_record': record_log['old_record'],
'record': record,
'details': details
}
else:
if detail_item._dataset:
records = self.copy_records(detail_item._dataset)
if records or logs:
dest[detail_id] = {'logs': logs, 'records': records, 'fields': fields, 'expanded': expanded}
def store_record_log(self):
if not self.log_changes():
result = {}
result['record'] = self.copy_record(self.cur_record())
details = {}
for detail in self.item.details:
if not detail.disabled and detail._dataset:
details[str(detail.ID)] = list(detail._dataset)
result['details'] = details
else:
record_log = self.find_record_log()
details = {}
self.store_details(record_log['details'], details)
result = {}
result['old_record'] = record_log['old_record']
result['record'] = self.copy_record(record_log['record'])
result['details'] = details
return result
def restore_record_log(self, log):
def restore_record():
record = log['record']
cur_record = self.cur_record()
info_index = self.item._record_info_index
for i, it in enumerate(cur_record):
if i < info_index:
cur_record[i] = record[i]
if not self.log_changes():
restore_record()
for detail in self.item.details:
detail._dataset = log['details'][str(detail.ID)]
else:
record_log = self.find_record_log()
restore_record()
record_log['old_record'] = log['old_record']
record_log['record'] = self.cur_record()
record_log['details'] = log['details']
for detail in self.item.details:
detail_log = log['details'].get(str(detail.ID))
if detail_log:
detail._dataset = detail_log['records']
if self.item.record_status == consts.RECORD_UNCHANGED:
self.remove_record_log()
def prepare(self):
if self.item.master:
log = self.item.master.change_log.get_detail_log(str(self.item.ID))
if log:
log['records'] = []
log['logs'] = {}
log['fields'] = []
log['fields'] = [field.field_name for field in self.item.fields if not field.master_field]
log['expanded'] = self.item.expanded
else:
self.records = []
self.logs = {}
self.fields = []
self.fields = [field.field_name for field in self.item.fields if not field.master_field]
self.expanded = self.item.expanded
def update(self, updates, master_rec_id=None):
if updates:
changes = updates['changes']
for change in changes:
log_id = change['log_id']
rec_id = change['rec_id']
details = change['details']
record_log = self.logs.get(log_id)
if record_log:
record = record_log['record']
record_details = record_log['details']
for detail in details:
ID = detail['ID']
detail_item = self.item.detail_by_ID(int(ID))
item_detail = record_details.get(str(ID))
if item_detail:
detail_item.change_log.logs = item_detail['logs']
detail_item.change_log.update(detail, rec_id)
if rec_id and self.item._primary_key:
if not record[self.item._primary_key_field.bind_index]:
record[self.item._primary_key_field.bind_index] = rec_id
if master_rec_id:
if not record[self.item._master_rec_id_field.bind_index]:
record[self.item._master_rec_id_field.bind_index] = master_rec_id
info = self.item.get_rec_info(record=record)
info[consts.REC_STATUS] = consts.RECORD_UNCHANGED
info[consts.REC_CHANGE_ID] = consts.RECORD_UNCHANGED
del self.logs[log_id]
class AbstractDataSet(object):
def __init__(self):
self.ID = 0
self.field_defs = []
self._fields = []
self.fields = []
self.filter_defs = []
self.filters = DBList()
self.details = DBList()
self.master = None
self.change_log = ChangeLog(self)
self._log_changes = True
self._dataset = None
self._primary_key = None
self._deleted_flag = None
self._master_id = None
self._master_rec_id = None
self._primary_key_db_field_name = None
self._deleted_flag_db_field_name = None
self._master_id_db_field_name = None
self._master_rec_id_db_field_name = None
self.__eof = False
self.__bof = False
self.__cur_row = None
self.__old_row = 0
self._old_status = None
self._buffer = None
self._modified = None
self._state = consts.STATE_INACTIVE
self._read_only = False
self._active = False
self._virtual_table = False
self._where_list = []
self._order_by_list = []
self._select_field_list = []
self.on_state_changed = None
self.on_filter_changed = None
self._record_lookup_index = -1
self._record_info_index = -1
self.expanded = True
self._open_params = {}
self._disabled_count = 0
self._is_delta = False
self._keep_history = False
self.edit_lock = False
self.select_all = False
self.on_field_get_text = None
def __getitem__(self, key):
if key == 0:
self.first()
else:
self.next()
if self.eof():
raise IndexError
return self
def add_field_def(self, field_id, field_name, field_caption, data_type, size=0, required=False,
lookup_item=None, lookup_field=None, visible=True, read_only=False, default=None, default_value=None,
master_field=None, alignment=None, lookup_values=None, enable_typeahead=False, field_help=None,
field_placeholder=None, lookup_field1=None, lookup_field2=None, db_field_name=None, field_mask=None,
image_edit_width=None, image_edit_height=None, image_view_width=None, image_view_height=None,
image_placeholder=None, image_camera=None, file_download_btn=None, file_open_btn=None, file_accept=None,
textarea=None, do_not_sanitize=None
):
if not db_field_name:
db_field_name = field_name.upper()
field_def = [None for i in range(len(FIELD_DEF))]
field_def[FIELD_ID] = field_id
field_def[FIELD_NAME] = field_name
field_def[FIELD_CAPTION] = field_caption
field_def[FIELD_DATA_TYPE] = data_type
field_def[REQUIRED] = required
field_def[LOOKUP_ITEM] = lookup_item
field_def[MASTER_FIELD] = master_field
field_def[LOOKUP_FIELD] = lookup_field
field_def[LOOKUP_FIELD1] = lookup_field1
field_def[LOOKUP_FIELD2] = lookup_field2
field_def[FIELD_READ_ONLY] = read_only
field_def[FIELD_VISIBLE] = visible
field_def[FIELD_SIZE] = size
field_def[FIELD_DEFAULT] = default
field_def[FIELD_DEFAULT_VALUE] = default_value
field_def[FIELD_ALIGNMENT] = alignment
field_def[FIELD_LOOKUP_VALUES] = lookup_values
field_def[FIELD_ENABLE_TYPEAHEAD] = enable_typeahead
field_def[FIELD_HELP] = field_help
field_def[FIELD_PLACEHOLDER] = field_placeholder
field_def[FIELD_INTERFACE] = {'field_mask': field_mask,
'textarea': textarea, 'do_not_sanitize': do_not_sanitize}
if data_type == consts.IMAGE:
field_def[FIELD_IMAGE] = {'edit_width': image_edit_width, 'edit_height': image_edit_height,
'view_width': image_view_width, 'view_height': image_view_height, 'placeholder': image_placeholder,
'camera': image_camera}
if data_type == consts.FILE:
field_def[FIELD_FILE] = {'download_btn': file_download_btn, 'open_btn': file_open_btn, 'accept': file_accept}
field_def[DB_FIELD_NAME] = db_field_name
self.field_defs.append(field_def)
return field_def
def add_filter_def(self, filter_name, filter_caption, field_name, filter_type,
multi_select_all, data_type, visible, filter_help, filter_placeholder, filter_ID):
filter_def = [None for i in range(len(FILTER_DEF))]
filter_def[FILTER_OBJ_NAME] = filter_name
filter_def[FILTER_NAME] = filter_caption
filter_def[FILTER_FIELD_NAME] = field_name
filter_def[FILTER_TYPE] = filter_type
filter_def[FILTER_MULTI_SELECT] = multi_select_all
filter_def[FILTER_DATA_TYPE] = data_type
filter_def[FILTER_VISIBLE] = visible
filter_def[FILTER_HELP] = filter_help
filter_def[FILTER_PLACEHOLDER] = filter_placeholder
filter_def[FILTER_ID] = filter_ID
self.filter_defs.append(filter_def)
return filter_def
@property
def virtual_table(self):
return self._virtual_table
@property
def dataset(self):
result = []
if self.active:
for r in self._dataset:
result.append(r[0:self._record_info_index])
return result
@dataset.setter
def dataset(self, value):
self._dataset = value
@property
def keep_history(self):
if self.master:
return self.prototype._keep_history
else:
return self._keep_history
def _copy(self, filters=True, details=True, handlers=True):
result = self.__class__(self.task, None, self.item_name, self.item_caption, self.visible)
result.ID = self.ID
result.item_name = self.item_name
result.expanded = self.expanded
result.field_defs = self.field_defs
result.filter_defs = self.filter_defs
result._virtual_table = self._virtual_table
result._keep_history = self._keep_history
result.select_all = self.select_all
for field_def in result.field_defs:
field = DBField(result, field_def)
result._fields.append(field)
result.prepare_fields()
for filter_def in result.filter_defs:
fltr = DBFilter(result, filter_def)
result.filters.append(fltr)
result.prepare_filters()
result._events = self._events
if handlers:
for func_name, func in result._events:
setattr(result, func_name, func)
return result
def clone(self):
result = self.__class__(self.task, None, self.item_name, self.item_caption, self.visible)
result.ID = self.ID
result.item_name = self.item_name
result.field_defs = self.field_defs
result.filter_defs = self.filter_defs
for field_def in result.field_defs:
field = DBField(result, field_def)
result._fields.append(field)
result.prepare_fields()
for field in result.fields:
if hasattr(result, field.field_name):
delattr(result, field.field_name)
result.fields = [];
for field in self.fields:
new_field = result._field_by_name(field.field_name)
result.fields.append(new_field)
if not hasattr(result, new_field.field_name):
setattr(result, new_field.field_name, new_field)
result._bind_fields()
result._dataset = self._dataset
result._active = True
result.first()
return result
def prepare_fields(self):
for field in self._fields:
if field.lookup_item and type(field.lookup_item) == int:
field.lookup_item = self.task.item_by_ID(field.lookup_item)
if field.master_field and type(field.master_field) == int:
field.master_field = self._field_by_ID(field.master_field)
if field.lookup_field and type(field.lookup_field) == int:
lookup_field = field.lookup_item._field_by_ID(field.lookup_field)
field.lookup_field = lookup_field.field_name
field.lookup_db_field = lookup_field.db_field_name
if lookup_field.lookup_item and field.lookup_field1:
field.lookup_item1 = lookup_field.lookup_item
if type(field.lookup_item1) == int:
field.lookup_item1 = self.task.item_by_ID(field.lookup_item1)
if type(field.lookup_field1) == int:
lookup_field1 = field.lookup_item1._field_by_ID(field.lookup_field1)
field.lookup_field1 = lookup_field1.field_name
field.lookup_db_field1 = lookup_field1.db_field_name
if lookup_field1.lookup_item and field.lookup_field2:
field.lookup_item2 = lookup_field1.lookup_item
if type(field.lookup_item2) == int:
field.lookup_item2 = self.task.item_by_ID(field.lookup_item2)
if type(field.lookup_field2) == int:
lookup_field2 = field.lookup_item2._field_by_ID(field.lookup_field2)
field.lookup_field2 = lookup_field2.field_name
field.lookup_db_field2 = lookup_field2.db_field_name
if field.lookup_values and type(field.lookup_values) == int:
try:
field.lookup_values = self.task.lookup_lists[field.lookup_values]
except:
pass
self.fields = list(self._fields)
for field in self.fields:
if not hasattr(self, field.field_name):
setattr(self, field.field_name, field)
for sys_field_name in ['_primary_key', '_deleted_flag', '_master_id', '_master_rec_id']:
sys_field = getattr(self, sys_field_name)
if sys_field and type(sys_field) == int:
field = self.field_by_ID(sys_field)
if field:
setattr(self, sys_field_name, field.field_name)
setattr(self, '%s_%s' % (sys_field_name, 'db_field_name'), field.db_field_name)
def prepare_filters(self):
for fltr in self.filters:
setattr(self.filters, fltr.filter_name, fltr)
if fltr.field.lookup_item and type(fltr.field.lookup_item) == int:
fltr.field.lookup_item = self.task.item_by_ID(fltr.field.lookup_item)
def field_by_name(self, field_name):
for field in self.fields:
if field.field_name == field_name:
return field
def _field_by_name(self, field_name):
for field in self._fields:
if field.field_name == field_name:
return field
def field_by_ID(self, id_value):
for field in self.fields:
if field.ID == id_value:
return field
def _field_by_ID(self, id_value):
for field in self._fields:
if field.ID == id_value:
return field
def filter_by_name(self, name):
for fltr in self.filters:
if fltr.filter_name == name:
return fltr
@property
def log_changes(self):
return self._log_changes
@log_changes.setter
def log_changes(self, value):
self._log_changes = value
def _set_modified(self, value):
self._modified = value
def is_modified(self):
return self._modified
@property
def active(self):
return self._active
@property
def item_state(self):
return self._state
@item_state.setter
def item_state(self, value):
if self._state != value:
self._state = value
if self.on_state_changed:
self.on_state_changed(self)
def _do_after_scroll(self):
pass
def _do_before_scroll(self):
pass
def skip(self, value, trigger_events=True):
if self.record_count() == 0:
self.__cur_row = None
if trigger_events:
self._do_before_scroll()
self.__eof = True
self.__bof = True
if trigger_events:
self._do_after_scroll()
else:
old_row = self.__cur_row
eof = False
bof = False
new_row = value
if new_row < 0:
new_row = 0
bof = True
if new_row >= len(self._dataset):
new_row = len(self._dataset) - 1
eof = True
self.__eof = eof
self.__bof = bof
if old_row != new_row:
if trigger_events:
self._do_before_scroll()
self.__cur_row = new_row
if trigger_events:
self._do_after_scroll()
self.__old_row = self.__cur_row
@property
def rec_no(self):
if self._active:
return self.__cur_row
@rec_no.setter
def rec_no(self, value):
self.skip(value)
def first(self):
self.rec_no = 0
def last(self):
self.rec_no = len(self._dataset) - 1
def next(self):
self.rec_no += 1
def prior(self):
self.rec_no -= 1
def eof(self):
if self.active:
return self.__eof
else:
return True
def bof(self):
if self.active:
return self.__bof
else:
return True
@property
def rec_count(self):
if self._dataset:
return len(self._dataset)
else:
return 0
def record_count(self):
return self.rec_count
def do_internal_open(self, params, connection):
pass
def find_rec_info(self, rec_no=None, record=None):
if record is None:
if rec_no is None:
rec_no = self.rec_no
if self.record_count() > 0:
record = self._dataset[rec_no];
if record and self._record_info_index > 0:
if len(record) < self._record_info_index + 1:
record.append([None, {}, None])
return record[self._record_info_index]
def get_rec_info(self, rec_no=None, record=None):
return self.find_rec_info(rec_no, record)
def get_records_status(self):
info = self.get_rec_info()
if info:
return info[consts.REC_STATUS]
def _set_record_status(self, value):
info = self.get_rec_info()
if info and (self.log_changes or self._is_delta):
info[consts.REC_STATUS] = value
record_status = property (get_records_status, _set_record_status)
def _get_rec_change_id(self):
info = self.get_rec_info()
if info:
return info[consts.REC_CHANGE_ID]
def _set_rec_change_id(self, value):
info = self.get_rec_info()
if info:
info[consts.REC_CHANGE_ID] = value
rec_change_id = property (_get_rec_change_id, _set_rec_change_id)
def rec_controls_info(self):
info = self.get_rec_info()
if info:
return info[consts.REC_CONTROLS_INFO]
def _bind_fields(self, expanded=True):
for field in self.fields:
field.bind_index = None
field.lookup_index = None
j = 0
for field in self.fields:
if not field.master_field:
field.bind_index = j
j += 1
for field in self.fields:
if field.master_field:
field.bind_index = field.master_field.bind_index
self._record_lookup_index = j
if expanded:
for field in self.fields:
if field.lookup_item:
field.lookup_index = j
j += 1
self._record_info_index = j
def get_where_list(self, field_dict):
result = []
for field_arg in iterkeys(field_dict):
field_name = field_arg
pos = field_name.find('__')
if pos > -1:
filter_str = field_name[pos+2:]
field_name = field_name[:pos]
else:
filter_str = 'eq'
filter_type = consts.FILTER_STR.index(filter_str)
if filter_type != -1:
filter_type += 1
else:
raise RuntimeError('%s: set_where method arument error %s' % (self.item_name, field_arg))
field = self._field_by_name(field_name)
if not field:
raise RuntimeError('%s: set_where method arument error %s: ' % (self.item_name, field_arg))
value = field_dict[field_arg]
if not value is None:
result.append([field_name, filter_type, value])
return result
def set_fields(self, lst=None, *fields):
field_list = []
if lst:
if type(lst) in (list, tuple):
field_list = list(lst)
else:
field_list.append(lst)
field_list = field_list + list(fields)
self._select_field_list = field_list
def set_where(self, dic=None, **fields):
field_dict = {}
if dic:
field_dict = dic
if fields:
for key, value in iteritems(fields):
field_dict[key] = value
self._where_list = self.get_where_list(field_dict)
def get_order_by_list(self, field_list):
result = []
for field in field_list:
field_name = field
desc = False
if field[0] == '-':
desc = True
field_name = field[1:]
try:
fld = self._field_by_name(field_name)
except:
raise RuntimeError('%s: order_by param error - %s' % (self.item_name, field))
result.append([fld.field_name, desc])
return result
def set_order_by(self, lst=None, *fields):
field_list = []
if lst:
if type(lst) in (list, tuple):
field_list = list(lst)
else:
field_list.append(lst)
field_list = field_list + list(fields)
self._order_by_list = self.get_order_by_list(field_list)
def _update_fields(self, fields):
for field in self.fields:
if hasattr(self, field.field_name):
delattr(self, field.field_name)
if fields:
self.fields = []
for field_name in fields:
field = self._field_by_name(field_name)
if field:
self.fields.append(field)
else:
raise Exception('%s - _do_before_open method error: there is no field with field_name: %s' % (self.item_name, field_name))
else:
self.fields = list(self._fields)
for field in self.fields:
if not hasattr(self, field.field_name):
setattr(self, field.field_name, field)
for sys_field_name in ['_primary_key', '_deleted_flag', '_master_id', '_master_rec_id']:
sys_field = getattr(self, sys_field_name)
if sys_field:
field = self.field_by_name(sys_field)
if field:
setattr(self, sys_field_name + '_field', field)
def _do_before_open(self, expanded, fields, where, order_by, open_empty,
params, offset, limit, funcs, group_by):
result = None
params['__expanded'] = expanded
params['__fields'] = []
params['__filters'] = []
filters = []
if fields is None and self._select_field_list:
fields = self._select_field_list
self._update_fields(fields)
if fields:
params['__fields'] = fields
if not open_empty:
params['__limit'] = 0
params['__offset'] = 0
if limit:
params['__limit'] = limit
if offset:
params['__offset'] = offset
if not where is None:
filters = self.get_where_list(where)
elif self._where_list:
filters = list(self._where_list)
else:
if self.filters:
for fltr in self.filters:
if not fltr.value is None:
filters.append([fltr.field.field_name, fltr.filter_type, fltr.value])
if params.get('__search'):
field_name, text = params['__search']
filters.append([field_name, consts.FILTER_CONTAINS_ALL, text])
params['__filters'] = filters
if not order_by is None:
params['__order'] = self.get_order_by_list(order_by)
elif self._order_by_list:
params['__order'] = list(self._order_by_list)
if funcs:
params['__funcs'] = funcs
if group_by:
params['__group_by'] = group_by
self._order_by_list = []
self._where_list = []
self._open_params = params
def open(self, expanded, fields, where, order_by, open_empty, params,
offset, limit, funcs, group_by, connection):
if not params:
params = {}
self._do_before_open(expanded, fields, where, order_by, open_empty,
params, offset, limit, funcs, group_by)
self.change_log.prepare()
self._bind_fields(expanded)
self._dataset = []
if not open_empty:
self.do_open(params, connection)
self._active = True
# ~ self.__cur_row = None
self.item_state = consts.STATE_BROWSE
self.first()
def do_open(self, params=None, connection=None):
if not params:
params = self._open_params
rows, error_mes, info = self.do_internal_open(params, connection)
if error_mes:
raise RuntimeError(error_mes)
else:
self._dataset = rows
def close(self):
self._active = False
self._dataset = None
self.skip(0)
self.close_details()
def close_details(self):
for detail in self.details:
detail.close()
def new_record(self):
result = [None for field in self.fields if not field.master_field]
if self.expanded:
result += [None for field in self.fields if field.lookup_item]
return result
def append(self, index=None):
if not self.active:
raise DatasetException(consts.language('append_not_active') % self.item_name)
if self.item_state != consts.STATE_BROWSE:
raise DatasetInvalidState(consts.language('append_not_browse') % self.item_name)
self._do_before_scroll()
self.item_state = consts.STATE_INSERT
if index == 0:
self._dataset.insert(0, self.new_record())
self.skip(0, trigger_events=False)
else:
self._dataset.append(self.new_record())
self.skip(len(self._dataset) - 1)
self.record_status = consts.RECORD_INSERTED
for field in self.fields:
field.assign_default_value()
self._modified = False
self._do_after_scroll()
def insert(self):
self.append(0)
def edit(self):
if not self.active:
raise DatasetException(consts.language('edit_not_active') % self.item_name)
if self.item_state == consts.STATE_EDIT:
return
if self.item_state != consts.STATE_BROWSE:
raise DatasetInvalidState(consts.language('edit_not_browse') % self.item_name)
if self.record_count() == 0:
raise DatasetEmpty(consts.language('edit_no_records') % self.item_name)
self.change_log.log_change()
self._buffer = self.change_log.store_record_log()
self.item_state = consts.STATE_EDIT
self._old_status = self.record_status
self._modified = False
def delete(self):
if not self.active:
raise DatasetException(consts.language('delete_not_active') % self.item_name)
if self.record_count() == 0:
raise DatasetEmpty(consts.language('delete_no_records') % self.item_name)
self._do_before_scroll()
self.item_state = consts.STATE_DELETE
self.change_log.log_change()
if self.master:
self.master._set_modified(True)
self._dataset.remove(self._dataset[self.rec_no])
self.skip(self.rec_no, trigger_events=False)
self._do_after_scroll()
self.item_state = consts.STATE_BROWSE
def cancel(self):
if self.item_state == consts.STATE_EDIT:
self.change_log.restore_record_log(self._buffer)
elif self.item_state == consts.STATE_INSERT:
self._do_before_scroll()
self.change_log.remove_record_log()
del self._dataset[self.rec_no]
else:
raise Exception(consts.language('cancel_invalid_state') % self.item_name)
prev_state = self.item_state
self.skip(self.__old_row, trigger_events=False)
self.item_state = consts.STATE_BROWSE
self._set_modified(False)
if prev_state == consts.STATE_EDIT:
self.record_status = self._old_status
elif prev_state == consts.STATE_INSERT:
self._do_after_scroll()
def post(self):
if not self.is_changing():
raise DatasetInvalidState(consts.language('not_edit_insert_state') % self.item_name)
self.check_record_valid()
if self.master and self._master_id:
self.field_by_name(self._master_id).value = self.master.ID
for detail in self.details:
if detail.is_changing():
detail.post()
if self.is_modified() or self.is_new():
self.change_log.log_change()
elif self.record_status == consts.RECORD_UNCHANGED:
self.change_log.remove_record_log()
self._modified = False
self.item_state = consts.STATE_BROWSE
def rec_inserted(self):
return self.record_status == consts.RECORD_INSERTED
def rec_deleted(self):
return self.record_status == consts.RECORD_DELETED
def rec_modified(self):
return self.record_status in (consts.RECORD_MODIFIED, consts.RECORD_DETAILS_MODIFIED)
def is_browsing(self):
return self.item_state == consts.STATE_BROWSE
def is_changing(self):
return (self.item_state == consts.STATE_INSERT) or (self.item_state == consts.STATE_EDIT)
def is_new(self):
return self.item_state == consts.STATE_INSERT
def is_edited(self):
return self.item_state == consts.STATE_EDIT
def is_deleting(self):
return self.item_state == consts.STATE_DELETE
def check_record_valid(self):
for field in self.fields:
field.check_valid()
return True
def locate(self, fields, values):
clone = self.clone()
def record_found():
if isinstance(fields, (list, tuple)):
for i, field in enumerate(fields):
if clone.field_by_name(field).value != values[i]:
return False
return True
else:
if clone.field_by_name(fields).value == values:
return True
for c in clone:
if record_found():
self.rec_no = clone.rec_no
return True
def filter_index(self, filter_name):
filter = self.filter_by_name(filter_name)
if filter:
return self.filters.index(filter)
def get_field_values(self):
result = []
for field in self.fields:
result.append(field.value)
return result
def set_field_values(self, values):
for i, field in enumerate(self.fields):
field.value = values[i]
def compare_field_values(self, values):
result = True
for i, field in enumerate(self.fields):
if self.get_field_values() != values:
result = False
break
return result
def clear_filters(self):
for filter in self.filters:
filter.value = None
def get_filter_values(self):
result = []
for filter in self.filters:
result.append(filter.value)
return result
def set_filter_values(self, values):
for i, filter in enumerate(self.filters):
filter.value = values[i]
def search(self, field_name, text):
searchText = text.strip()
params = {};
if len(searchText):
params['__search'] = [field_name, searchText]
self.open(params=params)
else:
self.open()
def round(self, value, dec):
return consts.round(value, dec)
class MasterDataSet(AbstractDataSet):
def __init__(self):
AbstractDataSet.__init__(self)
self.details_active = False
def _copy(self, filters=True, details=True, handlers=True):
result = super(MasterDataSet, self)._copy(filters, details, handlers)
if details:
for detail in self.details:
copy_table = detail._copy(filters, details, handlers)
copy_table.master = result
copy_table.expanded = detail.expanded
result.details.append(copy_table)
result.items.append(copy_table)
if not hasattr(result, copy_table.item_name):
setattr(result, copy_table.item_name, copy_table)
if not hasattr(result.details, copy_table.item_name):
setattr(result.details, copy_table.item_name, copy_table)
copy_table.owner = result
copy_table.prototype = detail.prototype
return result
def do_apply(self, params, safe):
pass
def apply(self, connection=None, params=None, safe=False):
result = None
if self.master or self.virtual_table:
return
if self.is_changing():
self.post()
self.do_apply(params, safe, connection)
def detail_by_ID(self, ID):
ID = int(ID)
for detail in self.details:
if detail.ID == ID:
return detail
def delta(self, changes=None):
if not changes:
changes = {}
self.change_log.get_changes(changes)
result = self.copy(filters=False, details=True, handlers=False)
result.log_changes = False
result.expanded = False
result._is_delta = True
for detail in result.details:
detail.log_changes = False
detail.expanded = False
detail._is_delta = True
result.details_active = True
result.change_log.set_changes(changes)
result._dataset = result.change_log.records
result._update_fields(result.change_log.fields)
result._bind_fields(result.change_log.expanded)
result.item_state = consts.STATE_BROWSE
result._active = True
result.first()
return result
def detail_by_name(self, name):
for detail in self.details:
if detail.item_name == name:
return detail
def open_details(self):
for detail in self.details:
if not detail.disabled:
detail.open()
def _do_after_scroll(self):
if self.details_active:
self.open_details()
else:
self.close_details()
super(MasterDataSet, self)._do_after_scroll()
def _set_read_only(self, value):
super(MasterDataSet, self)._set_read_only(value)
for detail in self.details:
detail._set_read_only(value)
class MasterDetailDataset(MasterDataSet):
def __init__(self):
MasterDataSet.__init__(self)
self.disabled = False
def find_change_log(self):
if self.master:
if self.master.record_status != consts.RECORD_UNCHANGED:
return self.master.change_log.get_detail_log(str(self.ID))
def open(self, options=None, expanded=None, fields=None, where=None, order_by=None,
open_empty=False, params=None, offset=None, limit=None, funcs=None,
group_by=None, safe=False, connection=None):
if safe and not self.can_view():
raise Exception(consts.language('cant_view') % self.item_caption)
if options and type(options) == dict:
if options.get('expanded'):
expanded = options['expanded']
if options.get('fields'):
fields = options['fields']
if options.get('where'):
where = options['where']
if options.get('order_by'):
order_by = options['order_by']
if options.get('open_empty'):
open_empty = False
if options.get('params'):
params = options['params']
if options.get('offset'):
offset = options['offset']
if options.get('limit'):
limit = options['limit']
if options.get('funcs'):
funcs = options['funcs']
if expanded is None:
expanded = self.expanded
else:
self.expanded = expanded
if self.virtual_table:
open_empty = True
group_by
if not params:
params = {}
if self.master:
if not self.disabled and self.master.record_count() > 0:
records = None
if self.master.is_new():
records = []
else:
log = self.find_change_log()
if log:
records = log['records']
fields = log['fields']
expanded = log['expanded']
if not records is None:
self._do_before_open(expanded, fields, where, order_by,
open_empty, params, offset, limit, funcs, group_by)
self._bind_fields(expanded)
if self.master.is_new():
self.change_log.prepare()
self._dataset = records
self._active = True
self.item_state = consts.STATE_BROWSE
self.first()
else:
params['__master_id'] = self.master.ID
params['__master_rec_id'] = self.master.field_by_name(self.master._primary_key).value
return super(MasterDetailDataset, self).open(expanded,
fields, where, order_by, open_empty, params, offset,
limit, funcs, group_by, connection)
else:
return
else:
return super(MasterDetailDataset, self).open(expanded,
fields, where, order_by, open_empty, params, offset, limit,
funcs, group_by, connection)
def open__in(self, ids, expanded=None, fields=None, where=None, order_by=None, open_empty=False, params=None, offset=None, limit=None): #depricated
def slice_ids(ids):
MAX_IN_LIST = 999
result = []
while True:
result.append(ids[0:MAX_IN_LIST])
ids = ids[MAX_IN_LIST:]
if len(ids) == 0:
break;
return result
params = {}
for args, value in iteritems(locals()):
if not args in ['self', 'ids', 'params', 'slice_ids']:
params[args] = value
if type(ids) == dict:
keys = list(iterkeys(ids))
id_field_name = keys[0]
ids = ids[id_field_name]
elif type(ids) == list:
id_field_name = 'id'
else:
raise Exception('Item %s: invalid ids parameter in open__in method' % self.item_name)
if params['where'] is None:
params['where'] = {}
records = []
lst = slice_ids(ids)
for l in lst:
params['where'][id_field_name + '__in'] = l
self.open(**params)
records += self._dataset
self._dataset = records
self.first()
def insert(self):
if self.master and not self.master.is_changing():
raise DatasetException(consts.language('insert_master_not_changing') % self.item_name)
super(MasterDetailDataset, self).insert()
def append(self):
if self.master and not self.master.is_changing():
raise DatasetException(consts.language('append_master_not_changing') % self.item_name)
super(MasterDetailDataset, self).append()
def edit(self):
if self.master and not self.master.is_changing():
raise DatasetException(consts.language('edit_master_not_changing') % self.item_name)
super(MasterDetailDataset, self).edit()
def delete(self):
if self.master and not self.master.is_changing():
raise DatasetException(consts.language('delete_master_not_changing') % self.item_name)
super(MasterDetailDataset, self).delete()
def _set_modified(self, value):
self._modified = value
if self.master and value:
self.master._set_modified(value)
def is_modified(self):
return super(MasterDetailDataset, self).is_modified()
def _get_read_only(self):
if self.master:
return self.master.read_only
else:
return super(MasterDetailDataset, self)._get_read_only()
def _set_read_only(self, value):
return super(MasterDetailDataset, self)._set_read_only(value)
read_only = property (_get_read_only, _set_read_only)
class Dataset(MasterDetailDataset):
pass
class Param(DBField):
def __init__(self, owner, param_def):
DBField.__init__(self, owner, param_def)
self.field_kind = consts.PARAM_FIELD
if self.data_type == consts.TEXT:
self.field_size = 1000
else:
self.field_size = 0
self.param_name = self.field_name
self.param_caption = self.field_caption
self._value = None
self._lookup_value = None
setattr(owner, self.param_name, self)
def system_field(self):
return False
@property
def data(self):
return self._value
@data.setter
def data(self, value):
self._value = value
@property
def lookup_data(self):
return self._lookup_value
@lookup_data.setter
def lookup_data(self, value):
self._lookup_value = value
def _do_before_changed(self):
pass
def _change_lookup_field(self, lookup_value=None, slave_field_values=None):
pass
def copy(self, owner):
result = Param(owner, self.param_caption, self.field_name, self.data_type,
self.lookup_item, self.lookup_field, self.required,
self.edit_visible, self.alignment)
return result
class ParamReport(object):
def __init__(self):
self.param_defs = []
self.params = []
def add_param(self, *args):
param_def = self.add_param_def(*args)
param = Param(self, param_def)
self.params.append(param)
def create_params(self, info):
for param_def in info:
self.add_param(*param_def)
def add_param_def(self, param_caption, param_name, data_type,
lookup_item, lookup_field, required, visible,
alignment, multi_select, multi_select_all, enable_typeahead,
lookup_values, param_help, param_placeholder):
param_def = [None for i in range(len(FIELD_DEF))]
param_def[FIELD_NAME] = param_name
param_def[FIELD_CAPTION] = param_caption
param_def[FIELD_DATA_TYPE] = data_type
param_def[REQUIRED] = required
param_def[LOOKUP_ITEM] = lookup_item
param_def[LOOKUP_FIELD] = lookup_field
param_def[FIELD_VISIBLE] = visible
param_def[FIELD_ALIGNMENT] = alignment
param_def[FIELD_MULTI_SELECT] = multi_select
param_def[FIELD_MULTI_SELECT_ALL] = multi_select_all
param_def[FIELD_ENABLE_TYPEAHEAD] = enable_typeahead
param_def[FIELD_LOOKUP_VALUES] = lookup_values
param_def[FIELD_HELP] = param_help
param_def[FIELD_PLACEHOLDER] = param_placeholder
self.param_defs.append(param_def)
return param_def
def prepare_params(self):
for param in self.params:
if param.lookup_item and type(param.lookup_item) == int:
param.lookup_item = self.task.item_by_ID(param.lookup_item)
if param.lookup_field and type(param.lookup_field) == int:
param.lookup_field = param.lookup_item._field_by_ID(param.lookup_field).field_name
if param.lookup_values and type(param.lookup_values) == int:
try:
param.lookup_values = self.task.lookup_lists[param.lookup_values]
except:
pass