This example is taken from the PyCon? tutorial, shortened and changed to use the new Many-to-Many relationship methods of dBizobj. It's just a demonstration of some of these methods, hopefully the most important, in a running, if not very nice or user-friendly application. Some diagnostic print statements are commented out, but left in.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# mmtest.py
# Test Many-to-Many relationships
import logging
import dabo
dabo.ui.loadUI("wx")
<h2>class BizCategories(dabo.biz.dBizobj):</h2>
def initProperties(self):
self.DataSource = "reccats"
self.KeyField = "id"
self.DataStructure = (
("id", "I", True, "reccats", "id"),
("descrp", "C", False, "reccats", "descrp"),
)
self.addFrom("reccats")
self.addField("reccats.id")
self.addField("descrp")
<h2>class BizRecipes(dabo.biz.dBizobj):</h2>
def initProperties(self):
self.DataSource = "recipes"
self.KeyField = "id"
self.DataStructure = (
("id", "I", True, "recipes", "id"),
("title", "C", False, "recipes", "title"),
("date", "D", False, "recipes", "date")
)
self.addFrom("recipes")
self.addField("recipes.id")
self.addField("title")
self.addField("date")
def afterInit(self):
# for SQLite
self.executeSafe("PRAGMA foreign_keys = ON")
# no separate bizobj class for the 'joiner' table reccat
class CklCategories(dabo.ui.dCheckList):
"""
nearly unchanged from the PyCon tutorial
"""
def initProperties(self):
self.Width = 200
self._needChoiceUpdate = True
def updateChoices(self):
self._needChoiceUpdate = False
(self.Choices, self.Keys) = self.Form.getCategoryChoicesAndKeys()
def update(self):
self.super()
dabo.ui.callAfterInterval(200, self.doUpdate)
def doUpdate(self):
if self._needChoiceUpdate:
self.updateChoices()
biz = self.Form.getBizobj("recipes")
mmbiz = self.Form.getBizobj("reccats")
keyvalues = []
# categories for the currently selected recipe
for mmval in biz.mmGetAssociatedValues(mmbiz, ["id"]):
keyvalues.append(mmval["id"])
self.KeyValue = keyvalues
# print "Categories for this recipe: %s" % keyvalues
def onHit(self, evt):
idx = evt.EventData["index"]
idxKey = self.Keys[idx]
# addCategory, delCategory now need value of field 'descrp', not PK
idxVal = self.Choices[idx]
# print "idx = %d, idxKey = %d, idxVal = %s" % (idx, idxKey, idxVal)
# print self.KeyValue
if idxKey in self.KeyValue:
self.Form.addCategory(idxVal)
else:
self.Form.delCategory(idxVal)
<h2>class FrmTest(dabo.ui.dForm):</h2>
def afterInit(self):
self.Sizer = gps = dabo.ui.dGridSizer(HGap=3, VGap=3, MaxCols=2)
pn = self.createGridPanel(self)
gps.append(pn, "expand")
pn = self.createCklPanel(self)
gps.append(pn, "expand")
pn = self.createTitlePanel(self)
gps.append(pn, "expand")
pn = self.createCatPanel(self)
gps.append(pn, "expand")
bt = dabo.ui.dButton(self, Caption="Save new", OnHit=self.saveNew)
gps.append(bt, "expand", colSpan=2)
gps.setColExpand(True, 0)
# gps.setColExpand
gps.setRowExpand(True, 0)
self.layout()
def afterInitAll(self):
self.requery()
self.grParentID.autoSizeCol("all")
self.grParentID.setFocus()
def createBizobjs(self):
conn = self.Application.getConnectionByName("recipesConnPK")
parentbiz = BizRecipes(conn)
self.addBizobj(parentbiz)
mmbiz = BizCategories(conn)
self.addBizobj(mmbiz)
# Set up the Many-to-Many relationship
parentbiz.addMMBizobj(mmbiz, "reccat", "recid", "catid")
def requeryCategory(self):
biz = self.getBizobj("reccats")
biz.UserSQL = "select * from reccats order by descrp"
biz.requery()
def getCategoryChoicesAndKeys(self, forceRequery=True):
""" Return two lists, one for all descrp values and one for all id values."""
cache = getattr(self, "_cachedCategories", None)
if not forceRequery and cache is not None:
return cache
(choices, keys) = ([], [])
biz = self.getBizobj("reccats")
if biz.RowCount <= 0 or forceRequery:
self.requeryCategory()
for rownum in biz.bizIterator():
choices.append(biz.Record.descrp)
keys.append(biz.Record.id)
self._cachedCategories = (choices, keys)
return self._cachedCategories
def addCategory(self, adescr, atitle=""):
mmbiz = self.getBizobj("reccats")
if atitle:
# New recipe
self.getBizobj("recipes").mmAddToBoth(mmbiz, "title", atitle, "descrp", adescr)
else:
self.getBizobj("recipes").mmAssociateValue(mmbiz, "descrp", adescr)
def delCategory(self, adescr):
mmbiz = self.getBizobj("reccats")
self.getBizobj("recipes").mmDissociateValues(mmbiz, "descrp", [adescr])
def saveNew(self, evt=None):
atitle = self.txTitleID.Value
adescr = self.txCatID.Value
# clear textboxes so the values aren't reused inadvertently
self.txTitleID.Value = ""
self.txCatID.Value = ""
if adescr:
# new category and possibly new recipe added, both related
self.addCategory(adescr, atitle)
self.requeryCategory()
else:
# only the recipe is new, no new category added
biz = self.getBizobj("recipes")
biz.new()
biz.setFieldVal("title", atitle)
self.save()
self.requery()
def createGridPanel(self, parent):
pn = dabo.ui.dPanel(parent)
pn.Sizer = vsp = dabo.ui.dSizer("v")
grParent = dabo.ui.dGrid(pn, AlternateRowColoring=True, ColumnCount=3,
SelectionMode="Row", RegID="grParentID", DataSource="recipes")
for (idx, (cpt, fld)) in enumerate([("ID", "id"), ("Title", "title"),
("Date", "date")]):
grParent.Columns[idx].Caption = cpt
grParent.Columns[idx].DataField = fld
vsp.append1x(grParent, border=10)
return pn
def createCklPanel(self, parent):
pn = dabo.ui.dPanel(parent)
pn.Sizer = vsp = dabo.ui.dSizer("v")
vsp.append1x(CklCategories(pn), border=10)
return pn
def createTitlePanel(self, parent):
pn = dabo.ui.dPanel(parent)
pn.Sizer = vsp = dabo.ui.dSizer("v")
vsp.append(dabo.ui.dLabel(pn, Caption="Title for new recipe"))
vsp.append(dabo.ui.dTextBox(pn, RegID="txTitleID"), "expand")
return pn
def createCatPanel(self, parent):
pn = dabo.ui.dPanel(parent)
pn.Sizer = vsp = dabo.ui.dSizer("v")
vsp.append(dabo.ui.dLabel(pn, Caption="New category"))
vsp.append(dabo.ui.dTextBox(pn, RegID="txCatID"), "expand")
return pn
if __name__ == "__main__":
app = dabo.dApp()
# just for showing what the new methods really do
dabo.dbConsoleLogHandler.setLevel(logging.DEBUG)
app.MainFormClass = FrmTest
app.start()