import os, time
import wx
import wx.wizard as wiz
import dabo
import dabo.db, dabo.ui, dabo.biz
dabo.ui.loadUI("wx")
def makePageTitle(wizPg, title):
sizer = wx.BoxSizer(wx.VERTICAL)
wizPg.SetSizer(sizer)
title = wx.StaticText(wizPg, -1, title)
title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
sizer.Add(wx.StaticLine(wizPg, -1), 0, wx.EXPAND|wx.ALL, 5)
return sizer
class TitledPage(wiz.WizardPageSimple):
def __init__(self, parent, title):
wiz.WizardPageSimple.__init__(self, parent)
self.sizer = makePageTitle(self, title)
def onLeavePage(self, direction):
return True
def onEnterPage(self, direction):
pass
class PageIntro(TitledPage):
def __init__(self, parent, title):
super(PageIntro, self).__init__(parent, title)
self.sizer.Add(wx.StaticText(self, -1, """
Use this wizard to quickly create an application
for your database. Point the wizard at a database;
specify a target directory; the wizard will create
a full-fledged Dabo application with forms for each
table in the database that allow you to run queries
and edit the results.
You can then edit the resulting application to fit
your exact requirements. There is a tool included
called 'FieldSpecEditor.py' that allows you to edit your
application settings visually.
Right now Dabo supports both MySQL and Firebird
databases. In time the number of supported databases
will grow.
Press 'Next' to enter database parameters.
"""))
class PageDatabase(TitledPage):
def __init__(self, parent, title):
super(PageDatabase, self).__init__(parent, title)
self.sizer.Add(wx.StaticText(self, -1, """
Enter the parameters here, and then click 'Next'.
"""))
self.dbPanel = dabo.ui.dPanel(self)
self.dbPanel.SetSizer(wx.BoxSizer(wx.VERTICAL))
self.sizer.Add(self.dbPanel, 1, wx.EXPAND, 0)
lbl = dabo.ui.dLabel(self.dbPanel, style=wx.ALIGN_RIGHT|wx.ST_NO_AUTORESIZE)
lbl.Name = "lblChoice"
lbl.Width = 75
lbl.Caption = "DB Type:"
self.dbChoice = wx.Choice(self.dbPanel, -1, choices=["MySQL", "Firebird"])
self.dbChoice.SetName("dbChoice")
self.dbChoice.Bind(wx.EVT_CHOICE, self.onDbChoice)
self.dbChoice.SetStringSelection("MySQL")
hs = wx.BoxSizer(wx.HORIZONTAL)
hs.Add(lbl)
hs.Add(self.dbChoice)
self.dbPanel.GetSizer().Add(hs, 0, wx.EXPAND)
self.dbDefaults = {}
self.dbDefaults["MySQL"] = {
"Host" : "leafe.com",
"Database" : "webtest",
"User" : "webuser",
"Password" : "foxrocks",
"Port" : "3306" }
self.dbDefaults["Firebird"] = {
"Host" : "leafe.com",
"Database" : "webtest",
"User" : "webuser",
"Password" : "foxrox",
"Port" : "3050" }
for field in ("Host", "Database", "User", "Password", "Port"):
lbl = dabo.ui.dLabel(self.dbPanel, style=wx.ALIGN_RIGHT|wx.ST_NO_AUTORESIZE)
lbl.Name = "lbl%s" % field
lbl.Width = 75
lbl.Caption = "%s:" % field
pw = (field.lower() == "password")
obj = dabo.ui.dTextBox(self.dbPanel, password=pw)
obj.Name = "txt%s" % field
hs = wx.BoxSizer(wx.HORIZONTAL)
hs.Add(lbl)
hs.Add(obj, 1, wx.ALL, 0)
self.dbPanel.GetSizer().Add(hs, 0, wx.EXPAND)
self.onDbChoice()
def onDbChoice(self, evt=None):
self.GetParent().dbType = self.dbChoice.GetStringSelection()
for k,v in self.dbDefaults[self.GetParent().dbType].items():
exec("self.dbPanel.txt%s.Value = '%s' " % (k,v) )
def onLeavePage(self, direction):
if direction == "forward":
if len(self.GetParent().tableDict) > 0:
if not dabo.ui.dMessageBox.areYouSure("Overwrite the current table information?"):
return True
# Set the wizard's connect info based on the user input:
ci = self.GetParent().connectInfo
ci.BackendName = self.dbPanel.dbChoice.GetStringSelection()
ci.Host = self.dbPanel.txtHost.Value
ci.DbName = self.dbPanel.txtDatabase.Value
ci.User = self.dbPanel.txtUser.Value
ci.Password = ci.encrypt(self.dbPanel.txtPassword.Value)
try:
ci.Port = int(self.dbPanel.txtPort.Value)
except ValueError:
ci.Port = None
# Try to get a connection::
try:
connection = ci.getConnection()
except:
dabo.ui.dMessageBox.stop("Could not connect to the database server. "
"Please check your parameters and try again.")
return False
# Define a Dabo cursor to interact with:
dictCursorClass = ci.BackendObject.getDictCursorClass()
class dCursor(dabo.db.dCursorMixin, dictCursorClass):
def __init__(self):
dabo.db.dCursorMixin.__init__(self)
dictCursorClass.__init__(self, connection)
self.superCursor = dictCursorClass
self.BackendObject = ci.BackendObject
cursor = dCursor()
tables = cursor.getTables()
self.GetParent().tableDict = {}
tableOrder = 0
for table in tables:
# Firebird databases have system tables with '$' in the name
if table.find("$") > -1:
continue
tableDict = {}
count = cursor.getTableRecordCount(table)
tableDict["name"] = table
tableDict["recordCount"] = count
tableDict["order"] = tableOrder
fields = cursor.getFields(table)
tableDict["fields"] = {}
fieldOrder = 0
for field in fields:
fieldName = field[0]
tableDict["fields"][fieldName] = {}
tableDict["fields"][fieldName]["name"] = fieldName
tableDict["fields"][fieldName]["type"] = field[1]
tableDict["fields"][fieldName]["order"] = fieldOrder
tableDict["fields"][fieldName]["pk"] = field[2]
fieldOrder += 1
self.GetParent().tableDict[table] = tableDict
tableOrder += 1
connection.close()
return True
class PageTableSelection(TitledPage):
def __init__(self, parent, title):
super(PageTableSelection, self).__init__(parent, title)
self.chks = []
self.sizer.Add(wx.StaticText(self, -1, """
The connection to the database was successful.
The following tables were found for that database.
Please check all tables you want included in
your application.
"""))
def onEnterPage(self, direction):
if direction == "forward":
tbls = self.GetParent().getTables()
if self.chks:
for i in range(len(self.chks)-1, -1, -1):
self.chks[i].Destroy()
self.chks = []
for tb in tbls:
chk = dabo.ui.dCheckBox(self)
chk.Caption = tb
chk.Value = False
self.chks.append(chk)
self.sizer.Add(chk, 0, wx.EXPAND)
self.sizer.Layout()
def onLeavePage(self, direction):
if direction == "forward":
self.GetParent().selectedTables = [chk.Caption for chk in self.chks if chk.Value]
if not self.GetParent().selectedTables:
dabo.ui.dMessageBox.stop("No tables were selected. " +
"Please select the tables you want to include in your application",
title="No Tables Selected")
return False
return True
import wx.lib.mixins.listctrl as listmix
class PropListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
""" Copied from the wxDemo. The mixin allows the rightmost
column to expand as the control is resized.
"""
def __init__(self, parent, ID=-1, pos=wx.DefaultPosition,
size=wx.DefaultSize, style= wx.LC_REPORT |
wx.SUNKEN_BORDER | wx.LC_VRULES):
wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
listmix.ListCtrlAutoWidthMixin.__init__(self)
def SetColumns(self, pageType):
if pageType == "Search":
self.InsertColumn(0, "Field", format=wx.LIST_FORMAT_LEFT, width=80)
self.InsertColumn(1, "Show?", format=wx.LIST_FORMAT_CENTER, width=50)
self.InsertColumn(2, "Caption", format=wx.LIST_FORMAT_LEFT, width=100)
elif pageType == "Results":
self.InsertColumn(0, "Field", format=wx.LIST_FORMAT_LEFT, width=80)
self.InsertColumn(1, "Show?", format=wx.LIST_FORMAT_CENTER, width=50)
self.InsertColumn(2, "Caption", format=wx.LIST_FORMAT_LEFT, width=100)
elif pageType == "Edit":
self.InsertColumn(0, "Field", format=wx.LIST_FORMAT_LEFT, width=80)
self.InsertColumn(1, "Show?", format=wx.LIST_FORMAT_CENTER, width=50)
self.InsertColumn(2, "Read-Only?", format=wx.LIST_FORMAT_LEFT, width=50)
self.InsertColumn(3, "Caption", format=wx.LIST_FORMAT_CENTER, width=1000)
class PageFieldSpecs(TitledPage):
def __init__(self, parent, title):
super(PageFieldSpecs, self).__init__(parent, title)
self.lb = dabo.ui.dPageFrame(self)
self.lb.TabPosition = "Top"
self.GetSizer().Add(self.lb, 1, wx.EXPAND)
def onEnterPage(self, direction):
if direction == "forward":
# Clear any existing pages
self.lb.PageCount = 0
tbls = self.GetParent().selectedTables
self.lb.PageCount = len(tbls)
self.lb.Refresh()
for i in range(len(tbls)):
pg = self.lb.GetPage(i)
# vsz = wx.BoxSizer(wx.VERTICAL)
vsz = pg.GetSizer()
pg.SetSizer(vsz)
pg.Caption = tbls[i]
pgf = dabo.ui.dPageFrame(pg)
pgf.PageCount = 3
pg0 = pgf.GetPage(0)
pg0.Caption = "Searching"
prop0 = PropListCtrl(pg0)
prop0.SetColumns("Search")
pg1 = pgf.GetPage(1)
pg1.Caption = "Results"
prop1 = PropListCtrl(pg1)
prop1.SetColumns("Results")
prop1.Show(True)
pg2 = pgf.GetPage(2)
pg2.Caption = "Editing"
prop2 = PropListCtrl(pg2)
prop2.SetColumns("Edit")
vsz.Add(pgf, 0, wx.EXPAND)
self.sizer.Layout()
class PageTargetDirectory(TitledPage):
def __init__(self, parent, title):
super(PageTargetDirectory, self).__init__(parent, title)
self.sizer.Add(wx.StaticText(self, -1, """
Enter the directory where you wish to place your
new application.
You can always move the directory later.
"""))
hs = wx.BoxSizer(wx.HORIZONTAL)
self.txtDir = dabo.ui.dTextBox(self)
self.txtDir.FontSize=10
self.txtDir.Value = ""
hs.Add(self.txtDir, 1)
self.cmdPick = dabo.ui.dCommandButton(self)
self.cmdPick.Caption = "..."
self.cmdPick.Width = 30
self.cmdPick.Height = self.txtDir.Height
hs.Add(self.cmdPick, 0)
self.sizer.Add(hs, 1, wx.EXPAND)
self.cmdPick.Bind(wx.EVT_BUTTON, self.onPick)
def onPick(self, evt):
dlg = wx.DirDialog(self, "Choose a directory:",
defaultPath = self.txtDir.Value,
style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON)
if dlg.ShowModal() == wx.ID_OK:
self.txtDir.Value = dlg.GetPath()
dlg.Destroy()
def onEnterPage(self, direction):
if direction == "forward":
if self.txtDir.Value == "":
self.txtDir.Value = os.path.abspath( os.path.join( os.getcwd(),
self.GetParent().connectInfo.DbName) )
def onLeavePage(self, direction):
if direction == "forward":
directory = self.txtDir.Value
if not os.path.exists(directory):
if dabo.ui.dMessageBox.areYouSure(
"The target directory %s does not exist. Do you want to create it now?" % (
directory,)):
os.mkdir(directory)
else:
return False
self.GetParent().outputDirectory = directory
return True
class PageGo(TitledPage):
def __init__(self, parent, title):
super(PageGo, self).__init__(parent, title)
self.sizer.Add(wx.StaticText(self, -1, """
Press 'Finish' to create your application, or
'Back' to edit any information."""))
def onLeavePage(self, direction):
if direction == "forward" and not self.GetParent().createApp():
return False
else:
dabo.ui.dMessageBox.info("""
Your application has been created. Please remember that the
intent of this is to give you, the database developer, the
ability to browse and edit all records in all tables of your
database.
As it stands now, there are no limits to what you can do with
the data: add records, delete records, modify records. In other
words, the application you just created IS DANGEROUS. Treat it
as a demo, and if you do use it, use it with care and consider
adding business rules and organizing your screens into
master/childview parent/child relationships.
I take no responsibility if your database gets hopelessly
mangled, but at the same time I hope you find this wizard useful.
To see your app in action, navigate to the target directory and
type 'python main.py' at the commandline.
pkm
""")
return True
class AppWizard(wiz.Wizard):
def __init__(self, parent=None):
wiz.Wizard.__init__(self, parent, -1, "Dabo Application Wizard",
dabo.ui.dIcons.getIconBitmap("daboIcon128"))
self.tableDict = {}
self.selectedTables = []
self.connectInfo = dabo.db.dConnectInfo()
self.dbType = "MySQL" # default to MySQL
self.Bind(wiz.EVT_WIZARD_PAGE_CHANGED, self.OnWizPageChanged)
self.Bind(wiz.EVT_WIZARD_PAGE_CHANGING, self.OnWizPageChanging)
self.Bind(wiz.EVT_WIZARD_CANCEL, self.OnWizCancel)
self.Bind(wiz.EVT_WIZARD_FINISHED, self.OnWizFinished)
pIntro = PageIntro(self, "Introduction")
pDatabase = PageDatabase(self, "Database Parameters")
pTableSel = PageTableSelection(self, "Table Selection")
# pFieldSpec = PageFieldSpecs(self, "Field Specifications")
pTargetDir = PageTargetDirectory(self, "Output Directory")
pCreate = PageGo(self, "Create Application")
self.page1 = pIntro
# self.GetPageAreaSizer().SetMinSize( (400, 1200) )
# Use the convenience Chain function to connect the pages
wiz.WizardPageSimple_Chain(pIntro, pDatabase)
wiz.WizardPageSimple_Chain(pDatabase, pTableSel)
wiz.WizardPageSimple_Chain(pTableSel, pTargetDir)
wiz.WizardPageSimple_Chain(pTargetDir, pCreate)
if self.RunWizard(self.page1):
print "Wizard completed."
else:
print "Wizard canceled."
self.Destroy()
def setNewSize(self, sz):
pgSzr = self.GetPageAreaSizer()
self.holdSize = pgSzr.GetMinSize()
pgSzr.SetMinSize( sz )
self.GetSizer().Layout()
def AdjustSize(self, sz=None ):
if sz is not None:
print "adjusting to:", sz
self.SetSize( sz )
print "new size:", self.GetSize()
def OnWizPageChanged(self, evt):
if evt.GetDirection():
dir = "forward"
else:
dir = "backward"
page = evt.GetPage()
page.onEnterPage(dir)
def OnWizPageChanging(self, evt):
if evt.GetDirection():
dir = "forward"
else:
dir = "backward"
page = evt.GetPage()
if not page.onLeavePage(dir):
evt.Veto()
def OnWizCancel(self, evt):
page = evt.GetPage()
def OnWizFinished(self, evt):
print "finished"
def getTables(self):
return self.tableDict.keys()
def getTableInfo(self):
ti = ""
td = self.tableDict
tables = td.keys()
tables.sort()
for table in tables:
count = td[table]["recordCount"]
ti = "".join((ti, "\n%s - %s record%s.\n" % (table, count, count==1 and "" or "s")))
fields = td[table]["fields"].keys()
fields.sort()
for field in fields:
ti = "".join((ti, " %s (%s) %s\n" % (td[table]["fields"][field]["name"],
td[table]["fields"][field]["type"],
td[table]["fields"][field]["pk"] == True and "PK" or "")))
return ti.strip()
def createApp(self):
directory = self.outputDirectory
if os.path.exists(directory):
td = self.tableDict
selTb = self.selectedTables
ci = self.connectInfo
oldDir = os.getcwd()
os.chdir(directory)
file = open("./main.py", "w")
formClassName = None
if len(self.selectedTables) == 1:
# Only one table; make it the main form for the app
formClassName = "frm" + self.selectedTables[0].title()
file.write(getMain(formClassName))
file.close()
file = open("./dbConnectionDefs.py", "w")
file.write(getDbConnectionDefs(ci))
file.close()
file = open("./myBizobjs.py", "w")
file.write(getMyBizobjs(td, selTb))
file.close()
file = open("./myFileOpenMenu.py", "w")
file.write(getMyFileOpenMenu(selTb))
file.close()
file = open("./myForms.py", "w")
file.write(getMyForms(td, selTb, ci, self.dbType))
file.close()
file = open("./fieldSpecs.xml", "w")
file.write(fieldSpecs(td, selTb))
file.close()
return True
else:
dabo.ui.dMessageBox.stop("The target directory does not exist. Cannot continue.")
return False
def getMain(mainForm=None):
ret = """import dabo, dabo.ui
import myForms
def main():
app = dabo.dApp()
%s
app.setup()
import myFileOpenMenu
myFileOpenMenu.fill(app.MainForm)
app.start()
if __name__ == "__main__":
main()
"""
if mainForm is None:
ret = ret.replace("\t%s\n", "")
else:
frmClass = "app.MainFormClass = myForms." + mainForm
ret = ret % frmClass
return ret
def getDbConnectionDefs(ci):
code = """def getDefs():
# dbConnectionDefs.py
# Created with the Dabo AppWizard on %s.
# Treat this as boilerplate code, and edit as needed.
# You can define any number of DB Connection definitions here.
# They will all be set up in dabo's app object for global availability.
# Set up the empty dictionary
dbConnectionDefs = {}
# For each Connection Definition you want, repeat the following
# block with different keys and values:
dbConnectionDefs["%s@%s"] = {
"dbType": "%s",
"host": "%s",
"user": "%s",
"password": "%s",
"dbName": "%s",
"port": %s}
return dbConnectionDefs
""" % (time.ctime(),
ci.User,
ci.Host,
ci.BackendName,
ci.Host,
ci.User,
ci.Password,
ci.DbName,
ci.Port)
return code
def getMyBizobjs(tableDict, tables):
code = """# myBizobjs.py
# Created with the Dabo AppWizard on %s.
# Treat this as boilerplate code, and edit as needed.
import dabo.biz as biz
import dabo.dException as dException
from dabo.dLocalize import _
# Each table has been assigned a bizobj, but it is up to
# you to provide any needed business rules. By default,
# every change to the data will be allowed.
""" % (time.ctime(),)
# tables = tableDict.keys()
tables.sort()
for table in tables:
# find the pk field, if any:
pkField = None
for field in tableDict[table]["fields"].keys():
if tableDict[table]["fields"][field]["pk"] == True:
pkField = "\"" + field + "\""
break
code = "".join((code, """class Biz%s(biz.dBizobj):
def beforeInit(self):
self.Caption = "%s"
self.DataSource = "%s"
self.KeyField = %s
self.RequeryOnLoad = False
def afterInit(self):
# Set the default values for new records added:
self.defaultValues = {}
def validateRecord(self):
# Initially set the error message to an empty string. If this method
# makes it all the way through with no biz rule violations, the string will
# still be empty and the bizobj will let the commit happen.
errorText = ""
# Biz rules follow. These can be as complex as necessary, the key
# thing is to remember to add to the error message so the user knows
# why a failure happened,.
# (your biz rules here)
# If we return an empty string, there were no validation problems.
return errorText
""" % (table.title(),
table.title(),
table,
pkField)))
return code
def getMyFileOpenMenu(tables):
code = """# myFileOpenMenu.py
# Created with the Dabo AppWizard on %s.
import dabo.ui
dabo.ui.loadUI("wx")
import myForms, wx
def fill(form):
menuBar = form.GetMenuBar()
actionList = form.Application.actionList
""" % time.ctime()
# tables = tableDict.keys()
tables.sort()
for table in tables:
code = "".join((code, "\tactionList.setAction(\"FileOpen%s\", open%s)\n" % (
table.title(), table.title())))
code = "".join((code, """
fileMenu = menuBar.GetMenu(0)
# The File/Open menu should be inserted before the File/Quit, but it doesn't
# work on Mac ('Open' appears as a menu item, not a submenu).
if wx.Platform == "__WXMAC__":
submenu = fileMenu.AppendMenu(-1, "&Open\tCtrl+O", FileOpenMenu(form))
else:
# On linux, it still just gets appended, but at least it becomes a submenu.
submenu = fileMenu.PrependMenu(-1, "&Open\tCtrl+O", FileOpenMenu(form))
class FileOpenMenu(dabo.ui.dMenu):
def __init__(self, form):
FileOpenMenu.doDefault(form)
"""))
for table in tables:
code = "".join((code, """
menuId = wx.NewId()
self.Append(menuId, "&%s")
form.Bind(wx.EVT_MENU, self.actionList.getAction("FileOpen%s")["func"],
id=menuId)
""" % (table.title(), table.title())))
code = "".join((code, "\n"))
for table in tables:
code = "".join((code, """
def open%s(event):
openForm(myForms.frm%s)
event.Skip()
""" % (table.title(), table.title())))
code = "".join((code, """
def openForm(classRef):
mainForm = wx.GetApp().GetTopWindow()
dApp = mainForm.Application
dForm = classRef(mainForm)
dForm.Show()
""" ))
return code
def getMyForms(tableDict, tables, ci, dbType):
code = """# myForms.py
# Created with the Dabo AppWizard on %s.
import dabo
import myBizobjs, myFileOpenMenu
dabo.ui.loadUI("wx")
### Define a base form class for the application:
class MyFormBase(dabo.ui.dDataNavForm):
def afterInit(self):
# The connect info was already loaded by dApp, but a connection instance is
# needed to pass to the bizobj's constructor.
self.connection = dabo.db.dConnection(self.Application.dbConnectionDefs["%s@%s"])
MyFormBase.doDefault()
def afterSetMenuBar(self):
myFileOpenMenu.fill(self)
### Each table gets its own form derived from MyFormBase:""" % (
time.ctime(), ci.User, ci.Host)
# tables = tableDict.keys()
tables.sort()
for table in tables:
code = "".join((code, """
class frm%s(MyFormBase):
def afterInit(self):
frm%s.doDefault()
self.Name = "frm%s"
self.Caption = "%s"
# Instantiate the bizobj defined in myBizobjs.py:
primaryBizobj = myBizobjs.Biz%s(self.connection)
# Register it with dForm:
self.addBizobj(primaryBizobj)
# Tell the dDataNavForm some things about the columns in the result set,
# so that it can do some automatic setup chores, such as filling in the
# browse grid, edit page, and select page. If you want to control what
# fields appear on the select/browse/edit pages, run the FieldSpecEditor
# program, and select the file named 'fieldSpecs.xml' in your app's
# directory.
self.setFieldSpecs("fieldSpecs.xml", "%s")
""" % (table.title(), table.title(), table.title(), table.title(), table.title(), table)))
return code
def fieldSpecs(tbDict, tbls):
ret = """