专栏名称: 编程派
Python程序员都在看的公众号,跟着编程派一起学习Python,看最新国外教程和资源!
目录
相关文章推荐
Python中文社区  ·  比特币冲击10万美金!币圈美联储MSTR或是 ... ·  3 天前  
Python爱好者社区  ·  王者归来!《大模型最佳实践》开源了。。。 ·  5 天前  
Python爱好者社区  ·  北大韦东奕上课照片走红,板书潇洒,新发型吸睛 ... ·  1 周前  
Python爱好者社区  ·  11月,终于跨过了5W这个坎!! ·  6 天前  
51好读  ›  专栏  ›  编程派

Flask 插件学习:Flask-WTF 和 WTForms 扩展

编程派  · 公众号  · Python  · 2017-07-19 11:30

正文

继续学习 Flask 插件。。

作者:孤独的自我;原文:https://segmentfault.com/a/1190000004502589

Flask-WTF 和 Flask-SQLAlchemy 都是很好用的插件,然而当它们结合到一起后,就不是那么美妙了。

问题的提出

在 models.py 中定义了一个 Article、 Category 和 Tag 类:

  1. class Article(db.Model):

  2.    """定义文章"""

  3.    __tablename__ = 'articles'

  4.    id = db.Column(db.Integer, primary_key=True)

  5.    title = db.Column(db.String(128), unique=True, index=True)

  6.    # 保存 md 格式的文本

  7.    content = db.Column(db.Text)

  8.    # 保存 html 格式的文本

  9.    content_html = db.Column(db.Text)

  10.    # 文章分类

  11.    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))

  12.    # 文章标签

  13.    tags = db.relationship(

  14.        'Tag', secondary='article_tag_ref', backref='articles')

  15. class Category(db.Model):

  16.    """文章分类"""

  17.    __tablename__ = 'categories'

  18.    id = db.Column(db.Integer, primary_key=True)

  19.    name = db.Column(db.String(128), unique=True)

  20.    articles = db.relationship('Article', backref='category', lazy='dynamic')

  21. class Tag(db.Model):

  22.    """文章标签"""

  23.    __tablename__ = 'tags'

  24.    id = db.Column(db.Integer, primary_key=True)

  25.    name = db.Column(db.String(128), unique=True)

  26. # 文章和标签的映射表 ,多对多关系

  27. article_tag_ref = db.Table('article_tag_ref',

  28.                           db.Column('article_id', db.Integer,

  29.                                     db.ForeignKey('articles.id')),

  30.                           db.Column('tag_id',  db.Integer,

  31.                                     db.ForeignKey('tags.id'))

  32.                           )

然后在 forms.py 中定义一个 ArticleForm 表单

  1. class ArticleForm(Form):

  2.    title = StringField(u"标题", validators=[Required()])

  3.    category = QuerySelectField(u"分类", query_factory=getUserFactory(['id', 'name']), get_label='name')

  4.    tags = StringField(u"标签", validators=[Required()])

  5.    content = PageDownField(u"正文", validators=[Required()])

  6.    submit = SubmitField(u"发布")

此时在处理表单的时候可以这样:

  1. form = ArticleForm()

  2. if form.validate_on_submit():

  3.    article = Article(title=from.data.title, content=form.data.content,category=form.category.data)

  4.    ...

等等,这样怎么处理 form.data.tags?只有像下面这样写了:

  1. """

  2. :param tags:

  3.    标签列表,如 [u'测试',u'Flask']

  4. """

  5. def str_to_obj(tags):

  6.    r = []

  7.    for tag in tags:

  8.        tag_obj = Tag.query.filter_by(name=tag).first()

  9.        if tag_obj is None:

  10.            tag_obj = Tag(name=tag)

  11.        r.append(tag_obj)

  12.    return r

然后在上面的代码中加入:

  1. form = ArticleForm()

  2. if form.validate_on_submit():

  3.    article = Article(title=from.data.title, content=form.data.content, category=form.category.data, tags=str_to_obj(form.data.tags))  

