Discussion:
[Tkinter-discuss] Tkinter (python 3) and Sql-Alchemy
Russell Adams
2013-01-26 00:50:56 UTC
Permalink
I've hit a wall trying to make a fairly simple tkinter form oriented
application using SQL-Alchemy in keeping the tkinter widgets and the
sql-alchemy objects in synchronization. Google searches haven't
provided any benefit, so I turn to the mailing list.

Does anyone have any code samples, best practices, or could recommend
other open source programs that use tkinter and sql-alchemy that
correctly demonstrate how to work with the pair?

The issues I've been having are typically due to implicit string
conversions, input validation, converting strings back to the sql data
type (date, datetime, boolean, integer, decimal.decimal), and
synchronizing updates to the screen vs updates to the sql-a object.

I've tried a few methods now, including using Variables (ie: IntVar,
StringVar, etc), tracing those variables, and now I'm working through
creating a class which would inherit from Variable but perform the
type conversions.

Oddly enough session management has been a breeze.

Code is at:
https://code.launchpad.net/~rladams/+junk/TERT

Thanks.



------------------------------------------------------------------
Russell Adams RLAdams at AdamsInfoServ.com

PGP Key ID: 0x1160DCB3 http://www.adamsinfoserv.com/

Fingerprint: 1723 D8CA 4280 1EC9 557F 66E8 1154 E018 1160 DCB3
Michael Lange
2013-01-26 17:37:11 UTC
Permalink
Hi,

On Fri, 25 Jan 2013 18:50:56 -0600
Post by Russell Adams
Does anyone have any code samples, best practices, or could recommend
other open source programs that use tkinter and sql-alchemy that
correctly demonstrate how to work with the pair?
(...)
Post by Russell Adams
I've tried a few methods now, including using Variables (ie: IntVar,
StringVar, etc), tracing those variables, and now I'm working through
creating a class which would inherit from Variable but perform the
type conversions.
I don't know anything about sql-alchemy, from what you write my first
idea too was to subclass StringVar and add a datatype attribute to the
new SqlVariable class and let get() and set() handle the conversions
according to self.datatype. If you have only the five mentioned datatypes
I think this shouldn't be too hard and might prove quite handy then.

For the date and time datatypes maybe you could borrow some code from the
Pmw.EntryField validation mechanisms.

Regards

Michael


.-.. .. ...- . .-.. --- -. --. .- -. -.. .--. .-. --- ... .--. . .-.

Vulcans worship peace above all.
-- McCoy, "Return to Tomorrow", stardate 4768.3
Russell Adams
2013-01-28 23:22:14 UTC
Permalink
Post by Michael Lange
I don't know anything about sql-alchemy, from what you write my first
idea too was to subclass StringVar and add a datatype attribute to the
new SqlVariable class and let get() and set() handle the conversions
according to self.datatype. If you have only the five mentioned datatypes
I think this shouldn't be too hard and might prove quite handy then.
So I've just tried this, with verbose logging.

I've overridden the set method to also try to set the DB object to the
native value with a conversion, and soft failure (ie: if you haven't
finished typing a date, it doesn't bomb, it just doesn't update the DB
object).

Making changes on the screen (ie: typing in a widget) doesn't appear
to call set at all, my debugging messages never even pop up. Thus the
DB object is never updated.

Did I misunderstand something in your suggestion?

I'm thinking I may need to add a trace that does the sync, not
override set().

Thanks.




------------------------------------------------------------------
Russell Adams RLAdams at AdamsInfoServ.com

PGP Key ID: 0x1160DCB3 http://www.adamsinfoserv.com/

Fingerprint: 1723 D8CA 4280 1EC9 557F 66E8 1154 E018 1160 DCB3
Russell Adams
2013-01-29 14:26:11 UTC
Permalink
Post by Russell Adams
I'm thinking I may need to add a trace that does the sync, not
override set().
To answer my own question and put it on the ML for future reference,
creating classes for SQL-Alchemy SQL data types that bind a trace
callback does indeed synchronize changes that happen to the widget
onscreen with the SQL-A object.

This is very convenient because my forms are all a single transaction
in the DB, and when the changes are synced my save call is merely
commit.

Thanks.

# ----------------------------------------------------------------------
class stringFieldVar(Variable):
"""Override Tk Variable class to allow sync and validation with SQLA objects"""

def __init__(self, datum, field):

self.datum = datum
self.field = field

super().__init__( value = self._tkConvert(getattr(self.datum, self.field, '')) )
self.trace("w", self.synchronizeWithSQL)


def _tkConvert(self, value):
"""Convert to a string for Tk, only used on initial set from DB"""
if value == None:
result = ''
else:
result = str(value)
return result


def _sqlConvert(self, value):
"""Convert a Tk string back to SQL datatype, happens every widget refresh"""
if value == '':
result = None
elif value:
result = str(value)
return result


def synchronizeWithSQL(self, *args):
"""Convert data for DB type, the widget calls this repeatedly with changes"""

try:
# only update the sql object if the value is valid
logger.debug("Testing {}, is '{}' valid?".format(self.field, self.get()))
sqlValue = self._sqlConvert(self.get())
setattr(self.datum, self.field, sqlValue)
logger.debug("Setting {}, converted '{}', to '{}'".format(self.field, self.get(), sqlValue))
except (AttributeError, TypeError):
pass



class intFieldVar(stringFieldVar):

def _sqlConvert(self, value):
if value == '':
result = None
elif value:
result = int(value)
return result



class numericFieldVar(stringFieldVar):

def _sqlConvert(self, value):
if value == '':
result = None
elif value:
result = D.decimal(value)
return result



class booleanFieldVar(stringFieldVar):

def _tkConvert(self, value):
"""Convert to a string for Tk, only used on initial set from DB"""
if value == None:
result = ''
elif value == True:
result = '1'
else:
result = '0'
return result


def _sqlConvert(self, value):
if value == '':
result = None
elif value == '1':
result = True
else:
result = False
return result



class dateFieldVar(stringFieldVar):

def _tkConvert(self, value):
return '' if value == None else value.strftime("%Y/%m/%d")


def _sqlConvert(self, value):
if value == '':
result = None
elif value == 'now':
result = 'now'
elif value:
result = time.strftime("%Y/%m/%d", value)
return result



class datetimeFieldVar(stringFieldVar):

def _tkConvert(self, value):
return '' if value == None else value.strftime("%Y/%m/%d %H:%M:%S")


def _sqlConvert(self, value):
if value == '':
result = None
elif value == 'now':
result = 'now'
elif value:
result = time.strftime("%Y/%m/%d %H:%M:%S", value)
return result





------------------------------------------------------------------
Russell Adams RLAdams at AdamsInfoServ.com

PGP Key ID: 0x1160DCB3 http://www.adamsinfoserv.com/

Fingerprint: 1723 D8CA 4280 1EC9 557F 66E8 1154 E018 1160 DCB3
Loading...