2010-02-19 14 views
3

我在SQLAlchemy的AttributeExtension中遇到了一些麻煩。使用AttributeExtension自動更新非規範化屬性

實際上,我在Partent表中存儲了一個非規範化的總和屬性,因爲我經常需要它進行排序。但是,我希望該屬性在它的某個子項的值發生更改時得到更新。

不幸的是,AttributeExtension的set()方法永遠不會被調用,所以變化無法識別。使用更新父級的屬性設置器可能會工作,但我想知道如何正確使用SQLAlchemy的AttributeExtension(版本:0.6beta2)。

這裏是一個小(可運行)的代碼片斷這表明了問題:

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 

感謝您的幫助!
Christoph

回答

3

據我所見,您正在尋找child上的事件,但更改child.value。這樣的事情應該做的伎倆:

class ValueAttributeExtension(AttributeExtension): 
    ... 

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

EDIT-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 
+2

同意。你也可能想要關注(孩子)父母(從關係的backref,所以當它改變時,你也增加/減少該值)。 – van

+0

@van:感謝你的工作示例 – stephan

+0

謝謝stephan和許多人感謝示例代碼範!完美的作品:) – tux21b