我有一个使用SQLAlchemy的Flask应用程序。这个应用程序中的几个模型/表都有存储原始HTML的列,我想在列的setter上注入一个函数,以便“清除”传入的原始HTML。我想在模型中这样做,这样我就不必在表单或路线代码中到处撒“清除这些数据”。
我目前已经可以这样做了:
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名称,调用更干净的函数,并使用清理后的数据。好极了
我当然可以到此为止,只吃列的丑陋处理,但当你可以处理元类时,为什么要这样做呢?
笔记
:以下所有假设“application”是主要Flask模块,并且它包含两个子模块:“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和Flask幕后进行的诡计与元类的结合使这一点变得不那么直接(这也是为什么“自定义元类以在SQLAlcheme中创建混合属性”这一几乎匹配的问题没有太大帮助-Flask也会碍手碍脚)。在application/models/__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是(我已经用这两个语句中的print语句进行了确认),但setter被完全忽略,因此从未调用cleaner函数。数据
是
尽管设置了-使用未清理的数据进行的更改非常愉快。
显然,我还没有完全模拟动态版本中的静态版本的代码,但我真的不知道问题出在哪里
应该
注册setter就像它有getter一样,但它不是。在静态版本中,setter被注册并使用得很好。
关于如何完成最后一步,有什么想法吗?