2013-07-09 67 views
3

我正在開發一個需要對創建的每個模型進行通用定製的項目。我迄今完成大部分工作的方式是通過模型繼承。這裏是我的代碼塊給你一個更好的主意:所有模型的SQLAlchemy事件after_create

app.core.dba.mixins:

class AuditExtension(MapperExtension): 
    """ 
    AuditExtension enforces the audit column values, and ensures any interaction with 
    SQLAlchemy cannot override the values 
    """ 

    def before_insert(self, mapper, connection, instance): 
     instance.created_dt = datetime.utcnow() 
     instance.created_by = audit_session_user() 

     instance.updated_dt = datetime.utcnow() 
     instance.updated_by = audit_session_user() 

    def before_update(self, mapper, connection, instance): 
     # Never update the created columns 
     instance.created_dt = instance.created_dt 
     instance.created_by = instance.created_by 

     instance.updated_dt = datetime.utcnow() 
     instance.updated_by = audit_session_user() 


class AuditColumns(object): 

    """ Generate the column schema for simple table level auditing. """ 
    created_dt = Column(DateTime, 
         default=datetime.utcnow(), 
         nullable=False) 
    created_by = Column(String(64), 
         #ForeignKey('operators.username', ondelete="RESTRICT"), 
         nullable=False) 

    updated_dt = Column(DateTime, 
         default=datetime.utcnow(), 
         nullable=False, 
         onupdate=datetime.utcnow()) 

    updated_by = Column(String(64), 
         #ForeignKey('operators.username', ondelete="RESTRICT"), 
         nullable=False) 

    __mapper_args__ = { 
     'extension': AuditExtension()} 

我的模型,然後繼承AuditColumns:

class ObjectTypes(Base, AuditColumns): 
    __tablename__ = 'object_types' 

    id = Column(BigInteger, primary_key=True) 
    name = Column(String, nullable=False, unique=True) 

    def __repr__(self): 
     return self.name 

我的問題是;只要操作包含在flask應用程序和SQLAlchemy中,我的解決方案就可以強制執行審計數據 - 這不會阻止任何具有數據庫訪問權限的用戶更新值。

因此,我現在需要在每個繼承AuditColumns的模型上實現一個觸發器。我發現這篇文章Sqlalchemy mixins/and event listener - 它描述了一個用於before_insert/update(我以前工作過)的方法,但不是用於「after_create」。

現在,我已經添加到了我的混入文件的代碼(後直奔我上面的審計代碼:

trig_ddl = DDL(""" 
      CREATE TRIGGER tr_audit_columns BEFORE INSERT OR UPDATE 
      ON test_table 
      FOR EACH ROW EXECUTE PROCEDURE 
      ss_test(); 
     """) 

event.listen(AuditColumns, 'after_create', trig_ddl) 

然而,當我運行測試用例:

Base.metadata.drop_all(db.get_engine(app)) 
Base.metadata.create_all(db.get_engine(app)) 

我得到的以下錯誤:

File "D:\Devel\flask-projects\sc2\app\core\dba\mixins.py", line 59, in <module> 
    event.listen(AuditColumns, 'after_create', trig_ddl) 
    File "D:\Devel\flask-projects\env\lib\site-packages\sqlalchemy\event.py", line 43, in listen 
    (identifier, target)) 
sqlalchemy.exc.InvalidRequestError: No such event 'after_create' for target '<class 'app.core.dba.mixins.AuditColumns'>' 

我猜這是因爲它還沒有表;但我將如何全局定義一個表創建的事件監聽器,它會執行這種類型的命令?

我知道我必須使trig_ddl動態(我認爲這不會太難,但我至少需要弄清楚這個全局元素)。

基本上,我不想讓人們在每個模型中手動編寫這個事件,當它與這些審計列明顯聯繫在一起時。

任何推向正確的方向將是偉大的。

+0

要完成所有更改都必須通過orm是完全不可行的嗎?試圖在應用程序中保持業務邏輯_並且數據庫看起來像是未來頭痛的祕訣。看起來完全合理的決定使用orm,保持應用程序層中的邏輯並禁止(或者非常小心)通過進程/訪問控制直接訪問數據庫 – second

+0

目前,我們直接使用數據庫訪問進行遷移,批量數據修正等。但可以想象的是,所有這些東西都可以通過未來在py/sqlalchemy中編寫的單元測試來強制執行...... – Trent

回答

6

那麼你就需要在這裏捎帶的事件,所以你可以在那個Table得到:

@event.listens_for(AuditColumns, "instrument_class", propagate=True) 
def instrument_class(mapper, class_): 
    if mapper.local_table is not None: 
     trigger_for_table(mapper.local_table) 

def trigger_for_table(table): 
    trig_ddl = DDL(""" 
       CREATE TRIGGER tr_%s_audit_columns BEFORE INSERT OR UPDATE 
       ON %s 
       FOR EACH ROW EXECUTE PROCEDURE 
       ss_test(); 
      """ % (table.name, table.name)) 

    event.listen(table, 'after_create', trig_ddl) 

AuditColumms任何子類映射,mapper.local_table將在那裏已經(將class.__table__,同樣的事情),你在那個時候應用DDL事件。

+0

好的,我已經閱讀了文檔,並從我可以通過首先聽取任何嘗試繼承AuditColumns的新模型 - 然後它使用映射器找出繼承擴展的表,然後調用trigger_for_table函數來簡單地生成DDL併爲該特定模型生成偵聽器?如果你對instrument_class有任何進一步的信息,那將是非常好的,因爲文檔有點模糊(或者可能有點太具體,以至於我不太瞭解它)......順便說一下 - 這是有效的;但我真的明白它爲什麼起作用。 – Trent

+1

instrument_class是一個事件,「只要將AuditColumns的子類映射到一個表」就可以做到這一點。如果你創建了一個在'__init __()'上做了某事的元類,當一個新的類被創建。 – zzzeek