2015-04-02 77 views
3

我有一个使用SQLAlchemy的现有工作Flask应用程序。这个应用程序中的几个模型/表具有存储原始HTML的列,并且我想在列的setter中注入一个函数,以便传入的原始html得到“清理”。我想在模型中这样做,所以我不需要通过表单或路径代码来“清理这些数据”。Flask + SQLAlchemy - 用于修改列设置器的自定义元类(动态hybrid_property)

我现在已经做到这一点,像这样:

from application import db, clean_the_data 
from sqlalchemy.ext.hybrid import hybrid_property 
class Example(db.Model): 
    __tablename__ = 'example' 

    normal_column = db.Column(db.Integer, 
          primary_key=True, 
          autoincrement=True) 

    _html_column = db.Column('html_column', db.Text, 
          nullable=False) 

    @hybrid_property 
    def html_column(self): 
    return self._html_column 

    @html_column.setter 
    def html_column(self, value): 
    self._html_column = clean_the_data(value) 

这就像一个魅力 - 除了模型定义的_html_column名称是从未见过的,清洁函数被调用,并清理数据使用。万岁。

我当然可以在那里停下来,只是吃了不好处理的列,但为什么当你可以混淆metaclasses?

注意:以下所有假设“应用”是主要的瓶模块,并且它有两个孩子:“DB” - 在SQLAlchemy的手柄和“clean_the_data”功能来清理输入HTML。

因此,我着手尝试创建一个新的基类Model类,该类可以在创建类时发现需要清理的列,并且自动处理周围的东西,以便代替上述代码,可以执行类似这样的:

from application import db 
class Example(db.Model): 
    __tablename__ = 'example' 
    __html_columns__ = ['html_column'] # Our oh-so-subtle hint 

    normal_column = db.Column(db.Integer, 
          primary_key=True, 
          autoincrement=True) 

    html_column = db.Column(db.Text, 
          nullable=False) 

当然,挂羊头卖狗肉的使用元类的事情与SQLAlchemy的和瓶幕后的组合使这个比直截了当以下(也是为什么近匹配问题“自定义元类来创建混合性质在SQLAlchemy中“并不完全有帮助 - Flask也是如此)。我几乎在应用/型号以下/ __ init__.py变得有:

from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.ext.hybrid import hybrid_property 
# Yes, I'm importing _X stuff...I tried other ways to avoid this 
# but to no avail 
from flask_sqlalchemy import (Model as BaseModel, 
           _BoundDeclarativeMeta, 
           _QueryProperty) 
from application import db, clean_the_data 

class _HTMLBoundDeclarativeMeta(_BoundDeclarativeMeta): 
    def __new__(cls, name, bases, d): 
    # Move any fields named in __html_columns__ to a 
    # _field/field pair with a hybrid_property 
    if '__html_columns__' in d: 
     for field in d['__html_columns__']: 
     if field not in d: 
      continue 
     hidden = '_' + field 
     fget = lambda self: getattr(self, hidden) 
     fset = lambda self, value: setattr(self, hidden, 
              clean_the_data(value)) 
     d[hidden] = d[field] # clobber... 
     d[hidden].name = field # So we don't have to explicitly 
           # name the column. Should probably 
           # force a quote on the name too 
     d[field] = hybrid_property(fget, fset) 
     del d['__html_columns__'] # Not needed any more 
    return _BoundDeclarativeMeta.__new__(cls, name, bases, d) 

# The following copied from how flask_sqlalchemy creates it's Model 
Model = declarative_base(cls=BaseModel, name='Model', 
         metaclass=_HTMLBoundDeclarativeMeta) 
Model.query = _QueryProperty(db) 

# Need to replace the original Model in flask_sqlalchemy, otherwise it 
# uses the old one, while you use the new one, and tables aren't 
# shared between them 
db.Model = Model 

一旦这样设置,你的模型类可以看起来像:

from application import db 
from application.models import Model 

class Example(Model): # Or db.Model really, since it's been replaced 
    __tablename__ = 'example' 
    __html_columns__ = ['html_column'] # Our oh-so-subtle hint 

    normal_column = db.Column(db.Integer, 
          primary_key=True, 
          autoincrement=True) 

    html_column = db.Column(db.Text, 
          nullable=False) 

几乎作品,没有错误,数据被正确读取和保存等。除了hybrid_property的setter从未被调用。 getter是(我已经在两个打印语句中确认过),但是setter被完全忽略,因此清理函数从不会被调用。数据设置为 - 使用未清理的数据可以非常愉快地进行更改。

很明显,我还没有完全模拟我的动态版本中的静态代码版本,但我真的不知道问题出在哪里。据我所知,hybrid_property 应该注册setter就像它有吸气,但它不是。在静态版本中,setter被注册并使用得很好。

关于如何让最后一步工作的任何想法?

+0

为什么要使用元类和'__html_columns__ = [” html_column']'?你为什么不使用自定义类型?基本上你想要一个'db.Text'类型,增加清理逻辑。也许检查[增加现有类型](http://docs.sqlalchemy.org/en/latest/core/custom_types.html#augmenting-existing-types)文档? – 2015-04-02 14:19:07

回答

3

也许使用自定义类型?

from sqlalchemy import TypeDecorator, Text 

class CleanedHtml(TypeDecorator): 
    impl = Text 

    def process_bind_param(self, value, dialect): 
     return clean_the_data(value) 

然后,你可以写你的模式是这样的:

class Example(db.Model): 
    __tablename__ = 'example' 
    normal_column = db.Column(db.Integer, primary_key=True, autoincrement=True) 
    html_column = db.Column(CleanedHtml) 

更多的解释是文档在这里可供选择:http://docs.sqlalchemy.org/en/latest/core/custom_types.html#augmenting-existing-types

+0

该解决方案太干净了:)感谢 - 由于某种原因,这种解决方案甚至没有跨过我的想法。 我仍然想知道为什么我的代码不起作用,但现在这是学术。 – MDB 2015-04-07 03:56:56