2012-02-03 4 views
2

Il existe une table info et une relation avec la table car ou la table suv.SqlAlchemy Association de modèle basée sur les données d'enregistrement

Il est spécifié dans le champ info.type. Comment puis-je créer une association à la volée basée sur les données de type de l'enregistrement?

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") 

Edit: J'ai déjà les tables remplies de données, donc je ne peux pas changer le schéma db.

+0

Est-ce que chaque voiture devrait avoir une relation un-à-un avec Item, ou un one-to-many? –

+0

Il est un-à-un entre voiture-ou-suv et info –

Répondre

2

Puisque vous êtes à la recherche d'une solution qui ne nécessite pas le déplacement de la clé à une autre table, vous pouvez essayer cette approche:

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') 

Je rebaptisés Info.car à Info._car depuis. _car sera inévitablement un objet voiture fictif même si .type est 'suv'. J'ai laissé les choses de l'écouteur d'événement pour le garder simple, mais vous pouvez certainement adapter les morceaux dont vous avez besoin de mon autre réponse pour éviter des choses dans un état incohérent.

1

En SQL, une clé étrangère doit être mappée à une table spécifique, vous devez donc placer la clé étrangère dans la table 'car' ou 'suv' pointant sur 'info.id'.

Ceci est probablement exagéré pour ce que vous avez besoin, mais voici une façon de le résoudre (en supposant que vous faites en fait vouloir chaque voiture pour avoir une seule Info):

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' 

Ce que la complexité des écouteurs d'événements vous fait est que le type est géré automatiquement lorsque vous faites quelque chose comme:

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

ou

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

Si vous souhaitez conserver vous-même Info.type, Info.car et Info.suv, vous pouvez omettre toutes les fonctions de l'écouteur d'événements.

Il serait également très raisonnable d'avoir des objets et des tables séparés pour CarInfo et SuvInfo, et d'éviter toute cette complexité.

+0

Merci pour les informations détaillées, mais je ne peux pas changer le schéma de la base de données actuelle, je vais essayer de comprendre à partir de votre réponse. Merci encore. –

Questions connexes