dabo Commit
Revision 6626
Date: 2011-06-16 05:37:40 -0700 (Thu, 16 Jun 2011)
Author: Ed
Trac: http://trac.dabodev.com/changeset/6626

Changed:
U   trunk/dabo/biz/dBizobj.py
U   trunk/dabo/db/dCursorMixin.py

Log:
Added basic methods for handling many-to-many (M-M) relationships. This is a 
first pass, as it limits a bizobj to a single M-M relationship. My plan is to 
move all of the MM-specific stuff into a separate class, so a bizobj can have 
more than one such relation in the same way that it can have more than one 
child bizobj. I've named the methods that are going to be refactored with an 
'mm' prefix. 

Here are the primary methods of the bizobj that are involved; most are simply 
pass-through to similar methods in the cursor layer.

    createAssociation(self, mmOtherTable, mmOtherPKCol, assocTable, 
assocPKColThis, assocPKColOther):
        This establishes the MM relationship. It takes several parameters:
            mmOtherTable: the name of the DataSource (table) for the other half 
of the MM relation
            mmOtherPKCol: the name of the PK column in the mmOtherTable
            assocTable: the name of the table holding the association between 
the two
            assocPKColThis: the name of the column in the association table for 
this PK
            assocPKColOther: the name of the column in the association table 
for the other PK 

    lookupPKWithAdd(self, field, val):
        Runs a lookup in the specified field for the desired value. If found, 
returns the PK for that record. If not found, a new record is created with the 
'field' column populated with 'val', and the new record's PK is returned. None 
of this affects the current dataset.
    
    mmAssociateValue(self, otherField, otherVal):
        This accepts a column and value in the "other" table, and creates an 
association to that record for the current record of the bizobj. If the value 
doesn't exist in the "other" table, it is added.
    
    mmDisssociateValue(self, otherField, otherVal):
        Removes any association between the current record of the bizobj and 
the record in the "other" table whose 'otherField' column matches 'otherVal'. 
If there is no such association, nothing happens.
    
    mmDisssociateAll(self):
        Removes all associations between the current record and the associated 
M-M table.
    
    mmSetFullAssociation(self, otherField, listOfValues):
        Establishes the full association between the current record of the 
bizobj and the "other" table. Any existing relationships that are not in 
'listOfValues' are removed.
    
    mmAddToBoth(self, thisField, thisVal, otherField, otherVal):
        Creates an association in a M-M relationship. If the relationship 
already exists, nothing changes. Otherwise, this will ensure that both values 
exist in their respective tables, and will create the entry in the association 
table.


Diff:
Modified: trunk/dabo/biz/dBizobj.py
===================================================================
--- trunk/dabo/biz/dBizobj.py   2011-06-12 13:31:07 UTC (rev 6625)
+++ trunk/dabo/biz/dBizobj.py   2011-06-16 12:37:40 UTC (rev 6626)
@@ -1803,6 +1803,15 @@
        setValues = setFieldVals  ## deprecate setValues in future version
 
 
+       def lookupPKWithAdd(self, field, val):
+               """Runs a lookup in the specified field for the desired value. 
If
+               found, returns the PK for that record. If not found, a new 
record is
+               created with the 'field' column populated with 'val', and the 
new
+               record's PK is returned. None of this affects the current 
dataset.
+               """
+               return self._CurrentCursor.lookupPKWithAdd(field, val)
+
+
        def dataToXML(self):
                """
                Returns XML representing the data set. If there are child 
bizobjs,
@@ -2036,16 +2045,63 @@
                return self._CurrentCursor.oldVal(fieldName, row)
 
 
+       def mmAssociateValue(self, otherField, otherVal):
+               """
+               Associates the value in the 'other' table of a M-M relationship 
with the
+               current record in the bizobj. If that value doesn't exist in 
the other
+               table, it is added.
+               """
+               self._CurrentCursor.mmAssociateValue(otherField, otherVal)
+
+
+       def mmDisssociateValue(self, otherField, otherVal):
+               """
+               Removes the association between the current record and the 
specified value
+               in the 'other' table of a M-M relationship. If no such 
association exists,
+               nothing happens.
+               """
+               self._CurrentCursor.mmDisssociateValue(otherField, otherVal)
+
+
+       def mmDisssociateAll(self):
+               """
+               Removes all associations between the current record and the 
associated
+               M-M table.
+               """
+               self._CurrentCursor.mmDisssociateAll()
+
+
+       def mmSetFullAssociation(self, otherField, listOfValues):
+               """
+               Adds and/or removes association records so that the current 
record in this
+               bizobj is associated with every item in listOfValues, and none 
other.
+               """
+               self._CurrentCursor.mmSetFullAssociation(otherField, 
listOfValues)
+
+
+       def mmAddToBoth(self, thisField, thisVal, otherField, otherVal):
+               """
+               Creates an association in a M-M relationship. If the 
relationship
+               already exists, nothing changes. Otherwise, this will ensure 
that
+               both values exist in their respective tables, and will create 
the 
+               entry in the association table.
+               """
+               return self._CurrentCursor.mmAddToBoth(thisField, thisVal, 
otherField, otherVal)
+
+
        ########## SQL Builder interface section ##############
        def addField(self, exp, alias=None):
                """Add a field to the field clause."""
                return self._CurrentCursor.addField(exp, alias)
+
        def addFrom(self, exp, alias=None):
                """
