2016-01-07 71 views
0

我有一個父Employee表和一個Child Engineer表。從客戶角度來看,我只想與Employee模型進行交互。這對於READ和DELETE很容易實現,但在嘗試更新或插入時會出現問題。SQLAlchemy級聯多態列更新

sqlalchemy docs狀態:

警告

目前,只有一個識別器字段可以被設置爲,通常在分層結構中的基最類。 「級聯」多態列還不被支持。

所以看起來默認情況下這不起作用。我正在尋找如何完成這項工作的想法。

這是一個使用postgres和psycopg2的完整測試設置。 SQL可能與其他SQL數據庫一起工作,但我測試了其他任何其他SQL數據庫。

SQL腳本來創建測試數據庫(TESTDB)和表(員工,工程師):

CREATE DATABASE testdb; 
\c testdb; 

CREATE TABLE employee(
    id INT PRIMARY KEY NOT NULL, 
    name TEXT, 
    type TEXT 
); 

CREATE TABLE engineer(
    id   INT PRIMARY KEY NOT NULL, 
    engineer_name TEXT, 
    employee_id INT REFERENCES employee(id) 
    ON UPDATE CASCADE 
    ON DELETE CASCADE 
); 

的Python測試腳本:

AS-是INSERT測試將失敗,但DELETE將通過。如果您更改代碼(註釋/取消註釋)以使用子工程師模型,它將通過這兩種情況。

import sqlalchemy as sa 
import sqlalchemy.orm as orm 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy import (
    Column, 
    ForeignKey, 
    Integer, 
    Text, 
    ) 

Base = declarative_base() 

class Employee(Base): 
    __tablename__ = 'employee' 
    id = Column(Integer, primary_key=True) 
    name = Column(Text(), default='John') 
    type = Column(Text, default='engineer') 

    __mapper_args__ = { 
     'polymorphic_identity':'employee', 
     'polymorphic_on':type, 
     'with_polymorphic': '*', 
    } 

class Engineer(Employee): 
    __tablename__ = 'engineer' 
    id = Column(Integer, ForeignKey('employee.id', 
       ondelete='CASCADE', onupdate='CASCADE'), primary_key=True) 
    engineer_name = Column(Text(), default='Eugine') 

    __mapper_args__ = { 
     'polymorphic_identity':'engineer', 
    } 


def count(session, Model): 
    query = session.query(Model) 
    count = len(query.all()) 
    return count 

url = 'postgresql+psycopg2://[email protected]/testdb' 
engine = sa.create_engine(url) 
Base.metadata.bind = engine 
Base.metadata.create_all() 
Session = orm.sessionmaker(engine) 
session = Session() 

if __name__ == '__main__': 
    id=0 
    print '#'*30, 'INSERT', '#'*30 
    id += id 
    # I only want to interact with the Employee table 
    e = Employee(id=id) 
    # Use the child model to see the INSERT test pass 
    # e = Engineer(id=id) 
    session.add(e) 
    session.commit() 
    print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail' 

    print '#'*30, 'DELETE', '#'*30 
    # e = session.query(Employee).first() 
    session.delete(e); 
    session.commit(); 
    print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail' 

    session.flush() 

有關如何通過sqlalchemy模型定義完成此操作而不必使用顯式控制器代碼的任何想法?

謝謝!

編輯 嗯,我沒有得到任何這個愛。任何人有關於如何完成這個控制器代碼的想法?

回答

0

使用控制器邏輯,這可以通過getting the polymorphic subclass using the polymorphic identity完成。

我添加了兩個函數來封裝一些基本的邏輯。

def get_polymorphic_class(klass, data): 
    column = klass.__mapper__.polymorphic_on 
    if column is None: 
    # The column is not polymorphic so the Class can be returned as-is 
    return klass 
    identity = data.get(column.name) 
    if not identity: 
    raise ValueError('Missing value for "' + column.name + '"', data) 
    mapper = klass.__mapper__.polymorphic_map.get(identity) 
    if mapper: 
    return mapper.class_ 
    else: 
    raise ValueError('Missing polymorphic_identity definition for "' + identity + '"') 
    return klass 

def insert(klass, data): 
    klass = get_polymorphic_class(klass, data) 
    e = klass(**data) 
    session.add(e) 
    session.commit() 
    return e 

現在我更新main使用insert功能,一切都按預期工作:

if __name__ == '__main__': 
    id=0 
    print '#'*30, 'INSERT', '#'*30 
    id += id 
    e = insert(Employee, {'id': id, 'type': 'engineer'}) 
    print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail' 

    print '#'*30, 'DELETE', '#'*30 
    session.delete(e); 
    session.commit(); 
    print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail' 
    session.flush() 

有一個在我的封裝可重用一些額外的代碼,但重要的部分是做Employee.__mapper__.polymorphic_map['engineer'].class_它返回Engineer所以我們可以做一個適當的級聯INSERT。