-
Notifications
You must be signed in to change notification settings - Fork 32
Description
I could not get colanderalchemy to persist data from relations. I had a look at the source of
'schema.objectify()'
where schema is an instance of 'SQLAlchemySchemaNode'
I think i found three bugs in this code:
line 691:
cls = prop.mapper.class_
here cls is created but never used.
line 694/695:
value = [self[attr].children[0].objectify(obj)
for obj in dict_[attr]]
if objectify is called on an appstruct, where the corresponding value is already persisted,
this fails. objectify needs an context in this case.
The same holds true for
line 698:
value = self[attr].objectify(dict_[attr])
I tried to fix this. For this i redefined SQLAlchemySchemaNode:
import colander
import colanderalchemy
import deform
import logging
from sqlalchemy import inspect
log = logging.getLogger(__name__)
class Custom_colanderalchemy_SchemaNode(colanderalchemy.SQLAlchemySchemaNode):
'''only redefine objectify on this class'''
def objectify(self, dict_, context=None):
mapper = self.inspector
# if context exists, there is usually a session
# to which it is assigned. Get this session:
if context:
state = inspect(context)
session = state.session if state.session else None
else:
session = None
context = mapper.class_()
for attr in dict_:
if mapper.has_property(attr):
prop = mapper.get_property(attr)
if hasattr(prop, 'mapper'):
# this is a relation?
# get the primary_key of the class
# that belongs to attr
# todo: handle class without primary_key
cls = prop.mapper.class_
pk = [v.key for v in inspect(cls).primary_key]
if prop.uselist:
# Sequence of objects
# all children have the same subschema
subschema = self[attr].children[0]
value = []
for obj in dict_[attr]:
# get the identity from appstruct
obj_identity = obj.get(*pk, None)
if obj_identity:
# this obj exists in session
# we need to objectify with the
# corresponding obj_context
obj_context = session.query(cls).get(obj_identity)
else:
# create new instance of related object
# to add to session
obj_context = None
value.append(subschema.objectify(obj, obj_context))
else:
# Single object
subschema = self[attr]
# get the identity from appstruct
obj_identity = obj.get(*pk, None)
if obj_identity:
# this obj exists in session
# we need to objectify with the
# corresponding obj_context
obj_contex = session.query(cls).get(obj_identity)
else:
# create new instance of related object
# to add to session
obj_context = None
value = subschema.objectify(dict_[attr], obj_contex)
else:
value = dict_[attr]
if value is colander.null:
# `colander.null` is never an appropriate
# value to be placed on an SQLAlchemy object
# so we translate it into `None`.
value = None
setattr(context, attr, value)
else:
# Ignore attributes if they are not mapped
log.debug(
'SQLAlchemySchemaNode.objectify: %s not found on '
'%s. This property has been ignored.',
attr, self
)
continue
return context
I am not really experienced in writing code (and writing bugreports). Maybe someone who knows this stuff better, can take a look at it. This is not very good tested, but it works for me. I only tried the case with a one-column primary key. Maybe this needs corretion for multiple-column primary keys.
Wolfgang