-               Add a table to the sql statement. For joins, use 
-               the addJoin() method.
+               Add a table to the sql statement. For 1-M joins, use 
+               the addJoin() method. For M-M joins, use the
+               createAssociation() method.
                """
                return self._CurrentCursor.addFrom(exp, alias)
+
        def addJoin(self, tbl, exp, joinType=None):
                """
                Add SQL JOIN clause.
@@ -2055,44 +2111,71 @@
                :param joinType: examples: "LEFT", "RIGHT", "INNER", "OUTER"
                """
                return self._CurrentCursor.addJoin(tbl, exp, joinType)
+
+       def createAssociation(self, mmOtherTable, mmOtherPKCol, assocTable, 
assocPKColThis,
+                       assocPKColOther):
+               """
+               Create a many-to-many association.
+
+               :param mmOtherTable: the name of the table for the other half 
of the MM relation
+               :param mmOtherPKCol: the name of the PK column in the 
mmOtherTable
+               :param assocTable: the name of the table holding the 
association between the two
+               :param assocPKColThis: the name of the column in the 
association table for this PK
+               :param assocPKColOther: the name of the column in the 
association table for the other PK 
+               """
+               return self._CurrentCursor.createAssociation(mmOtherTable, 
mmOtherPKCol, assocTable,
+                               assocPKColThis, assocPKColOther)
+
        def addGroupBy(self, exp):
                """Add an expression to the group-by clause."""
                return self._CurrentCursor.addGroupBy(exp)
+
        def addOrderBy(self, exp):
                """Add an expression to the order-by clause."""
                return self._CurrentCursor.addOrderBy(exp)
+
        def addWhere(self, exp, comp="and"):
                """Add a filter expression to the where clause."""
                return self._CurrentCursor.addWhere(exp, comp=comp)
+
        def getSQL(self):
                """Returns the SQL statement currently set in the backend."""
                return self._CurrentCursor.getSQL()
+
        def setFieldClause(self, clause):
                """Explicitly set the field clause. Replaces any existing field 
settings."""
                return self._CurrentCursor.setFieldClause(clause)
+
        def setFromClause(self, clause):
                """Explicitly set the from clause. Replaces any existing from 
settings."""
                return self._CurrentCursor.setFromClause(clause)
+
        def setJoinClause(self, clause):
                """Explicitly set the join clauses. Replaces any existing join 
settings."""
                return self._CurrentCursor.setJoinClause(clause)
+
        def setGroupByClause(self, clause):
                """Explicitly set the group-by clause. Replaces any existing 
group-by settings."""
                return self._CurrentCursor.setGroupByClause(clause)
+
        def getLimitClause(self):
                """Returns the current limit clause set in the backend."""
                return self._CurrentCursor.getLimitClause()
+
        def setLimitClause(self, clause):
                """Explicitly set the limit clause. Replaces any existing limit 
settings."""
                return self._CurrentCursor.setLimitClause(clause)
        # For simplicity's sake, create aliases
        setLimit, getLimit = setLimitClause, getLimitClause
+
        def setOrderByClause(self, clause):
                """Explicitly set the order-by clause. Replaces any existing 