这样是不是很难看,像 form.data.category 就是一个对象,为撒到 form.data.tags 了就不是了,还要专门写一个函数来坐一个转换?这个时候就有必要扩展 WTForms 中的表单了。 
 

WTForms 入门

阅读 WTForms 文档,关于如何创建一个 TagListField,贴一下代码:

  1. class TagListField(Field):

  2.    widget = TextInput()

  3.    def _value(self):

  4.        if self.data:

  5.            return u', '.join(self.data)

  6.        else:

  7.            return u''

  8.    def process_formdata(self, valuelist):

  9.        if valuelist:

  10.            self.data = [x.strip() for x in valuelist[0].split(',')]

  11.        else:

  12.            self.data = []

简单了看了一下 WTForms 源码,大致搞清楚了上面代码两个方法的作用:

  1. _value The _value method is called by the TextInput widget to provide the value that is displayed in the form. 在初始化表单的时候,就是调用这个方法在表单中渲染数据

  2. process_formdata 表单提交时,处理该字段的数据。

编写 WTForm 扩展

根据上面的代码,将 TagListField 中的字符串转为 models.py 中定义的 Tag 对象即可:

  1. class TagListField(Field):

  2.    widget = TextInput()

  3.    def __init__(self, label=None, validators=None,

  4.                 **kwargs):

  5.        super(TagListField, self).__init__(label, validators, **kwargs)

  6.    def _value(self):

  7.        if self.data:

  8.            r = u''

  9.            for obj in self.data:

  10.                r  = self.obj_to_str(obj)

  11.            return u''

  12.        else:

  13.            return u''

  14.    def process_formdata(self, valuelist):

  15.        print 'process_formdata..'

  16.        print valuelist

  17.        if valuelist:

  18.            tags = self._remove_duplicates([x.strip() for x in valuelist[0].split(',')])

  19.            self.data = [self.str_to_obj(tag) for tag in tags]

  20.        else:

  21.            self.data = None

  22.    def pre_validate(self, form):

  23.        pass

  24.    @classmethod

  25.    def _remove_duplicates(cls, seq):

  26.        """去重"""

  27.        d = {}

  28.        for item in seq:

  29.            if item.lower() not in d:

  30.                d[item.lower()] = True

  31.                yield item

  32.    @classmethod

  33.    def str_to_obj(cls, tag):

  34.        """将字符串转换位 obj 对象"""

  35.        tag_obj = Tag.query.filter_by(name=tag).first()

  36.        if tag_obj is None:

  37.            tag_obj = Tag(name=tag)

  38.        return tag_obj

  39.    @classmethod

  40.    def obj_to_str(cls, obj):

  41.        """将对象转换为字符串"""

  42.        if obj:

  43.            return obj.name

  44.        else:

  45.            return u''

主要就是在 process_formdata 这一步处理表单的数据,将字符串转换为需要的数据。最终就可以在 forms.py 中这样定义表单了:

  1. ...

  2. class ArticleForm(Form):

  3.    """编辑文章表单"""

  4.    title = StringField(u'标题', validators=[Required()])

  5.    category = QuerySelectField(u'分类', query_factory=get_category_factory(['id', 'name']), get_label='name')

  6.    tags = TagListField(u'标签', validators=[Required()])

  7.    content = PageDownField(u'正文', validators=[Required()])

  8.    submit = SubmitField(u'发布')

  9. ...

在 views.py 中处理表单就很方便了:

  1. def edit_article():

  2.    """编辑文章"""

  3.    form = ArticleForm()

  4.    if form.validate_on_submit():

  5.        article = Article(title=form.title.data, content=form.content.data)

  6.        article.tags = form.tags.data

  7.        article.category = form.category.data

  8.        try:

  9.            db.session.add(article)

  10.            db.session.commit()

  11.        except:

  12.            db.session.rollback()

  13.    return render_template('dashboard/edit.html', form=form)

代码是不是很简洁了?^_^。。。

当然了写一个完整的 WTForms 扩展还是很麻烦的。这里只是刚刚入门。可以看官方扩展 QuerySelectField 的源码。。。

最终效果


题图:pexels,CC0 授权。

点击阅读原文,查看更多 Python 教程和资源。