2012-02-03 48 views
2

有一個info表,它與car表或suv表有關係。基於記錄數據的SqlAlchemy模型關聯

它在info.type字段中指定。

那麼如何基於記錄的類型數據創建關聯?

class Info(Base): 
    item_id = Column(ForeignKey('cars-or-suvs-table.id')) 
    type = Column(String()) 

class Car(Base): 
    - data - 

    info = relationship('Info', backref="car") 

class Suv(Base): 
    - data - 

    info = relationship('Info', backref="suv") 

編輯:我已經充滿了數據的表,所以我不能改變DB模式。

+0

每輛車應該與物品有一對一的關係還是一對多? – 2012-02-04 08:52:36

+0

這是汽車或suv和信息之間的一對一 – 2012-02-05 03:35:21

回答

2

既然你正在尋找不需要移動外鍵不同的表的解決方案,你可以試試這個方法:

import sqlalchemy 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy import Column, String, Integer 
from sqlalchemy.orm import sessionmaker, relationship 
from sqlalchemy import sql 
Base = declarative_base() 
engine = sqlalchemy.create_engine('sqlite:///:memory:') 
Session = sessionmaker(bind=engine) 
session = Session() 

class Info(Base): 
    __tablename__ = 'info' 
    id = Column(Integer(), primary_key=True) 
    type = Column(String()) 
    item_id = Column(Integer()) 

    @property 
    def item(self): 
     if self.type == 'car': 
      return self._car 
     elif self.type == 'suv': 
      return self._suv 
     return None 

    @item.setter 
    def item(self, value): 
     if value is not None: 
      self.item_id = value.id 
      self.type = value.__tablename__ 
     else: 
      self.item_id = None 

class Car(Base): 
    __tablename__ = 'car' 
    id = Column(Integer(), primary_key=True) 
    info = relationship(Info, primaryjoin=sql.and_(id == Info.item_id, Info.type == 'car'), foreign_keys=Info.item_id, uselist=False, backref='_car') 

class Suv(Base): 
    __tablename__ = 'suv' 
    id = Column(Integer(), primary_key=True) 
    info = relationship(Info, primaryjoin=sql.and_(id == Info.item_id, Info.type == 'suv'), foreign_keys=Info.item_id, uselist=False, backref='_suv') 

我改名Info.car因爲對Info._car。即使.type是'suv',_car也不可避免地會成爲一個虛假的汽車對象。

我已經將事件監聽器的內容保留了很簡單,但是您絕對可以根據我的其他答案調整所需的部分,以避免事件進入不一致的狀態。

1

在SQL中,一個外鍵必須被映射到一個特定的表,所以你需要把外鍵在「汽車」或「SUV」表指向「info.id」。

這可能是您所需要的矯枉過正,但這裏是解決這個問題的一種方法(假設你做實際上希望每個車到只有一個信息):

import sqlalchemy 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy import Column, String, Integer, ForeignKey 
from sqlalchemy.orm import relationship 
from sqlalchemy import event 
Base = declarative_base() 

class Info(Base): 
    __tablename__ = 'info' 
    id = Column(Integer(), primary_key=True) 
    type = Column(String()) 
    # NOTE: can't use backref='info' because we need the attributes defined 
    # directly on both classes so we can attach event listeners 
    car = relationship('Car', back_populates='info', uselist=False) 
    suv = relationship('Suv', back_populates='info', uselist=False) 

    @property 
    def item(self): 
     # could check self.type here if you wanted 
     return self.car or self.suv 

    @item.setter 
    def item(self, value): 
     if isinstance(value, Car): 
      self.car = value 
     elif isinstance(value, Suv): 
      self.suv = value 
     elif value is None: 
      self.car = None 
      self.suv = None 
     else: 
      raise ValueError("item must be Car or Suv") 

@event.listens_for(Info.car, 'set') 
def _car_set_event(target, value, oldvalue, initiator): 
    if value is not None: 
     target.type = 'car' 
     if target.suv: 
      target.suv = None 
    elif target.type == 'car': 
     target.type = None 

@event.listens_for(Info.suv, 'set') 
def _suv_set_event(target, value, oldvalue, initiator): 
    if value is not None: 
     target.type = 'suv' 
     if target.car: 
      target.car = None 
    elif target.type == 'suv': 
     target.type = None 

class Car(Base): 
    __tablename__ = 'car' 
    id = Column(Integer(), primary_key=True) 
    info_id = Column(Integer(), ForeignKey('info.id')) 
    info = relationship(Info, back_populates='car') 

@event.listens_for(Car.info, 'set') 
def _car_info_set_event(target, value, oldvalue, initiator): 
    if value is not None: 
     value.type = 'car' 

class Suv(Base): 
    __tablename__ = 'suv' 
    id = Column(Integer(), primary_key=True) 
    info_id = Column(Integer(), ForeignKey('info.id')) 
    info = relationship(Info, back_populates='suv') 

@event.listens_for(Suv.info, 'set') 
def _suv_info_set_event(target, value, oldvalue, initiator): 
    if value is not None: 
     value.type = 'suv' 

什麼事件偵聽器的複雜性讓你的是,類型自動管理,當你做這樣的事情:

car1.info = Info() 
assert (car1.info.type == 'car') 

info1 = car1.info 
info1.suv = suv1 
assert (car1.info is None) 
assert (info1.type == 'suv') 

如果您想讓Info.type,Info.car和Info.suv保持一致,您可以省略所有事件偵聽器函數。

爲CarInfo和SuvInfo設置單獨的對象和表也是一個非常合理的選擇,並且完全避免了所有這些複雜性。

+0

感謝您的詳細信息,但我不能更改當前的數據庫模式,我會嘗試從您的答案中找出它。再次感謝。 – 2012-02-05 03:40:20

相關問題