order-by settings."""
                return self._CurrentCursor.setOrderByClause(clause)
+
        def setWhereClause(self, clause):
                """Explicitly set the where clause. Replaces any existing where 
settings."""
                return self._CurrentCursor.setWhereClause(clause)
+
        def prepareWhere(self, clause):
                """
                Calls the backend's pre-processing routine for improving 
efficiency
@@ -2100,29 +2183,33 @@
                nothing is done.
                """
                return self._CurrentCursor.prepareWhere(clause)
+
        def getFieldClause(self):
                """Returns the current field clause set in the backend."""
                return self._CurrentCursor.getFieldClause()
+
        def getFromClause(self):
                """Returns the current from clause set in the backend."""
                return self._CurrentCursor.getFromClause()
+
        def getJoinClause(self):
                """Returns the current join clause set in the backend."""
                return self._CurrentCursor.getJoinClause()
+
        def getWhereClause(self):
                """Returns the current where clause set in the backend."""
                return self._CurrentCursor.getWhereClause()
+
        def getGroupByClause(self):
                """Returns the current group-by clause set in the backend."""
                return self._CurrentCursor.getGroupByClause()
+
        def getOrderByClause(self):
                """Returns the current order-by clause set in the backend."""
                return self._CurrentCursor.getOrderByClause()
        ########## END - SQL Builder interface section ##############
 
 
-
-
        def _makeHookMethod(name, action, mainDoc=None, additionalDoc=None):
                mode = name[:5]
                if mode == "befor":

Modified: trunk/dabo/db/dCursorMixin.py
===================================================================
--- trunk/dabo/db/dCursorMixin.py       2011-06-12 13:31:07 UTC (rev 6625)
+++ trunk/dabo/db/dCursorMixin.py       2011-06-16 12:37:40 UTC (rev 6626)
@@ -29,6 +29,13 @@
                self._initProperties()
                if sql and isinstance(sql, basestring) and len(sql) > 0:
                        self.UserSQL = sql
+               # Attributes used for M-M relationships
+               # Temporary! until the refactoring
+               self._mmOtherTable = None
+               self._mmOtherPKCol = None
+               self._assocTable = None
+               self._assocPKColThis = None
+               self._assocPKColOther = None
 
                #self.super()
                #super(dCursorMixin, self).__init__()
@@ -475,6 +482,12 @@
                ac.IsPrefCursor = self.IsPrefCursor
                ac.KeyField = self.KeyField
                ac.Table = self.Table
+               # Temporary! until the refactoring
+               ac._mmOtherTable = self._mmOtherTable
+               ac._mmOtherPKCol = self._mmOtherPKCol
+               ac._assocTable = self._assocTable
+               ac._assocPKColThis = self._assocPKColThis
+               ac._assocPKColOther = self._assocPKColOther
 
 
        def requery(self, params=None):
@@ -1117,6 +1130,93 @@
                        return True
 
 
+       def lookupPKWithAdd(self, field, val, tbl=None):
+               """Runs a lookup in the specified field for the desired value. 
If
+               found, returns the PK for that record. If not found, a record is
+               inserted into the table, with its 'field' column populated with 
'val',
+               and the new PK is returned. None of this affects the current 
dataset.
+               """
+               aux = self.AuxCursor
+               if tbl is None:
+                       tbl = self.Table
+               sql = "select %s from %s where %s = ?" % (self.KeyField, tbl, 
field)
+               aux.execute(sql, (val,))
+               if aux.RowCount:
+                       return aux.getPK()
+               else:
+                       # Add the record
+                       sql = "insert into %s (%s) values (?)" % (tbl, field)
+                       aux.execute(sql, (val,))
+                       return aux.getLastInsertID()
+
+
+       def mmAssociateValue(self, otherField, otherVal):
+               """
+               Associates the value in the 'other' table of a M-M relationship 
with the
+               current record. If that value doesn't exist in the other table, 
it is added.
+               """
+               return self.mmAddToBoth(self.KeyField, self.getPK(), 
otherField, otherVal)
+
+
+       def mmDisssociateValue(self, otherField, otherVal):
+               """
+               Removes the association between the current record and the 
specified value
+               in the 'other' table of a M-M relationship. If no such 
association exists,
+               nothing happens.
+               """
+               thisTable = self.Table
+               otherTable = self._mmOtherTable
+               thisPK = self.lookupPKWithAdd(thisField, thisVal, thisTable)
+               otherPK = self.lookupPKWithAdd(otherField, otherVal, otherTable)
+               aux = self.AuxCursor
+               sql = "delete from %s where %s = ? and %s = ?" % 
(self._assocTable,
+                               self._assocPKColThis, self._assocPKColOther)
+               aux.execute(sql, (thisPK, otherPK))
+
+
+       def mmDisssociateAll(self):
+               """
+               Removes all associations between the current record and the 
associated
+               M-M table.
+               """
+               aux = self.AuxCursor
+               sql = "delete from %s where %s = ?" % (self._assocTable, 
self._assocPKColThis)
+               aux.execute(sql, (self.getPK(),))
+
+
+       def mmSetFullAssociation(self, otherField, listOfValues):
+               """
+               Adds and/or removes association records so that the current 
record
+               is associated with every item in listOfValues, and none other.
+               """
+               self.mmDisssociateAll()
+               keyField = self.KeyField
+               pk = self.getPK()
+               for val in listOfValues:
+                       self.mmAddToBoth(keyField, pk, otherField, val)
+
+
+       def mmAddToBoth(self, thisField, thisVal, otherField, otherVal):
+               """
+               Creates an association in a M-M relationship. If the 
relationship
+               already exists, nothing changes. Otherwise, this will ensure 
that
+               both values exist in their respective tables, and will create 
the 
+               entry in the association table.
+               """
+               thisTable = self.Table
+               otherTable = self._mmOtherTable
+               thisPK = self.lookupPKWithAdd(thisField, thisVal, thisTable)
+               otherPK = self.lookupPKWithAdd(otherField, otherVal, otherTable)
+               aux = self.AuxCursor
+               sql = "select * from %s where %s = ? and %s = ?" % 
(self._assocTable,
+                               self._assocPKColThis, self._assocPKColOther)
+               aux.execute(sql, (thisPK, otherPK))
+               if not aux.RowCount:
+                       sql = "insert into %s (%s, %s) values (?, ?)" % 
(self._assocTable,
+                                       self._assocPKColThis, 
self._assocPKColOther)
+                       aux.execute(sql, (thisPK, otherPK))
+
+
        def getRecordStatus(self, row=None, pk=None):
                """
                Returns a dictionary containing an element for each changed
