Flask-WTF进阶和WTForms扩展

Posted Python开发者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask-WTF进阶和WTForms扩展相关的知识,希望对你有一定的参考价值。

来源:孤独的自我

链接:https://segmentfault.com/a/1190000004502589


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


问题的提出


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


class Article(db.Model):

    """定义文章"""

 

    __tablename__ = 'articles'

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

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

    # 保存md格式的文本

    content = db.Column(db.Text)

    # 保存html格式的文本

    content_html = db.Column(db.Text)

    # 文章分类

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

    # 文章标签

    tags = db.relationship(

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

class Category(db.Model):

    """文章分类"""

 

    __tablename__ = 'categories'

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

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

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

 

class Tag(db.Model):

    """文章标签"""

 

    __tablename__ = 'tags'

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

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

 

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

article_tag_ref = db.Table('article_tag_ref',

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

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

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

                                     db.ForeignKey('tags.id'))

                           )


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


class ArticleForm(Form):

 

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

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

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

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

    submit = SubmitField(u"发布")


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


form = ArticleForm()

if form.validate_on_submit():

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

    ...


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


"""

:param tags:

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

"""

def str_to_obj(tags):

    r = []

    for tag in tags:

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

        if tag_obj is None:

            tag_obj = Tag(name=tag)

        r.append(tag_obj)

    return r


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


form = ArticleForm()

if form.validate_on_submit():

    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,贴一下代码:


class TagListField(Field):

    widget = TextInput()

 

    def _value(self):

        if self.data:

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

        else:

            return u''

 

    def process_formdata(self, valuelist):

        if valuelist:

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

        else:

            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对象即可:


class TagListField(Field):

    widget = TextInput()

 

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

                 **kwargs):

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

 

    def _value(self):

        if self.data:

            r = u''

            for obj in self.data:

                r += self.obj_to_str(obj)

            return u''

        else:

            return u''

 

    def process_formdata(self, valuelist):

        print 'process_formdata..'

        print valuelist

        if valuelist:

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

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

        else:

            self.data = None

 

    def pre_validate(self, form):

        pass

 

    @classmethod

    def _remove_duplicates(cls, seq):

        """去重"""

        d = {}

        for item in seq:

            if item.lower() not in d:

                d[item.lower()] = True

                yield item

 

    @classmethod

    def str_to_obj(cls, tag):

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

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

        if tag_obj is None:

            tag_obj = Tag(name=tag)

        return tag_obj

 

    @classmethod

    def obj_to_str(cls, obj):

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

        if obj:

            return obj.name

        else:

            return u''


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


...

class ArticleForm(Form):

    """编辑文章表单"""

 

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

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

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

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

    submit = SubmitField(u'发布')

...


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


def edit_article():

    """编辑文章"""

 

    form = ArticleForm()

    if form.validate_on_submit():

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

        article.tags = form.tags.data

        article.category = form.category.data

        try:

            db.session.add(article)

            db.session.commit()

        except:

            db.session.rollback()

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


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


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


最终效果



【今日微信公号推荐↓】

更多推荐请看


以上是关于Flask-WTF进阶和WTForms扩展的主要内容,如果未能解决你的问题,请参考以下文章

Python---Flask--03--Web表单

我的第三十五篇博客---flask-wtf表单验证

flask入门到起飞(五) flask-wtf使用

Flask之WTForms验证

flask web 表单验证 WTForms

flask表单操作