代码之家  ›  专栏  ›  技术社区  ›  tux21b

使用attributeextension自动更新非规范化属性

  •  3
  • tux21b  · 技术社区  · 15 年前

    我有点麻烦 AttributeExtension 圣灵降临节

    实际上,我在partent表中存储了一个非规范化的sum属性,因为我经常需要它来进行排序。但是,我希望在更改属性的某个子项的值时更新该属性。

    不幸的是,从未调用attributeextension的set()方法,因此无法识别更改。使用同样更新父级的属性setter可能会工作,但我想知道如何正确使用sqlachemy(版本:0.6beta2)的attributeextension。

    下面是一个小的(可运行的)代码片段,它演示了这个问题:

    from sqlalchemy import create_engine, Column, Integer, ForeignKey
    from sqlalchemy.orm import relation, scoped_session, sessionmaker, \
             AttributeExtension
    from sqlalchemy.ext.declarative import declarative_base
    
    engine = create_engine('sqlite:///:memory:', echo=True)
    session = scoped_session(sessionmaker(bind=engine, autoflush=True))
    Base = declarative_base()
    Base.query = session.query_property()
    
    class ChildrenAttributeExtension(AttributeExtension):
        active_history = True
    
        def append(self, state, child, initiator):
            parent = state.obj()
            parent.sum_of_children += child.value
            return child
    
        def remove(self, state, child, initiator):
            parent = state.obj()
            parent.sum_of_children -= child.value
    
        def set(self, state, child, oldchild, initiator):
            print 'set called' # gets never printed
            parent = state.obj()
            parent.sum_of_children += -oldchild.value + child.value
            return child
    
    
    class Child(Base):
        __tablename__ = 'child'
        id = Column(Integer, primary_key=True)
        parent_id = Column(Integer, ForeignKey('parent.id'), nullable=False)
        value = Column(Integer, nullable=False, default=0)
    
    
    class Parent(Base):
        __tablename__ = 'parent'
        id = Column(Integer, primary_key=True)
        sum_of_children = Column(Integer, nullable=False, default=0)
    
        children = relation('Child', backref='parent',
                extension=ChildrenAttributeExtension())
    
    Base.metadata.create_all(engine)
    
    # Add a parent
    p = Parent()
    session.add(p)
    session.commit()
    
    p = Parent.query.first()
    assert p.sum_of_children == 0
    
    
    # Add a child
    c = Child(parent=p, value=5)
    session.add(c)
    session.commit()
    
    p = Parent.query.first()
    assert p.sum_of_children == 5
    
    # Change a child
    c = Child.query.first()
    c.value = 3
    session.commit()  # extension.set() doesn't get called
    
    p = Parent.query.first()
    assert p.sum_of_children == 3 # Assertion fails
    

    谢谢你的帮助!
    克里斯托夫

    1 回复  |  直到 15 年前
        1
  •  3
  •   van    15 年前

    据我所知,你正在寻找 child 但改变 child.value . 这样的技巧应该可以做到:

    class ValueAttributeExtension(AttributeExtension):
      ...
    
    class Child(Base):
      ...
      value = ColumnProperty(Column(Integer, nullable=False, default=0), 
                             extension=ValueAttributeExtension()) 
    

    编辑1 :完整的工作示例如下:

    from sqlalchemy import create_engine, Column, Integer, ForeignKey
    from sqlalchemy.orm import relation, scoped_session, sessionmaker, AttributeExtension, ColumnProperty
    from sqlalchemy.ext.declarative import declarative_base
    
    engine = create_engine('sqlite:///:memory:', echo=False)
    session = scoped_session(sessionmaker(bind=engine, autoflush=True))
    Base = declarative_base()
    Base.query = session.query_property()
    
    class ValueAttributeExtension(AttributeExtension):
        active_history = True
    
        def append(self, state, child, initiator):
            assert False, "should not be called"
    
        def remove(self, state, child, initiator):
            assert False, "should not be called"
    
        def set(self, state, value, oldvalue, initiator):
            print 'set called', state.obj(), value, oldvalue
            child = state.obj()
            if not(child.parent is None):
                child.parent.sum_of_children += -oldvalue + value
            return value
    
    class ChildrenAttributeExtension(AttributeExtension):
        active_history = True
    
        def append(self, state, child, initiator):
            print 'append called', state.obj(), child
            parent = state.obj()
            parent.sum_of_children += child.value
            return child
    
        def remove(self, state, child, initiator):
            print 'remove called', state.obj(), child
            parent = state.obj()
            parent.sum_of_children -= child.value
    
        def set(self, state, child, oldchild, initiator):
            print 'set called', state, child, oldchild
            parent = state.obj()
            parent.parent.sum_of_children += -oldchild.value + child.value
            #parent.sum_of_children += -oldchild.value + child.value
            return child
    
    class Child(Base):
        __tablename__ = 'child'
        id = Column(Integer, primary_key=True)
        parent_id = Column(Integer, ForeignKey('parent.id'), nullable=False)
        value = ColumnProperty(Column(Integer, nullable=False, default=0),
                        extension=ValueAttributeExtension())
    
    class Parent(Base):
        __tablename__ = 'parent'
        id = Column(Integer, primary_key=True)
        sum_of_children = Column(Integer, nullable=False, default=0)
    
        children = relation('Child', backref='parent',
                            extension=ChildrenAttributeExtension())
    
    Base.metadata.create_all(engine)
    
    # Add a parent
    p = Parent()
    session.add(p)
    session.commit()
    
    p = Parent.query.first()
    assert p.sum_of_children == 0
    
    
    # Add a child
    c = Child(parent=p, value=5)
    session.add(c)
    session.commit()
    
    p = Parent.query.first()
    assert p.sum_of_children == 5
    
    # Change a child
    #c = Child.query.first()
    c.value = 3 # fixed bug: = instead of ==
    session.commit()  # extension.set() doesn't get called
    
    p = Parent.query.first()
    assert p.sum_of_children == 3 # Assertion is OK