#1175: Enhance dCursorMixin to support (1) editable and (2) compound primary
keys
-------------------------------------------+--------------------------------
Reporter: aecker | Owner: somebody
Type: defect | Status: new
Priority: major | Milestone: 0.8.3
Component: ui | Version: 0.8.4
Keywords: editable multiple compound pk |
-------------------------------------------+--------------------------------
First I have to say "'THANK YOU!! ' * 1000"... for to publish dabo to the
community. Two weeks ago I found dabo via google because I had to make a
little prototype for a project offer. Luckily I could spent some time to
discover the dabo universe and I am so excited that I want help you where
I can to maintain and enhance this fantastic framework.
The prototype I had to make is for a management office where some hobby
programmers / database administrators need to merge statistics from a
group of companies. In the moment they enter and report on the data
directly within the database admin/management tool. This is the main
reason why they want to have meaningful and editable primary key values
instead of (autoincrement) numbers, on some of the tables with compound
PKs. When they buy it, I could publish all code to the community as an
example dabo application after the project is finished.
After I first created the database structure, run the dabo AppWizard and
then started the generated applicaten I was really impressed about the
functionality already built into dabo. All test data was showing fine, but
when I then started to edit or add new records some strange errors
occured. So I dived deeper...
First I got the current revision 4664 out of SVN, then tried to fix the
problems in the classes of my prototype application but it got ugly and I
had to use a lot of private attributes of dCursorMixin (like
cursor._mementos/._newRecords/._compoundKey). So finally I patched
dCursorMixin and the code got more easy to maintain and understand. Please
feel free to use this code into the code base and also to change and
critize to fit dabo code conventions. Although I love python I can use it
only rarely in my job (as Omnis Studio and .NET developer,..). so please
bare with my python coding.
== PATCHES FOR TO SUPPORT EDITABLE MULTIPLE PRIMARY KEY FIELDS ==
A) Changed in dCursorMixin.setFieldValue() the "if fld == keyField:" code
block (all until the respective "else:" code block) into this code:
{{{
if fld == keyField or fld in keyField:
# Changing the key field(s) value,
need to key the mementos on the new
# value, not the old.
Additionally, need to copy the mementos from the
# old key value to the new one.
if self._compoundKey:
old_keyFieldValue =
tuple([rec[k] for k in keyField])
keyFieldValue = tuple([val
if k == fld else rec[k] for k in keyField])
}}}
else:
{{{
old_keyFieldValue =
old_val
keyFieldValue = val
# new records pk must be unique
(at least within current dataset, filtered
# datasets will throw db error if
not unique)
if
(self._newRecords.has_key(old_keyFieldValue) \
and
self._getRowByPk(keyFieldValue) >= 0) \
or
self._newRecords.has_key(keyFieldValue):
#raise
dException.MissingPKException, _("The code/id/key value you entered
already exists in the data set. val=") + str(val)
return # best
simply RETURN silently without changing cursor, biz.validateField()
informs user (in most cases)
old_mem =
self._mementos.get(old_keyFieldValue, None)
if old_mem is not None:
self._mementos[keyFieldValue] = old_mem
del
self._mementos[old_keyFieldValue]
if
self._newRecords.has_key(old_keyFieldValue):
self._newRecords[keyFieldValue] = None
del
self._newRecords[old_keyFieldValue]
}}}
B) Inserted into dCursorMixin.getFieldVal() after the code line:
{{{
rec = self._records[row]
}}}
this code block to support mult. field pk:
{{{
if isinstance(fld, basestring) and fld.find(",") >= 0:
# convert also if multi PK KeyField value passed
from bizObj layer
fld = [f.strip() for f in fld.split(",")]
}}}
C) To warn user on duplicate pk I also had to add to Base.py bizobj class
(maybe patch/include into dBizObj, what do you think? ... and what would
be the right method/exception to notify the user from the framework
level):
{{{
def validateField(self, fld, val):
# If the new value is different from the current value, change it and
also
# update the mementos if necessary.
row = self._CurrentCursor.RowNumber # MOVE TO
dBizObj OR REPLACE PRIVATE ATTRIBUTES
rec = self._CurrentCursor._records[row] # ..rec = self.Record?!?!?
keyField = self._CurrentCursor.KeyField # ..used cursor
prop. because biz.KeyField is comma sep str?!?!?
old_val = rec[fld]
# ..old_val = rec.getAttr(fld)
if old_val != val:
if fld == keyField or fld in keyField:
# Changing the key field(s) value, need to key the mementos on the
new
# value, not the old. Additionally, need to copy the mementos from
the
# old key value to the new one.
if self._CurrentCursor._compoundKey: # ..if
isinstance(keyField, tuple):
old_keyFieldValue = tuple([rec[k] for k in keyField])
keyFieldValue = tuple([val if k == fld else rec[k] for k in
keyField])
else:
old_keyFieldValue = old_val
keyFieldValue = val
# new records pk must be unique (at least within current dataset)
if self._CurrentCursor._newRecords.has_key(old_keyFieldValue) \
and self._CurrentCursor._getRowByPk(keyFieldValue) >= 0:
return "Code/Id/PrimaryKey already exists in the dataset"
elif self._CurrentCursor._newRecords.has_key(keyFieldValue):
return "Duplicate Code/Id/PrimaryKey in another, new and unsaved
record. Please enter unique value or delete the other new/unsaved record."
return ""
}}}
With these patches the framework would support editable and
compound primary keys. I first want to discuss this with you before I
spend more time working this out, which could include also the following
features:
o add opt. fld+val arguments to dCursorMixin.pkExpression(self, rec=None,
fld=None, val=None) and virtually overwrite the return value (or part of
it) with passed val - to get a preview of the pk if val would be written
to the database column specified by fld.
o use extended method getPkExpression() in dCursorMixin.setFieldVal() and
in Base.py (or dBizObj.fieldValidation() after code transfer from
Base.py/validateField()) to minimize redundant code.
o While investigation more on compound primary keys I found other places
in dCursorMixin where compound primary keys are not supported. Not sure if
the following extension of RemoteConnector.save() to support multiple
field pk will not break the remote protocol.
The extensoion could be done by:
(1) in dCursorMixin.getRecordStatus()/_getNewRecordDiff() replace:
{{{
recs = [r for r in self._records if r[self._keyField] == pk]
}}}
with:
{{{
recs = [r for r in self._records if self.pkExpression(r) == pk]
}}}
(2) replace in dCursorMixin.getDataDiff().rowDiff():
{{{
ret[self._keyField] = pk
}}}
with:
{{{
if self._compoundKey:
rec = [r for r in self._records if
self.pkExpression(r) == pk][0]
for fld in self._keyField:
ret[fld] = rec[fld]
else:
ret[self._keyField] = pk
}}}
(3) in dCursorMixin.setDefaults() first remove the following local
variables at the top:
{{{
keyField = self.KeyField
keyFieldSet = False
}}}
then replace the last code part of the method:
{{{
if self._nullDefaults:
for field in rec.keys():
if field == keyField:
continue
self.setFieldVal(field, None)
else:
if keyField in vals.keys():
# Must set the pk default value first, for
mementos to be filled in
# correctly.
setDefault(keyField, vals[keyField])
keyFieldSet = True
for field, val in vals.items():
if field == keyField and keyFieldSet:
continue
setDefault(field, val)
}}}
with something like:
{{{
if self._compoundKey:
keyFields = [fld for fld in self.KeyField]
else:
keyFields = [self.KeyField]
if self._nullDefaults:
for field in rec.keys():
if field not in keyFields:
self.setFieldVal(field, None)
else:
# Must set the pk default value first, for
mementos to be filled in
# correctly.
for field in keyFields:
if field in vals:
setDefault(field, vals[field])
for field, val in vals.items():
if field not in keyFields:
setDefault(field, val)
}}}
(4) not sure if fld[2] in dCursorMixin._setTable() contains True for a pk
field in all DBs, if yes then we could replace:
{{{
self._keyField = [fld[0] for fld in
self.getFields(table)
if fld[2] ][0]
}}}
with:
{{{
kf = tuple([fld[0] for fld in
self.getFields(table)
if fld[2] ])
if len(kf) == 1:
kf = kf[0]
self._keyField = kf
}}}
Since long time I want to participate/support an open source project and
dabo is exactly what I am looking for. Unfortunately I have still no
internet access from my home ... far out, only from working place ... but
my best boss of the world allows us to stay late and use our equipment
privatly. I also will motivate my collueges as good as I can to use d a b
o .
--
Ticket URL: <http://trac.dabodev.com/ticket/1175>
Dabo Trac <http://trac.dabodev.com>
Trac Page for Dabo
_______________________________________________
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]