Skip to content

use objectify to persist relation #101

@wolfgang-302

Description

@wolfgang-302

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions