Changeset 2653
- Timestamp:
- 01/11/07 10:43:31 (2 years ago)
- Files:
-
- trunk/dabo/biz/dBizobj.py (modified) (17 diffs)
- trunk/dabo/biz/test/test_dBizobj.py (modified) (7 diffs)
- trunk/dabo/db/__init__.py (modified) (1 diff)
- trunk/dabo/db/dBackend.py (modified) (1 diff)
- trunk/dabo/db/dCursorMixin.py (modified) (28 diffs)
- trunk/dabo/db/dDataSet.py (modified) (5 diffs)
- trunk/dabo/db/dMemento.py (deleted)
- trunk/dabo/db/dbMySQL.py (modified) (1 diff)
- trunk/dabo/db/dbSQLite.py (modified) (3 diffs)
- trunk/dabo/db/test/test_dCursorMixin.py (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/dabo/biz/dBizobj.py
r2643 r2653 254 254 255 255 def saveAll(self, startTransaction=False, topLevel=True): 256 """ Iterates through all the records of the bizobj, and calls save() 257 for any record that has pending changes. 258 """ 256 """Saves all changes to the bizobj and children.""" 257 258 useTransact = startTransaction or topLevel 259 old_pk = getattr(self.Record, self.KeyField, None) 259 260 cursor = self._CurrentCursor 261 262 if useTransact: 263 # Tell the cursor to begin a transaction, if needed. 264 cursor.beginTransaction() 265 266 changed_rows = self.getChangedRows() 267 for row in changed_rows: 268 self._moveToRowNum(row) 269 try: 270 self.save(startTransaction=False, topLevel=False) 271 except dException.ConnectionLostException, e: 272 self._moveToPK(old_pk) 273 raise dException.ConnectionLostException, e 274 except dException.DBQueryException, e: 275 # Something failed; reset things. 276 if useTransact: 277 cursor.rollbackTransaction() 278 # Pass the exception to the UI 279 self._moveToPK(old_pk) 280 raise dException.DBQueryException, e 281 except dException.dException, e: 282 if useTransact: 283 cursor.rollbackTransaction() 284 self._moveToPK(old_pk) 285 raise 286 287 if useTransact: 288 cursor.commitTransaction() 289 290 if old_pk is not None: 291 self._moveToPK(old_pk) 292 293 294 def save(self, startTransaction=False, topLevel=True): 295 """Save any changes that have been made in the current row. 296 297 If the save is successful, the saveAll() of all child bizobjs will be 298 called as well. 299 """ 300 cursor = self._CurrentCursor 301 errMsg = self.beforeSave() 302 if errMsg: 303 raise dException.dException, errMsg 304 305 if self.KeyField is None: 306 raise dException.dException, _("No key field defined for table: ") + self.DataSource 307 308 # Validate any changes to the data. If there is data that fails 309 # validation, an Exception will be raised. 310 self._validate() 311 260 312 useTransact = startTransaction or topLevel 261 313 if useTransact: … … 263 315 cursor.beginTransaction() 264 316 265 try: 266 self.scan(self._saveRowIfChanged, startTransaction=False, topLevel=False) 317 # Save to the Database, but first save the IsAdding flag as the save() call 318 # will reset it to False: 319 isAdding = self.IsAdding 320 try: 321 cursor.save() 322 if isAdding: 323 # Call the hook method for saving new records. 324 self._onSaveNew() 325 326 # Iterate through the child bizobjs, telling them to save themselves. 327 for child in self.__children: 328 # No need to start another transaction. And since this is a child bizobj, 329 # we need to save all rows that have changed. 330 child.saveAll(startTransaction=False, topLevel=False) 331 332 # Finish the transaction, and requery the children if needed. 333 if useTransact: 334 cursor.commitTransaction() 335 if self.RequeryChildOnSave: 336 self.requeryAllChildren() 337 267 338 except dException.ConnectionLostException, e: 268 339 raise dException.ConnectionLostException, e 340 341 except dException.NoRecordsException, e: 342 # Nothing to roll back; just throw it back for the form to display 343 raise dException.NoRecordsException, e 344 269 345 except dException.DBQueryException, e: 270 346 # Something failed; reset things. … … 273 349 # Pass the exception to the UI 274 350 raise dException.DBQueryException, e 275 except dException.dException, e:276 if useTransact:277 cursor.rollbackTransaction()278 raise dException.dException, e279 280 if useTransact:281 cursor.commitTransaction()282 283 284 def _saveRowIfChanged(self, startTransaction, topLevel):285 """ Meant to be called as part of a scan loop. That means that we can286 assume that the current record is the one we want to act on. Also, we287 can pass False for the two parameters, since they will have already been288 accounted for in the calling method.289 """290 if self.isChanged():291 self.save(startTransaction, topLevel)292 293 294 def save(self, startTransaction=False, topLevel=True):295 """ Save any changes that have been made in the data set. If the296 save is successful, the save() of all child bizobjs will be297 called as well.298 """299 cursor = self._CurrentCursor300 errMsg = self.beforeSave()301 if errMsg:302 raise dException.dException, errMsg303 304 if self.KeyField is None:305 raise dException.dException, _("No key field defined for table: ") + self.DataSource306 307 # Validate any changes to the data. If there is data that fails308 # validation, an Exception will be raised.309 self._validate()310 311 useTransact = startTransaction or topLevel312 if useTransact:313 # Tell the cursor to begin a transaction, if needed.314 cursor.beginTransaction()315 316 # Save to the Database, but first save the IsAdding flag as the save() call317 # will reset it to False:318 isAdding = self.IsAdding319 try:320 cursor.save()321 if isAdding:322 # Call the hook method for saving new records.323 self._onSaveNew()324 325 # Iterate through the child bizobjs, telling them to save themselves.326 for child in self.__children:327 # No need to start another transaction. And since this is a child bizobj,328 # we need to save all rows that have changed.329 child.saveAll(startTransaction=False, topLevel=False)330 331 # Finish the transaction, and requery the children if needed.332 if useTransact:333 cursor.commitTransaction()334 if self.RequeryChildOnSave:335 self.requeryAllChildren()336 337 self.setMemento()338 339 except dException.ConnectionLostException, e:340 raise dException.ConnectionLostException, e341 342 except dException.NoRecordsException, e:343 # Nothing to roll back; just throw it back for the form to display344 raise dException.NoRecordsException, e345 346 except dException.DBQueryException, e:347 # Something failed; reset things.348 if useTransact:349 cursor.rollbackTransaction()350 # Pass the exception to the UI351 raise dException.DBQueryException, e352 351 353 352 except dException.dException, e: … … 356 355 cursor.rollbackTransaction() 357 356 # Pass the exception to the UI 358 raise dException.dException, e357 raise 359 358 360 359 # Some backends (Firebird particularly) need to be told to write … … 369 368 370 369 def cancelAll(self): 371 """ Iterates through all the records, canceling each in turn."""372 self.scan (self.cancel)370 """Cancel all changes made to the current dataset, including all children.""" 371 self.scanChangedRows(self.cancel, allCursors=True) 373 372 374 373 375 374 def cancel(self): 376 """ Cancel any changes to the current record, reverting the fields 377 back to their original values. 375 """Cancel all changes to the current record and all children. 376 377 Two hook methods will be called: beforeCancel() and afterCancel(). The 378 former, if it returns an error message, will raise an exception and not 379 continue cancelling the record. 378 380 """ 379 381 errMsg = self.beforeCancel() 380 if not errMsg:381 errMsg = self.beforePointerMove()382 382 if errMsg: 383 383 raise dException.dException, errMsg 384 384 385 # Tell the cursor to cancel any changes385 # Tell the cursor and all children to cancel themselves: 386 386 self._CurrentCursor.cancel() 387 # Tell each child to cancel themselves388 387 for child in self.__children: 389 388 child.cancelAll() 390 child.requery() 391 392 self.setMemento() 389 393 390 self.afterCancel() 394 391 395 392 396 393 def delete(self, startTransaction=False): 397 """ Delete the current row of the data set."""394 """Delete the current row of the data set.""" 398 395 cursor = self._CurrentCursor 399 396 errMsg = self.beforeDelete() … … 471 468 472 469 473 def getChangedRecordNumbers(self): 474 """ Returns a list of record numbers for which isChanged() 475 returns True. The changes may therefore not be in the record 476 itself, but in a dependent child record. 477 """ 478 self.__changedRecordNumbers = [] 479 self.scan(self._listChangedRecordNumbers) 480 return self.__changedRecordNumbers 481 482 483 def _listChangedRecordNumbers(self): 470 def getChangedRows(self): 471 """ Returns a list of row numbers for which isChanged() returns True. The 472 changes may therefore not be in the record itself, but in a dependent child 473 record. 474 """ 475 if self.__children: 476 # Must iterate all records to find potential changes in children: 477 self.__changedRows = [] 478 self.scan(self._listChangedRows) 479 return self.__changedRows 480 else: 481 # Can use the much faster cursor.getChangedRows(): 482 return self._CurrentCursor.getChangedRows() 483 484 485 def _listChangedRows(self): 484 486 """ Called from a scan loop. If the current record is changed, 485 487 append the RowNumber to the list. 486 488 """ 487 489 if self.isChanged(): 488 self.__changedR ecordNumbers.append(self.RowNumber)490 self.__changedRows.append(self.RowNumber) 489 491 490 492 … … 510 512 self.__scanReverse is true, the records are processed in reverse order. 511 513 """ 512 if self.RowCount <= 0: 513 # Nothing to scan! 514 return 515 514 self.scanRows(func, range(self.RowCount), *args, **kwargs) 515 516 517 def scanRows(self, func, rows, *args, **kwargs): 518 """Iterate over the specified rows and apply the passed function to each. 519 520 Set self.exitScan to True to exit the scan on the next iteration. 521 """ 516 522 # Flag that the function can set to prematurely exit the scan 517 523 self.exitScan = False 524 rows = list(rows) 518 525 if self.__scanRestorePosition: 519 526 currRow = self.RowNumber 520 527 try: 521 528 if self.__scanReverse: 522 recRange = range(self.RowCount-1, -1, -1) 523 else: 524 recRange = range(self.RowCount) 525 for i in recRange: 529 rows.reverse() 530 for i in rows: 526 531 self._moveToRowNum(i) 527 532 func(*args, **kwargs) … … 544 549 545 550 551 def scanChangedRows(self, func, allCursors=False, *args, **kwargs): 552 """Move the record pointer to each changed row, and call func. 553 554 If allCursors is True, all other cursors for different parent records will 555 be iterated as well. 556 557 If you want to end the scan on the next iteration, set self.exitScan=True. 558 559 Records are scanned in arbitrary order. Any exception raised by calling 560 func() will be passed up to the caller. 561 """ 562 self.exitScan = False 563 old_currentCursorKey = self.__currentCursorKey 564 old_pk = getattr(self.Record, self.KeyField, None) 565 566 if allCursors: 567 cursors = self.__cursors 568 else: 569 cursors = [{None: self._CurrentCursor}] 570 571 for key, cursor in cursors.iteritems(): 572 self._CurrentCursor = key 573 changed_keys = list(set(cursor._mementos.keys() + cursor._newRecords.keys())) 574 for pk in changed_keys: 575 self._moveToPK(pk) 576 try: 577 func(*args, **kwargs) 578 except: 579 # Reset things and bail: 580 self._CurrentCursor = old_currentCursorKey 581 self._moveToPK(old_pk) 582 raise 583 584 self._CurrentCursor = old_currentCursorKey 585 if old_pk is not None: 586 self._moveToPK(old_pk) 587 588 546 589 def getFieldNames(self): 547 590 """Returns a tuple of all the field names in the cursor.""" … … 574 617 575 618 self._CurrentCursor.new() 576 # Hook method for things to do after a new record is created.577 619 self._onNew() 578 620 … … 586 628 child.new() 587 629 588 self.setMemento()589 630 self.afterPointerMove() 590 631 self.afterNew() … … 662 703 except dException.NoRecordsException: 663 704 pass 664 self.setMemento()665 705 self.afterRequery() 666 706 … … 832 872 833 873 834 def isAnyChanged(self): 835 """ Returns True if any record in the current record set has been 836 changed. 837 """ 838 self.__areThereAnyChanges = False 839 self.scan(self._checkForChanges) 840 return self.__areThereAnyChanges 841 842 843 def _checkForChanges(self): 844 """ Designed to be called from the scan iteration over the records 845 for this bizobj. Once one changed record is found, set the scan's 846 exit flag, since we only need to know if anything has changed. 847 """ 848 if self.isChanged(): 849 self.__areThereAnyChanges = True 850 self.exitScan = True 874 def isAnyChanged(self, _topLevel=True): 875 """Returns True if any record in the current record set has been changed.""" 876 877 if _topLevel: 878 # Only check the _CurrentCursor: 879 if self._CurrentCursor.isChanged(allRows=True): 880 return True 881 else: 882 # Need to check all cached cursors: 883 for cursor in self.__cursors.values(): 884 if cursor.isChanged(allRows=True): 885 return True 886 887 # Nothing's changed in the top level, so we need to recurse the children: 888 for child in self.__children: 889 if child.isAnyChanged(_topLevel=False): 890 return True 891 892 return False 851 893 852 894 … … 864 906 # No cursor, no changes. 865 907 return False 866 ret = cc.isChanged(allRows =False)908 ret = cc.isChanged(allRows=False) 867 909 868 910 if not ret: … … 903 945 """ 904 946 cursor = self._CurrentCursor 905 cursor.setDefaults(self.DefaultValues)906 947 if self.AutoPopulatePK: 907 948 # Provide a temporary PK so that any linked children can be properly … … 911 952 if self.Parent and self.FillLinkFromParent and self.LinkField: 912 953 self.setParentFK() 954 cursor.setDefaults(self.DefaultValues) 955 cursor.setNewFlag() 956 913 957 # Call the custom hook method 914 958 self.onNew() … … 1050 1094 # Let the child know the current dependent PK 1051 1095 child.setCurrentParent(pk) 1052 if not child.isChanged() and child.RequeryWithParent: 1053 child.requery() 1054 1096 if child.RequeryWithParent: 1097 if not child.isChanged(): 1098 child.requery() 1099 1055 1100 self.afterChildRequery() 1056 1101 … … 1140 1185 """ 1141 1186 return self.__params 1142 1143 1144 def setMemento(self):1145 """ Take a snapshot of the data in the cursor.1146 1147 Tell the cursor to take a snapshot of the current state of the1148 data. This snapshot will be used to determine what, if anything, has1149 changed later on.1150 1151 User code should not normally call this method.1152 """1153 self._CurrentCursor.setMemento()1154 1187 1155 1188 trunk/dabo/biz/test/test_dBizobj.py
r2649 r2653 3 3 from dabo.lib import getRandomUUID 4 4 5 6 # Testing anything other than sqlite requires network access. So set these 7 # flags so that only the db's you want to test against are True. 8 test_sqlite = True 9 test_mysql = True 10 11 if test_sqlite: 12 sqlite_unittest = unittest.TestCase 13 else: 14 sqlite_unittest = object 15 16 if test_mysql: 17 mysql_unittest = unittest.TestCase 18 else: 19 mysql_unittest = object 20 21 22 class Test_dBizobj(object): 5 ## Only tests against sqlite, as we already test dCursorMixin against the 6 ## various backends. 7 8 class Test_dBizobj(unittest.TestCase): 23 9 def setUp(self): 24 biz = self.biz 10 self.con = dabo.db.dConnection(DbType="SQLite", Database=":memory:") 11 biz = self.biz = dabo.biz.dBizobj(self.con) 25 12 uniqueName = getRandomUUID().replace("-", "")[-20:] 26 13 self.temp_table_name = "unittest%s" % uniqueName … … 36 23 37 24 def createSchema(self): 38 """Create the test schema. Override in subclasses.""" 39 pass 25 biz = self.biz 26 tableName = self.temp_table_name 27 childTableName = self.temp_child_table_name 28 biz._CurrentCursor.executescript(""" 29 create table %(tableName)s (pk INTEGER PRIMARY KEY AUTOINCREMENT, cField CHAR, iField INT, nField DECIMAL (8,2)); 30 insert into %(tableName)s (cField, iField, nField) values ("Paul Keith McNett", 23, 23.23); 31 insert into %(tableName)s (cField, iField, nField) values ("Edward Leafe", 42, 42.42); 32 insert into %(tableName)s (cField, iField, nField) values ("Carl Karsten", 10223, 23032.76); 33 34 create table %(childTableName)s (pk INTEGER PRIMARY KEY AUTOINCREMENT, parent_fk INT, cInvNum CHAR); 35 insert into %(childTableName)s (parent_fk, cInvNum) values (1, "IN00023"); 36 insert into %(childTableName)s (parent_fk, cInvNum) values (1, "IN00455"); 37 insert into %(childTableName)s (parent_fk, cInvNum) values (3, "IN00024"); 38 """ % locals()) 40 39 41 40 … … 102 101 biz.Record.cField = "Denise McNett" 103 102 self.assertEqual(biz.Record.cField, "Denise McNett") 103 self.assertEqual(cur._mementos[biz.Record.pk]["cField"], "Paul Keith McNett") 104 104 biz.Record.cField = "Alison Anton" 105 105 self.assertEqual(biz.Record.cField, "Alison Anton") 106 self.assertEqual(cur._mementos[biz.Record.pk]["cField"], "Paul Keith McNett") 106 107 biz.setFieldVal("iField", 80) 107 108 self.assertEqual(biz.Record.iField, 80) 108 109 self.assertTrue(isinstance(biz.Record.iField, (int, long))) 110 self.assertEqual(cur._mementos[self.biz.Record.pk]["iField"], 23) 109 111 110 112 def test_RowCount(self): … … 140 142 ## - End property unit tests - 141 143 144 145 def testMementos(self): 146 biz = self.biz 147 cur = biz._CurrentCursor 148 149 priorVal = biz.Record.cField 150 151 # Make a change that is the same as the prior value: 152 biz.Record.cField = priorVal 153 self.assertEqual(priorVal, biz.Record.cField) 154 155 # Make a change that is different: 156 biz.Record.cField = "New test value" 157 self.assertEqual(cur._mementos, {biz.Record.pk: {"cField": priorVal}}) 158 self.assertEqual(biz.isChanged(), True) 159 160 # Change it back: 161 biz.Record.cField = priorVal 162 self.assertEqual(cur._mementos, {}) 163 self.assertEqual(biz.isChanged(), False) 164 165 # Make a change that is different and cancel: 166 biz.Record.cField = "New test value" 167 biz.cancel() 168 self.assertEqual(cur._mementos, {}) 169 self.assertEqual(biz.isChanged(), False) 170 171 # Add a record: 172 biz.new() 173 174 self.assertEqual(biz.RowCount, 4) 175 self.assertEqual(biz.RowNumber, 3) 176 self.assertEqual(cur._newRecords, {"-1-dabotmp": None}) 177 self.assertEqual(biz.isChanged(), True) 178 self.assertEqual(cur.Record.pk, "-1-dabotmp") 179 self.assertEqual(biz.Record.cField, "") 180 self.assertEqual(biz.Record.iField, 0) 181 self.assertEqual(biz.Record.nField, 0) 182 biz.save() 183 biz.requery() 184 self.assertEqual(biz.RowCount, 4) 185 self.assertEqual(biz.RowNumber, 3) 186 self.assertEqual(cur._newRecords, {}) 187 self.assertEqual(biz.isChanged(), False) 188 self.assertEqual(biz.Record.pk, 4) 189 190 # The new fields should be NULL, since we didn't explicitly set them: 191 self.assertEqual(biz.Record.cField, None) 192 self.assertEqual(biz.Record.iField, None) 193 self.assertEqual(biz.Record.nField, None) 194 142 195 def testChildren(self): 143 196 bizMain = self.biz … … 147 200 bizChild.DataSource = self.temp_child_table_name 148 201 bizChild.LinkField = "parent_fk" 202 bizChild.FillLinkFromParent = True 149 203 150 204 bizMain.addChild(bizChild) … … 224 278 225 279 bizMain.saveAll() 280 281 self.assertEqual(bizMain.RowCount, 3) 282 self.assertEqual(bizMain.RowNumber, 2) 283 self.assertEqual(bizChild.RowCount, 1) 284 self.assertEqual(bizChild.Record.cInvNum, "IN99991") 285 self.assertEqual(bizChild.IsAdding, False) 226 286 bizMain.requery() 227 287 self.assertEqual(bizMain.RowCount, 3) … … 233 293 self.assertEqual(bizChild.RowCount, 1) 234 294 self.assertEqual(bizChild.Record.cInvNum, "IN99991") 235 236 237 class Test_dBizobj_sqlite(Test_dBizobj, sqlite_unittest): 238 def setUp(self): 239 self.con = dabo.db.dConnection(DbType="SQLite", Database=":memory:") 240 self.biz = dabo.biz.dBizobj(self.con) 241 super(Test_dBizobj_sqlite, self).setUp() 242 243 def createSchema(self): 244 biz = self.biz 245 tableName = self.temp_table_name 246 childTableName = self.temp_child_table_name 247 biz._CurrentCursor.executescript(""" 248 create table %(tableName)s (pk INTEGER PRIMARY KEY AUTOINCREMENT, cField CHAR, iField INT, nField DECIMAL (8,2)); 249 insert into %(tableName)s (cField, iField, nField) values ("Paul Keith McNett", 23, 23.23); 250 insert into %(tableName)s (cField, iField, nField) values ("Edward Leafe", 42, 42.42); 251 insert into %(tableName)s (cField, iField, nField) values ("Carl Karsten", 10223, 23032.76); 252 253 create table %(childTableName)s (pk INTEGER PRIMARY KEY AUTOINCREMENT, parent_fk INT, cInvNum CHAR); 254 insert into %(childTableName)s (parent_fk, cInvNum) values (1, "IN00023"); 255 insert into %(childTableName)s (parent_fk, cInvNum) values (1, "IN00455"); 256 insert into %(childTableName)s (parent_fk, cInvNum) values (3, "IN00024"); 257 """ % locals()) 258 259 260 class Test_dBizobj_mysql(Test_dBizobj, mysql_unittest): 261 def setUp(self): 262 self.con = dabo.db.dConnection(DbType="MySQL", User="dabo_unittest", 263 password="T30T35DB4K30Z45I67N60", Database="dabo_unittest", 264 Host="paulmcnett.com") 265 self.biz = dabo.biz.dBizobj(self.con) 266 super(Test_dBizobj_mysql, self).setUp() 267 268 def tearDown(self): 269 self.biz._CurrentCursor.execute("drop table %s" % self.temp_table_name) 270 super(Test_dBizobj_mysql, self).tearDown() 271 272 def createSchema(self): 273 biz = self.biz 274 cur = biz._CurrentCursor 275 tableName = self.temp_table_name 276 childTableName = self.temp_child_table_name 277 cur.execute(""" 278 create table %s (pk INTEGER PRIMARY KEY AUTO_INCREMENT, cField CHAR (32), iField INT, nField DECIMAL (8,2)) 279 """ % tableName) 280 cur.execute(""" 281 insert into %s (cField, iField, nField) values ("Paul Keith McNett", 23, 23.23) 282 """ % tableName) 283 cur.execute(""" 284 insert into %s (cField, iField, nField) values ("Edward Leafe", 42, 42.42) 285 """ % tableName) 286 cur.execute(""" 287 insert into %s (cField, iField, nField) values ("Carl Karsten", 10223, 23032.76) 288 """ % tableName) 289 290 cur.execute(""" 291 create table %s (pk INTEGER PRIMARY KEY AUTO_INCREMENT, parent_fk INT, cInvNum CHAR (16)) 292 """ % childTableName) 293 cur.execute(""" 294 insert into %s (parent_fk, cInvNum) values (1, "IN00023") 295 """ % childTableName) 296 cur.execute(""" 297 insert into %s (parent_fk, cInvNum) values (1, "IN00455") 298 """ % childTableName) 299 cur.execute(""" 300 insert into %s (parent_fk, cInvNum) values (3, "IN00024") 301 """ % childTableName) 302 295 303 296 304 297 if __name__ == "__main__": trunk/dabo/db/__init__.py
r2344 r2653 34 34 from dConnection import dConnection 35 35 from dCursorMixin import dCursorMixin 36 from dMemento import dMemento37 36 from dConnectInfo import dConnectInfo 38 37 from dTable import dTable trunk/dabo/db/dBackend.py
r2582 r2653 396 396 397 397 def getStructureDescription(self, cursor): 398 """This will work for most backends. However, SQLite doesn't 399 properly return the structure when no records are returned. 400 """ 401 #Try using the no-records version of the SQL statement. 402 try: 403 tmpsql = cursor.getStructureOnlySql() 404 except AttributeError: 405 # We need to parse the sql property to get what we need. 406 import re 407 pat = re.compile("(\s*select\s*.*\s*from\s*.*\s*)((?:where\s(.*))+)\s*", re.I | re.M | re.S) 408 if pat.search(cursor.sql): 409 # There is a WHERE clause. Add the NODATA clause 410 tmpsql = pat.sub("\\1 where 1=0 ", cursor.sql) 411 else: 412 # no WHERE clause. See if it has GROUP BY or ORDER BY clauses 413 pat = re.compile("(\s*select\s*.*\s*from\s*.*\s*)((?:group\s*by\s(.*))+)\s*", re.I | re.M | re.S) 414 if pat.search(cursor.sql): 415 tmpsql = pat.sub("\\1 where 1=0 ", cursor.sql) 416 else: 417 pat = re.compile("(\s*select\s*.*\s*from\s*.*\s*)((?:order\s*by\s(.*))+)\s*", re.I | re.M | re.S) 418 if pat.search(cursor.sql): 419 tmpsql = pat.sub("\\1 where 1=0 ", cursor.sql) 420 else: 421 # Nothing. So just tack it on the end. 422 tmpsql = cursor.sql + " where 1=0 " 423 #print tmpsql 424 auxCrs = cursor._getAuxCursor() 425 auxCrs.execute(tmpsql) 426 auxCrs.storeFieldTypes() 427 return auxCrs.FieldDescription 428 398 """Return the basic field structure.""" 399 field_structure = {} 400 field_names = [] 401 402 field_description = cursor.FieldDescription 403 if not field_description: 404 # No query run yet: execute the structure-only sql: 405 structure_only_sql = cursor.getStructureOnlySql() 406 aux = cursor.AuxCursor 407 aux.execute(structure_only_sql) 408 field_description = aux.FieldDescription 409 410 for field_info in field_description: 411 field_name = field_info[0] 412 field_type = self.getDaboFieldType(field_info[1]) 413 field_names.append(field_name) 414 field_structure[field_name] = (field_type, False) 415 416 standard_fields = cursor.getFields() 417 for field_name, field_type, pk in standard_fields: 418 if field_name in field_names or not field_names: 419 # We only use the info for the standard field in one of two cases: 420 # 1) There aren't any fields in the FieldDescription, which would be 421 # the case if we haven't set the SQL or requeried yet. 422 # 2) The field exists in the FieldDescription, and FieldDescription 423 # didn't provide good type information. 424 if field_structure[field_name][0] == "?": 425 # Only override what was in FieldStructure if getFields() gave better info. 426 field_structure[field_name] = (field_type, pk) 427 if pk is True: 428 # FieldStructure doesn't provide pk information: 429 field_structure[field_name] = (field_structure[field_name][0], pk) 430 431 ret = [] 432 for field in field_names: 433 ret.append( (field, field_structure[field][0], field_structure[field][1]) ) 434 return tuple(ret) 435 429 436 430 437 ########## Created by Echo ############## trunk/dabo/db/dCursorMixin.py
r2649 r2653 17 17 import dabo 18 18 import dabo.dConstants as kons 19 from dabo.db.dMemento import dMemento20 19 from dabo.dLocalize import _ 21 20 import dabo.dException as dException … … 108 107 self._cursorFactoryClass = None 109 108 109 # mementos and new records, keyed on record object ids: 110 self._mementos = {} 111 self._newRecords = {} 112 110 113 self.initProperties() 111 114 … … 168 171 # Fields of any type can be None (NULL). 169 172 pass 173 elif _USE_DECIMAL and type(field_val) in (float,) \ 174 and pythonType in (Decimal,): 175 ret = pythonType(str(field_val)) 170 176 else: 171 177 try: … … 292 298 _fromRequery=_fromRequery) 293 299 294 # Convert to dDataSet295 self._records = dDataSet(self._records)296 300 return res 297 301 … … 313 317 self.execute(self.CurrentSQL, params, _fromRequery=True) 314 318 315 # Add mementos to each row of the result set 316 self.addMemento(-1) 319 # clear mementos and new record flags: 320 self._mementos = {} 321 self._newRecords = {} 317 322 318 323 # Check for any derived fields that should not be included in … … 558 563 for rec in self._records: 559 564 recInfo = [ colTemplate % (k, self.getType(v), self.escape(v)) 560 for k,v in rec.items() 561 if k != "dabo-memento"] 565 for k,v in rec.items() ] 562 566 rowXML += rowTemplate % "\n".join(recInfo) 563 567 return base % (self.Encoding, self.AutoPopulatePK, self.KeyField, … … 609 613 610 614 def isChanged(self, allRows=True): 611 """Scan all the records and compare them with their mementos. 612 Returns True if any differ, False otherwise. 613 """ 614 ret = False 615 if self.RowCount > 0: 616 if allRows: 617 recs = self._records 618 else: 619 recs = (self._records[self.RowNumber],) 620 621 for rec in recs: 622 if self.isRowChanged(rec): 623 ret = True 624 break 625 return ret 626 627 628 def isRowChanged(self, rec): 629 ret = False 630 if rec.has_key(kons.CURSOR_MEMENTO): 631 mem = rec[kons.CURSOR_MEMENTO] 632 newrec = rec.has_key(kons.CURSOR_NEWFLAG) 633 ret = newrec or mem.isChanged(rec) 634 return ret 635 636 637 def setMemento(self): 638 if self.RowCount > 0: 639 if (self.RowNumber >= 0) and (self.RowNumber < self.RowCount): 640 self.addMemento(self.RowNumber) 641 642 615 """Return True if there are any changes to the local field values. 616 617 If allRows is True (the default), all records in the recordset will be 618 considered. Otherwise, only the current record will be checked. 619 """ 620 if allRows: 621 return len(self._mementos) > 0 or len(self._newRecords) > 0 622 else: 623 row = self.RowNumber 624 625 try: 626 rec = self._records[row] 627 except IndexError: 628 # self.RowNumber doesn't exist (init phase?) Nothing's changed: 629 return False 630 631 memento = self._mementos.get(rec[self.KeyField], None) 632 new_rec = self._newRecords.has_key(rec[self.KeyField]) 633 634 return not (memento is None and not new_rec) 635 636 637 def setNewFlag(self): 638 """Set the current record to be flagged as a new record. 639 640 dBizobj will automatically call this method as appropriate, but if you are 641 using dCursor without a proxy dBizobj, you'll need to manually call this 642 method after cursor.new(), and (if applicable) after cursor.genTempAutoPK(). 643 For example: 644 cursor.new() 645 cursor.genTempAutoPK() 646 cursor.setNewFlag() 647 """ 648 if self.KeyField: 649 rec = self._records[self.RowNumber] 650 self._newRecords[rec[self.KeyField]] = None 651 652 643 653 def genTempAutoPK(self): 644 654 """ Create a temporary PK for a new record. Set the key field to this … … 677 687 raise dException.NoRecordsException, _("No records in the data set.") 678 688 rec = self._records[self.RowNumber] 679 if rec.has_key(kons.CURSOR_NEWFLAG) and self.AutoPopulatePK:689 if self._newRecords.has_key(rec[self.KeyField]) and self.AutoPopulatePK: 680 690 # New, unsaved record 681 691 ret = rec[kons.CURSOR_TMPKEY_FIELD] … … 733 743 734 744 735 def setFieldVal(self, fld, val): 736 """ Set the value of the specified field. """ 745 def _hasValidKeyField(self): 746 """Return True if the KeyField exists and names valid fields.""" 747 try: 748 self.checkPK() 749 except dException.MissingPKException: 750 return False 751 return True 752 753 754 def setFieldVal(self, fld, val, row=None): 755 """Set the value of the specified field.""" 737 756 if self.RowCount <= 0: 738 757 raise dException.NoRecordsException, _("No records in the data set") 739 else: 740 rec = self._records[self.RowNumber] 741 if rec.has_key(fld): 742 if self._types.has_key(fld): 743 fldType = self._types[fld] 758 759 if row is None: 760 row = self.RowNumber 761 762 rec = self._records[row] 763 valid_pk = self._hasValidKeyField() 764 keyField = self.KeyField 765 766 if not rec.has_key(fld): 767 ss = _("Field '%s' does not exist in the data set.") % (fld,) 768 raise dException.FieldNotFoundException, ss 769 770 if self._types.has_key(fld): 771 fldType = self._types[fld] 772 else: 773 fldType = self._fldTypeFromDB(fld) 774 if fldType is not None: 775 if fldType != type(val): 776 convTypes = (str, unicode, int, float, long, complex) 777 if isinstance(val, convTypes) and isinstance(rec[fld], basestring): 778 if isinstance(fldType, str): 779 val = str(val) 780 else: 781 val = unicode(val) 782 elif isinstance(rec[fld], int) and isinstance(val, bool): 783 # convert bool to int (original field val was bool, but UI 784 # changed to int. 785 val = int(val) 786 elif isinstance(rec[fld], int) and isinstance(val, long): 787 # convert long to int (original field val was int, but UI 788 # changed to long. 789 val = int(val) 790 elif isinstance(rec[fld], long) and isinstance(val, int): 791 # convert int to long (original field val was long, but UI 792 # changed to int. 793 val = long(val) 794 795 if fldType != type(val): 796 ignore = False 797 # Date and DateTime types are handled as character, even if the 798 # native field type is not. Ignore these. NOTE: we have to deal with the 799 #
