22 |
# GDataObjects.py |
# GDataObjects.py |
23 |
# |
# |
24 |
# DESCRIPTION: |
# DESCRIPTION: |
25 |
# Class |
# This is a temporary backwards compatability file. |
26 |
|
# Should be removed at some point. |
27 |
# |
# |
28 |
# NOTES: |
# NOTES: |
29 |
# |
# |
30 |
# HISTORY: |
# TODO: Generate warnings whenever anything in this file is accessed. |
31 |
# |
# |
32 |
|
|
33 |
from gnue.common.apps import GDebug |
from Exceptions import * |
34 |
from gnue.common.datasources import GConditions |
from drivers.Base import * |
|
import string |
|
|
|
|
|
postingRecordset = None |
|
|
|
|
|
class Error(StandardError): |
|
|
# Base exception |
|
|
pass |
|
|
|
|
|
class LoginError(Error): |
|
|
# Raised when invalid login user/pass was provided |
|
|
# Client should attempt to get better information and |
|
|
# try again |
|
|
pass |
|
|
|
|
|
class ConnectError(Error): |
|
|
# Raised when connection data is invalid (e.g., host not found, etc). |
|
|
# Client should probably not attempt to relogin. Exit gracefully |
|
|
# with a reason. |
|
|
pass |
|
|
|
|
|
class ProviderNotSupportedError(Error): |
|
|
# Raised when a datasource type is requested that the dbdriver |
|
|
# does not support (e.g., not all dbdrivers support raw sql mode.) |
|
|
pass |
|
|
|
|
|
class ObjectTypeNotAvailableError(Error): |
|
|
# Raised when a datasource type is requested that the dbdriver |
|
|
# does not support (e.g., not all dbdrivers support raw sql mode.) |
|
|
pass |
|
|
|
|
|
class ReadOnlyError(Error): |
|
|
# Raised when an attempt is made to update a read-only data object. |
|
|
pass |
|
|
|
|
|
class MasterDetailFieldMismatch(Error): |
|
|
# Raised when a the number of master fields doesn't match the |
|
|
# number of detail fields. (e.g., masterlink="id,subid" |
|
|
# and detaillink="id" would be a problem; must be 1:1) |
|
|
pass |
|
|
|
|
|
class ConnectionError(Error): |
|
|
# Generic error reading from the database connection |
|
|
pass |
|
|
|
|
|
class DataTypeNotAvailable(Error): |
|
|
# Raised when a datatype is not supported by an database |
|
|
# during writing a schema definition to the database |
|
|
pass |
|
|
|
|
|
class NoWriteSchemaSupport(Error): |
|
|
# Raised when a database adapter doesn't support |
|
|
# writing Schema to datasource |
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
########################################################### |
|
|
# |
|
|
# |
|
|
# |
|
|
########################################################### |
|
|
class DataObject: |
|
|
|
|
|
def __init__(self, connection): |
|
|
self._connection = connection |
|
|
|
|
|
self.masterlink = "" |
|
|
self.detaillink = "" |
|
|
|
|
|
self._masterfields = [] |
|
|
self._detailfields = [] |
|
|
self._staticCondition = None |
|
|
|
|
|
self._masterObject = None |
|
|
self._detailObjects = [] |
|
|
self._dataConnection = None |
|
|
self._resultSetClass = ResultSet |
|
|
self._fieldReferences = {} # Set by GDataSource; lists all fields |
|
|
# a client explicitly references |
|
|
|
|
|
self._unboundFieldReferences = {} # Contains names of all unbound |
|
|
# field references |
|
|
|
|
|
self._defaultValues = {} |
|
|
|
|
|
self._unicodeMode = 0 # if set to true, datasources will unicode |
|
|
# strings instead of strings in local encoding |
|
|
# and if non unicode strings are passed to the |
|
|
# db driver a warning is raised (in 0.6.0 an |
|
|
# exception will be raised) |
|
|
|
|
|
# TODO: This is to keep old code from breaking. |
|
|
# TODO: 0.5.1 was last version to support |
|
|
# TODO triggerExtensions (functionality is now |
|
|
# TODO: encapsulated in Connection objects. |
|
|
self.triggerExtensions = connection |
|
|
|
|
|
# Do we have a master datasource? |
|
|
def hasMaster(self): |
|
|
return self._masterObject != None |
|
|
|
|
|
# Do not over-ride by vendor code |
|
|
def createResultSet(self, conditions={}, readOnly=0, masterRecordSet=None, sql=""): |
|
|
return self._createResultSet( |
|
|
GConditions.combineConditions(conditions, self._staticCondition), |
|
|
readOnly=readOnly, masterRecordSet=masterRecordSet, sql=sql) |
|
|
|
|
|
# Designed to be replaced by vendor-specific code |
|
|
def _createResultSet(self, conditions={}, readOnly=0, masterRecordSet=None, \ |
|
|
sql=""): |
|
|
pass |
|
|
|
|
|
# Do not over-ride by vendor code |
|
|
def getQueryString(self,conditions={},forDetailSQL=None,additionalSQL=""): |
|
|
return self._buildQuery(conditions,forDetailSQL,additionalSQL) |
|
|
|
|
|
def createEmptyResultSet(self, readOnly=0, masterRecordSet=None): |
|
|
return self._createEmptyResultSet(readOnly, masterRecordSet) |
|
|
|
|
|
# Designed to be replaced by vendor-specific code |
|
|
def _createEmptyResultSet(self, readOnly=0, masterRecordSet=None): |
|
|
cond = GConditions.GCondition() |
|
|
ceq = GConditions.GCeq(cond) |
|
|
GConditions.GCConst(ceq,1,"number") |
|
|
GConditions.GCConst(ceq,0,"number") |
|
|
return self.createResultSet(conditions=cond, readOnly=readOnly, |
|
|
masterRecordSet=masterRecordSet) |
|
|
|
|
|
|
|
|
# Add a detail data object. This dataobject will create a new resultset |
|
|
# everytime this dataobject changes (new record, etc). The optional |
|
|
# handler will be called after the detail dataobject is notified. The |
|
|
# client application may wish to add a handler to know when the detail |
|
|
# has been requeried. handler is a method that takes two arguments: |
|
|
# the master ResultSet and the detail ResultSet |
|
|
def addDetailDataObject(self, dataObject, handler=None, **params): |
|
|
|
|
|
dataObject.__dict__.update(params) |
|
|
|
|
|
GDebug.printMesg (1,"Adding a master/detail relationship to DataObject") |
|
|
dataObject._masterObject = self |
|
|
dataObject._masterfields = string.split(hasattr(dataObject,'masterlink') and \ |
|
|
string.lower(dataObject.masterlink) or "", ',') |
|
|
dataObject._detailfields = string.split(hasattr(dataObject,'detaillink') and \ |
|
|
string.lower(dataObject.detaillink) or "", ',') |
|
|
|
|
|
if len(dataObject._masterfields) != len(dataObject._detailfields): |
|
|
tmsg = _("master=%s; detail=%s") % (dataObject._masterfields, dataObject._detailfields) |
|
|
raise MasterDetailFieldMismatch, tmsg |
|
|
|
|
|
# Make sure "master" fields will be in our future query |
|
|
for field in dataObject._masterfields: |
|
|
self._fieldReferences[string.strip(field)] = "" |
|
|
|
|
|
for field in dataObject._detailfields: |
|
|
dataObject._fieldReferences[string.strip(field)] = "" |
|
|
|
|
|
self._detailObjects.append ([dataObject, handler]) |
|
|
|
|
|
|
|
|
# |
|
|
# Connect to database from a GConnection object |
|
|
# |
|
|
def connect(self): |
|
|
self._dataConnection = self._connection.native |
|
|
self._postConnect() |
|
|
|
|
|
def commit(self): |
|
|
pass |
|
|
|
|
|
def rollback(self): |
|
|
pass |
|
|
|
|
|
|
|
|
# |
|
|
# Schema (metadata) functions |
|
|
# |
|
|
|
|
|
# TODO: DEPRECATED!!!!! |
|
|
def getSchemaTypes(self): |
|
|
print "WARNING: Your app is calling GDataObject.getSchemaTypes()" |
|
|
return self._connection.schema.types[:] |
|
|
|
|
|
# TODO: DEPRECATED!!!!! |
|
|
def getSchemaList(self, type=None): |
|
|
print "WARNING: Your app is calling GDataObject.getSchemaList()" |
|
|
return self._connection.schema.find(type=type) |
|
|
|
|
|
# TODO: DEPRECATED!!!!! |
|
|
def getSchemaByName(self, name, type=None): |
|
|
print "WARNING: Your app is calling GDataObject.getSchemaByName()" |
|
|
return self._connection.schema.findfirst(name=name, type=type) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Called when new record master in master/detail is queried |
|
|
def _masterRecordChanged(self, master): |
|
|
GDebug.printMesg (5, 'Master Record Changed') |
|
|
criteria = {} |
|
|
|
|
|
# If a detail result set has already been created for a particular |
|
|
# master record set, then just return/reuse this old set (after all, |
|
|
# it may contain uncommitted changes) |
|
|
if (not master.current._cachedDetailResultSets.has_key(self)) or \ |
|
|
( not int(gConfig('CacheDetailRecords')) and \ |
|
|
not master.current._cachedDetailResultSets[self].isPending() ): |
|
|
doQuery = None |
|
|
for i in range(0, len(self._masterfields)): |
|
|
GDebug.printMesg(10,"Adding criteria") |
|
|
criteria[string.strip(self._detailfields[i])] = \ |
|
|
master.current.getField(string.strip(self._masterfields[i])) |
|
|
|
|
|
#If all are set to None then this will prevent the details |
|
|
#from being queried. This happens are startup with blank master |
|
|
#datasources. |
|
|
doQuery = doQuery or master.current.getField(string.strip(self._masterfields[i])) |
|
|
|
|
|
GDebug.printMesg(10,master.current.getField(self._masterfields[i])) |
|
|
if doQuery: |
|
|
master.current.addDetailResultSet(self.createResultSet(\ |
|
|
conditions=criteria, masterRecordSet=master.current)) |
|
|
else: |
|
|
master.current.addDetailResultSet(self.createEmptyResultSet()) |
|
|
## master.current._cachedDetailResultSets[self] = \ |
|
|
## self.createResultSet(conditions=criteria, masterRecordSet=master.current) |
|
|
return master.current._cachedDetailResultSets[self] |
|
|
|
|
|
|
|
|
########################################################### |
|
|
# |
|
|
# |
|
|
# |
|
|
########################################################### |
|
|
class ResultSet: |
|
|
|
|
|
def __init__(self, dataObject, cursor=None,defaultValues={},masterRecordSet=None): |
|
|
self._dataObject = dataObject |
|
|
self._recordSetClass = RecordSet |
|
|
self._cursor = cursor |
|
|
self._cachedRecords = [] |
|
|
self._currentRecord = -1 |
|
|
self._masterRecordSet = masterRecordSet |
|
|
self._readonly = 0 |
|
|
self._recordCount = 0 |
|
|
self._postingRecord = None |
|
|
|
|
|
self._defaultValues = {} |
|
|
self._defaultValues.update(defaultValues) |
|
|
|
|
|
self.current = None |
|
|
|
|
|
if masterRecordSet: |
|
|
masterRecordSet.addDetailResultSet(self) |
|
|
|
|
|
# Since we are overriding __len__ |
|
|
def __nonzero__(self): |
|
|
return 1 |
|
|
|
|
|
# Return the # of records |
|
|
def __len__(self): |
|
|
return self.getRecordCount() |
|
|
|
|
|
def __getitem__(self, index): |
|
|
rs = self.getRecord(index) |
|
|
if not rs: |
|
|
raise IndexError |
|
|
else: |
|
|
return rs |
|
|
|
|
|
|
|
|
# Returns whether this result set is read only or not |
|
|
def isReadOnly(self): |
|
|
return self._readonly |
|
|
|
|
|
|
|
|
# Returns 1=At first record, 0=Not first record |
|
|
def isFirstRecord(self): |
|
|
return (self._currentRecord == 0) |
|
|
|
|
|
|
|
|
# Returns 1=At last record, 0=Not last record |
|
|
def isLastRecord(self): |
|
|
if self._currentRecord < len(self._cachedRecords) - 1 or \ |
|
|
self._cacheNextRecord(): |
|
|
return 0 |
|
|
else: |
|
|
return 1 |
|
|
|
|
|
|
|
|
# returns -1=No records in memory, #=Current record # |
|
|
def getRecordNumber(self): |
|
|
return self._currentRecord |
|
|
|
|
|
|
|
|
# returns # of records currently loaded |
|
|
def getCacheCount(self): |
|
|
return len(self._cachedRecords) |
|
|
|
|
|
# returns # of records the |
|
|
def getRecordCount(self): |
|
|
return self._recordCount > 0 and self._recordCount or self.getCacheCount() |
|
|
|
|
|
# Get a specific record (0=based) |
|
|
def getRecord(self, record): |
|
|
while (record + 1 > len(self._cachedRecords)) and self._cacheNextRecord(): |
|
|
pass |
|
|
|
|
|
if record + 1 > len(self._cachedRecords): |
|
|
return None |
|
|
else: |
|
|
return self._cachedRecords[record] |
|
|
|
|
|
|
|
|
# move to record #, returns 1=New record loaded, 0=invalid # |
|
|
def setRecord(self, record): |
|
|
|
|
|
while (record > len(self._cachedRecords) -1) and self._cacheNextRecord(): |
|
|
pass |
|
|
|
|
|
if record >= len(self._cachedRecords): |
|
|
return None |
|
|
else: |
|
|
self._currentRecord = record |
|
|
self.current = self._cachedRecords[self._currentRecord] |
|
|
self.notifyDetailObjects() |
|
|
return self.current |
|
|
|
|
|
# returns 1=New record loaded, 0=No more records |
|
|
def nextRecord(self): |
|
|
if self._currentRecord + 1 == len(self._cachedRecords): |
|
|
if not self._cacheNextRecord(): |
|
|
return None |
|
|
|
|
|
self._currentRecord += 1 |
|
|
self.current = self._cachedRecords[self._currentRecord] |
|
|
self.notifyDetailObjects() |
|
|
return self.current |
|
|
|
|
|
|
|
|
# returns 1=New record loaded, 0=At first record |
|
|
def prevRecord(self): |
|
|
if self._currentRecord < 1: |
|
|
return None |
|
|
else: |
|
|
self._currentRecord -= 1 |
|
|
self.current = self._cachedRecords[self._currentRecord] |
|
|
self.notifyDetailObjects() |
|
|
return self.current |
|
|
|
|
|
|
|
|
# returns 1=at first record, 0=No records loaded |
|
|
def firstRecord(self): |
|
|
if self._currentRecord < 0: |
|
|
if not self._cacheNextRecord(): |
|
|
return None |
|
|
|
|
|
self._currentRecord = 0 |
|
|
self.current = self._cachedRecords[0] |
|
|
self.notifyDetailObjects() |
|
|
return self.current |
|
|
|
|
|
|
|
|
|
|
|
# returns 1=at last record, 0=No records loaded |
|
|
def lastRecord(self): |
|
|
if self._currentRecord == -1: |
|
|
return None |
|
|
else: |
|
|
while self._cacheNextRecord(): |
|
|
pass |
|
|
self._currentRecord = len(self._cachedRecords) - 1 |
|
|
self.current = self._cachedRecords[self._currentRecord] |
|
|
self.notifyDetailObjects() |
|
|
return self.current |
|
|
|
|
|
|
|
|
|
|
|
# Insert a blank record after the current record |
|
|
def insertRecord(self): |
|
|
if self.isReadOnly(): |
|
|
# Provide better feedback?? |
|
|
tmsg = _("Attempted to insert into a read only datasource") |
|
|
raise ReadOnlyError, tmsg |
|
|
else: |
|
|
GDebug.printMesg(7,'Inserting a blank record') |
|
|
self._currentRecord += 1 |
|
|
self._cachedRecords.insert(self._currentRecord, self._createEmptyRecord()) |
|
|
self._recordCount += 1 |
|
|
self.current = self._cachedRecords[self._currentRecord] |
|
|
|
|
|
# Set any dataobject-wide default values |
|
|
for field in self._dataObject._defaultValues.keys(): |
|
|
self.current.setField(field, self._dataObject._defaultValues[field],0) |
|
|
|
|
|
# Set any resultset specific values |
|
|
for field in self._defaultValues.keys(): |
|
|
self.current.setField(field, self._defaultValues[field],0) |
|
|
|
|
|
# Pull any primary keys from a master record set |
|
|
if self._masterRecordSet != None and hasattr(self._dataObject, '_masterfields'): |
|
|
i = 0 |
|
|
for field in self._dataObject._masterfields: |
|
|
self.current.setField(self._dataObject._detailfields[i],self._masterRecordSet.getField(field),0) |
|
|
i += 1 |
|
|
|
|
|
self.notifyDetailObjects() |
|
|
return self.current |
|
|
|
|
|
|
|
|
# Returns 1=DataObject, or a detail resultset, has uncommitted changes |
|
|
def isPending(self): |
|
|
for rec in (self._cachedRecords): |
|
|
if rec.isPending(): |
|
|
return 1 |
|
|
else: |
|
|
for detail in rec._cachedDetailResultSets.values(): |
|
|
if detail.isPending(): |
|
|
return 1 |
|
|
return 0 |
|
|
|
|
|
|
|
|
# Returns 1=DataObject has uncommitted changes |
|
|
def isRecordPending(self): |
|
|
return self.current.isPending() |
|
|
|
|
|
|
|
|
def getPostingRecordset(self): |
|
|
global postingRecordset |
|
|
print postingRecordset |
|
|
return postingRecordset |
|
|
|
|
|
# Post changes to the database |
|
|
def post(self, foreign_keys={}): |
|
|
global postingRecordset |
|
|
# post our changes |
|
|
self._update_cursor = self._dataObject._dataConnection.cursor() |
|
|
|
|
|
recordPosition = 0 |
|
|
while recordPosition < len(self._cachedRecords): |
|
|
self._postingRecord = self._cachedRecords[recordPosition] |
|
|
postingRecordset = self._postingRecord |
|
|
delete = self._postingRecord._emptyFlag or self._postingRecord._deleteFlag |
|
|
if not delete: |
|
|
# Flip the flag for 'default' values to true so that hidden |
|
|
# default fields are included in insert statements |
|
|
if self._postingRecord.isPending(): |
|
|
for field in self._dataObject._defaultValues.keys(): |
|
|
self._postingRecord._modifiedFlags[field] = 1 |
|
|
|
|
|
for field in foreign_keys.keys(): |
|
|
self._postingRecord._fields[field] = foreign_keys[field] |
|
|
# Some DBs will throw an exception if you update a Primary Key |
|
|
# (even if you are updating to the same value) |
|
|
if self._postingRecord._insertFlag: |
|
|
self._postingRecord._modifiedFlags[field] = 1 |
|
|
|
|
|
recordPosition += 1 |
|
|
else: |
|
|
# Adjust the current record if a preceding record |
|
|
# or the current record is deleted |
|
|
if recordPosition <= self._currentRecord: |
|
|
self._currentRecord -= 1 |
|
|
self._cachedRecords.pop(recordPosition) |
|
|
self._recordCount -= 1 |
|
|
|
|
|
self._postingRecord.post() |
|
|
|
|
|
# Move to record 0 if all preceding records were deleted |
|
|
# (or set to -1 if all records were deleted) |
|
|
if self._currentRecord < 0: |
|
|
if len(self._cachedRecords): |
|
|
self._currentRecord = 0 |
|
|
else: |
|
|
self._currentRecord = -1 |
|
|
# TODO: I don't think we need this anymore |
|
|
# if self._currentRecord >= self._recordCount: |
|
|
# self._currentRecord = self._recordCount - 1 |
|
|
|
|
|
def notifyDetailObjects(self): |
|
|
GDebug.printMesg(5,'Master record changed; Notifying Detail Objects') |
|
|
for detail in self._dataObject._detailObjects: |
|
|
if detail[1]: |
|
|
detail[1].masterResultSetChanged(self, |
|
|
detail[0]._masterRecordChanged(self)) |
|
|
|
|
|
|
|
|
# Returns 1=Field is bound to a database field |
|
|
def isFieldBound(self, fieldName): |
|
|
if self._dataObject._fieldReferences.has_key(fieldName): |
|
|
return 1 |
|
|
else: |
|
|
#TODO: the string.lower() line should never be called but is left |
|
|
#TODO: here untill the code is cleaned up |
|
|
return self._dataObject._fieldReferences.has_key(string.lower(fieldName)) |
|
|
|
|
|
|
|
|
# Load cacheCount number of new records |
|
|
def _cacheNextRecord(self): |
|
|
rs = self._loadNextRecord() |
|
|
if rs: |
|
|
self._dataObject._dataSource._onRecordLoaded(self._cachedRecords[-1]) |
|
|
return rs |
|
|
|
|
|
|
|
|
|
|
|
### |
|
|
### Methods below should be overridden by Vendor Specific functions |
|
|
### (_createEmptyRecord may not need to be overridden in all cases) |
|
|
### |
|
|
|
|
|
# Load cacheCount number of new records |
|
|
def _loadNextRecord(self): |
|
|
return 0 |
|
|
|
|
|
# Create an empty recordset |
|
|
def _createEmptyRecord(self): |
|
|
return self._recordSetClass(self) |
|
|
|
|
|
# Iterator support (Python 2.2+) |
|
|
def __iter__(self): |
|
|
return _ResultSetIter(self) |
|
|
|
|
|
|
|
|
########################################################### |
|
|
# |
|
|
# |
|
|
# |
|
|
########################################################### |
|
|
class RecordSet: |
|
|
|
|
|
def __init__(self, parent, initialData={}, dbIdentifier=None, defaultData={}): |
|
|
self._detailObjects = [] |
|
|
self._dbIdentifier = dbIdentifier |
|
|
self._deleteFlag = 0 |
|
|
self._updateFlag = 0 |
|
|
self._parent = parent |
|
|
self._modifiedFlags = {} # If field name is present as a key, |
|
|
# then field has been modified |
|
|
|
|
|
self._cachedDetailResultSets = {} |
|
|
|
|
|
self._initialData = initialData |
|
|
|
|
|
if self._initialData and len(self._initialData): |
|
|
self._insertFlag = 0 |
|
|
self._emptyFlag = 0 |
|
|
self._fields = {} |
|
|
self._fields.update(initialData) |
|
|
else: |
|
|
self._insertFlag = 1 |
|
|
self._emptyFlag = 1 |
|
|
self._fields = {} |
|
|
self._fields.update(defaultData) |
|
|
|
|
|
def __setitem__(self, attr, val): |
|
|
self.setField(attr, val) |
|
|
|
|
|
def __getitem__(self, attr): |
|
|
return self.getField(attr) |
|
|
|
|
|
# Returns 1=Record has uncommitted changes |
|
|
def isPending(self): |
|
|
|
|
|
# The _insertFlag and _deleteFlag takes care of records that |
|
|
# were inserted, but then deleted before a save (i.e., nothing to do) |
|
|
if self._emptyFlag or self._insertFlag and self._deleteFlag: |
|
|
return 0 |
|
|
else: |
|
|
return self._insertFlag or self._deleteFlag or self._updateFlag |
|
|
|
|
|
|
|
|
# Returns 1=Record is pending a deletion |
|
|
def isDeleted(self): |
|
|
if self._emptyFlag: |
|
|
return 0 |
|
|
else: |
|
|
return self._deleteFlag and not self._insertFlag |
|
|
|
|
|
|
|
|
# Returns 1=Record is pending an update |
|
|
def isModified(self): |
|
|
if self._emptyFlag or self._insertFlag: |
|
|
return 0 |
|
|
else: |
|
|
return self._updateFlag |
|
|
|
|
|
|
|
|
# Returns 1=Record is pending an insertion |
|
|
def isInserted(self): |
|
|
if self._emptyFlag: |
|
|
return 0 |
|
|
else: |
|
|
return self._insertFlag and not self._deleteFlag |
|
|
|
|
|
|
|
|
# Returns 1=Record is empty (inserted, but no data set) |
|
|
def isEmpty(self): |
|
|
return self._emptyFlag |
|
|
|
|
|
|
|
|
# Returns current value of "field" |
|
|
def getField(self, field): |
|
|
try: |
|
|
return self._fields[field] |
|
|
except KeyError: |
|
|
try: |
|
|
|
|
|
# TODO: When we're confident that |
|
|
# TODO: all field names are lowercase, |
|
|
# TODO: then this can be removed. |
|
|
|
|
|
return self._fields[string.lower(field)] |
|
|
except KeyError: |
|
|
# If a field value has yet to be set |
|
|
# (either from a query or via a setField), |
|
|
# then _fields will not contain a key |
|
|
# for the requested field even though |
|
|
# the field name may still be valid. |
|
|
return None |
|
|
|
|
|
|
|
|
# Sets current value of "field" |
|
|
# If trackMod is set to 0 then the modification flag isn't raised |
|
|
def setField(self, field, value, trackMod = 1): |
|
|
# If this field is bound to a datasource and the datasource is read only, |
|
|
# generate an error. |
|
|
if self._parent.isFieldBound(field) and self._parent.isReadOnly(): |
|
|
# Provide better feedback?? |
|
|
tmsg = _("Attempted to modify read only field '%s'") % field |
|
|
raise ReadOnlyError, tmsg |
|
|
else: |
|
|
fn = string.lower(field) |
|
|
self._fields[fn] = value |
|
|
if trackMod == 1: |
|
|
if self._parent.isFieldBound(field): |
|
|
self._emptyFlag = 0 |
|
|
self._updateFlag = 1 |
|
|
self._modifiedFlags[fn] = 1 |
|
|
|
|
|
try: |
|
|
self._parent._dataObject._dataSource._onModification(self) |
|
|
except AttributeError: |
|
|
pass |
|
|
return value |
|
|
|
|
|
# Batch mode of above setField method |
|
|
# If trackMod is set to 0 then the modification flag isn't raised |
|
|
def setFields(self, updateDict, trackMod = 1): |
|
|
# If this field is bound to a datasource and the datasource is read only, |
|
|
# generate an error. |
|
|
for field in updateDict.keys(): |
|
|
self.setField(field, updateDict[field], trackMod) |
|
|
|
|
|
|
|
|
# Returns 1=Field has been modified |
|
|
def isFieldModified(self, fieldName): |
|
|
if self._modifiedFlags.has_key (fieldName): |
|
|
return 1 |
|
|
else: |
|
|
#TODO: the string.lower() line should never be called but is left here |
|
|
#TODO: until the code is clean |
|
|
return self._modifiedFlags.has_key (string.lower(fieldName)) |
|
|
|
|
|
|
|
|
# Mark the current record as deleted |
|
|
def delete(self): |
|
|
if self._parent.isReadOnly(): |
|
|
# Provide better feedback?? |
|
|
tmsg = _("Attempted to delete from a read only datasource") |
|
|
raise ReadOnlyError, tmsg |
|
|
else: |
|
|
self._deleteFlag = 1 |
|
|
|
|
|
|
|
|
# Posts changes to database |
|
|
def post(self): |
|
|
# Should a post() to a read only datasource cause a ReadOnlyError? |
|
|
# It does no harm to attempt to post since nothing will be posted, |
|
|
# But does this allow sloppy programming? |
|
|
|
|
|
GDebug.printMesg(5,'Preparing to post datasource %s' % self._parent._dataObject.name) |
|
|
|
|
|
# Save the initial status so we know if any triggers changed us |
|
|
status = (self._insertFlag, self._deleteFlag, self._updateFlag) |
|
|
|
|
|
# Call the hooks for commit-level hooks |
|
|
if not self._emptyFlag and hasattr(self._parent._dataObject,'_dataSource'): |
|
|
|
|
|
if self._insertFlag and not self._deleteFlag: |
|
|
self._parent._dataObject._dataSource._beforeCommitInsert(self) |
|
|
elif self._deleteFlag and not self._insertFlag: |
|
|
self._parent._dataObject._dataSource._beforeCommitDelete(self) |
|
|
elif self._updateFlag: |
|
|
self._parent._dataObject._dataSource._beforeCommitUpdate(self) |
|
|
|
|
|
# |
|
|
# If the record status changed while we were doing the triggers, |
|
|
# start from the beginning and run the triggers again. |
|
|
# |
|
|
if status != (self._insertFlag, self._deleteFlag, self._updateFlag): |
|
|
self.post() |
|
|
return |
|
|
|
|
|
|
|
|
if self.isPending(): |
|
|
GDebug.printMesg(5,'Posting datasource %s' % self._parent._dataObject.name) |
|
|
|
|
|
if self.isPending(): |
|
|
self._postChanges() |
|
|
|
|
|
|
|
|
# Post all detail records |
|
|
for child in (self._cachedDetailResultSets.keys()): |
|
|
c = self._cachedDetailResultSets[child]._dataObject |
|
|
# Set the primary key for any new child records |
|
|
fk = {} |
|
|
for i in range(len(c._masterfields)): |
|
|
fk[c._detailfields[i]] = self.getField(c._masterfields[i]) |
|
|
|
|
|
self._cachedDetailResultSets[child].post(foreign_keys=fk) |
|
|
|
|
|
|
|
|
# Sets the ResultSet associated with this master record |
|
|
def addDetailResultSet(self, resultSet): |
|
|
self._cachedDetailResultSets[resultSet._dataObject] = resultSet |
|
|
|
|
|
|
|
|
### |
|
|
### Methods below should be over-written by Vendor Specific functions |
|
|
### |
|
|
|
|
|
# Post any changes to database |
|
|
def _postChanges(self): |
|
|
return 1 |
|
35 |
|
|
36 |
|
|
37 |
# Used to store schema data |
# Used to store schema data |
38 |
|
# TODO: Is this used anywhere?? |
39 |
|
import string |
40 |
class Schema: |
class Schema: |
41 |
def __init__(self, attrs={}, getChildSchema=None): |
def __init__(self, attrs={}, getChildSchema=None): |
42 |
self.name = '' |
self.name = '' |
58 |
|
|
59 |
|
|
60 |
|
|
|
# A simple resultset iterator |
|
|
# Lets you use ResultSets as: |
|
|
# |
|
|
# for record in myResultSet: |
|
|
# blah |
|
|
# |
|
|
# NOTE: Python 2.2+ (but it won't get called in |
|
|
# Python 2.1 or below, so not a problem) |
|
|
# |
|
|
class _ResultSetIter: |
|
|
def __init__(self, resultset): |
|
|
self.resultset = resultset |
|
|
self.used = 0 |
|
|
self.done = 0 |
|
|
|
|
|
def __iter__(self): |
|
|
return self |
|
|
|
|
|
def next(self): |
|
|
if self.done: |
|
|
raise StopIteration |
|
|
if self.used: |
|
|
rs = self.resultset.firstRecord() |
|
|
else: |
|
|
rs = self.resultset.nextRecord() |
|
|
|
|
|
if not rs: |
|
|
raise StopIteration |
|
|
else: |
|
|
return rs |
|
|
|
|