首先,如您所知,您必須跟蹤對象內對象的更改,因爲SQLAlchemy無法知道更改的內部對象。所以,我們會說出來的用鹼可變對象的方式,我們可以使用兩種:
class MutableObject(Mutable, object):
@classmethod
def coerce(cls, key, value):
return value
def __getstate__(self):
d = self.__dict__.copy()
d.pop('_parents', None)
return d
def __setstate__(self, state):
self.__dict__ = state
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
self.changed()
class Path(MutableObject):
def __init__(self, style, bounds):
super(MutableObject, self).__init__()
self.style = style
self.bounds = bounds
class Bound(MutableObject):
def __init__(self, l, t, r, b):
super(MutableObject, self).__init__()
self.l = l
self.t = t
self.r = r
self.b = b
而且我們還需要跟蹤路徑列表上的變化,所以,我們不得不做出這樣一個可變對象。但是,當調用changed()方法時,通過將子對象傳播給父對象,可變跟蹤會更改子對象,並且SQLAlchemy中的當前實現似乎只將父對象指派給作爲屬性指定的對象,而不是序列的項目,像字典或列表。這是事情變得複雜的地方。我認爲列表項應該有列表本身作爲父項,但這不起作用的原因有兩個:首先,_parents弱項無法獲取關鍵字列表,其次,已更改()信號不會一直傳播到最頂端,所以我們只會將列表本身標記爲已更改。我不是100%確定這是多麼正確,但似乎要將列表的父項分配給每個項目,所以組對象在項目更改時會獲得flag_modified調用。這應該做到這一點。
class MutableList(Mutable, list):
@classmethod
def coerce(cls, key, value):
if not isinstance(value, MutableList):
if isinstance(value, list):
return MutableList(value)
value = Mutable.coerce(key, value)
return value
def __setitem__(self, key, value):
old_value = list.__getitem__(self, key)
for obj, key in self._parents.items():
old_value._parents.pop(obj, None)
list.__setitem__(self, key, value)
for obj, key in self._parents.items():
value._parents[obj] = key
self.changed()
def __getstate__(self):
return list(self)
def __setstate__(self, state):
self[:] = state
但是,這裏有最後一個問題。父母通過一個正在收聽「加載」事件的呼叫被分配,所以在初始化時,_父母字典是空的,孩子們什麼也沒有分配。我想也許有一些更清晰的方法可以通過監聽負載事件來做到這一點,但我認爲這樣做的骯髒方法是在檢索項目時重新指派父母,因此,請添加以下內容:
def __getitem__(self, key):
value = list.__getitem__(self, key)
for obj, key in self._parents.items():
value._parents[obj] = key
return value
最後,我們不得不使用上Group.paths是MutableList:
class Group(BaseModel):
__tablename__ = 'group'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
paths = db.Column(MutableList.as_mutable(types.PickleType))
而這一切你的測試代碼應工作:
g = Group(name='g1', paths=[Path('blah', Bound(1,1,2,3)),
Path('other_style', Bound(1,1,2,3)),])
session.add(g)
db.session.commit()
g.name = 'g2'
assert g in db.session.dirty
db.session.commit()
g.paths[0].style = 'something else'
assert g in db.session.dirty
坦率地說,我不知道它是多麼安全ge如果你不需要靈活的模式,你可能會更好地使用路徑和邊界的表和關係。
明智的答案,感謝您抽出時間完成所有工作 - 我明白這是一個非常不規範的案例。這是我如何看待它的工作的一般想法,但無法完全掌握細節。我需要仔細研究這一點,真正理解它 - 正如你所說,甚至可能不是一個好主意,即使走這條路。你如何解決清單問題的想法非常聰明,而且我不確定我會遇到什麼問題。再次感謝。當我不在手機上時,會給你一個獎勵:) –
謝謝。我很樂意提供幫助。 –
令人煩惱的是,我可以在這個問題上給出的最低獎金是200,因爲我以前在這裏有過賞金。我已經開始了一個完全無關的問題的賞金,我會在24小時內獎勵你。 –