@@ -2318,7 +2418,28 @@
                return self.sqlManager._joinClause
 
 
+       def createAssociation(self, mmOtherTable, mmOtherPKCol, assocTable, 
assocPKColThis, assocPKColOther):
+               """Create a many-to-many association."""
+               # Save locally
+               # Temporary! until the refactoring
+               self._mmOtherTable = mmOtherTable
+               self._mmOtherPKCol = mmOtherPKCol
+               self._assocTable = assocTable
+               self._assocPKColThis = assocPKColThis
+               self._assocPKColOther = assocPKColOther
 
+               if self.sqlManager.BackendObject:
+                       thisJoin = "%s.%s = %s.%s" % (self.Table, 
self.KeyField, assocTable, assocPKColThis)
+                       otherJoin = "%s.%s = %s.%s" % (mmOtherTable, 
mmOtherPKCol, assocTable, assocPKColOther)
+                       self.sqlManager._joinClause = 
self.sqlManager.BackendObject.addJoin(assocTable,
+                                       thisJoin, self.sqlManager._joinClause, 
"LEFT",
+                                       autoQuote=self.AutoQuoteNames)
+                       self.sqlManager._joinClause = 
self.sqlManager.BackendObject.addJoin(mmOtherTable,
+                                       otherJoin, self.sqlManager._joinClause, 
"LEFT",
+                                       autoQuote=self.AutoQuoteNames)
+               return self.sqlManager._joinClause
+
+
        def getWhereClause(self):
                """Get the where clause of the sql statement."""
                return self.sqlManager._whereClause



_______________________________________________
Post Messages to: [email protected]
Subscription Maintenance: http://leafe.com/mailman/listinfo/dabo-dev
Searchable Archives: http://leafe.com/archives/search/dabo-dev
This message: 
http://leafe.com/archives/byMID/[email protected]

Reply via email to