[已解决] 使用表单创建分类或文章,运行并访问时提示“TypeError: 'NoneType' object is not iterable”

flask-wtf
表单

#1

如题,我在参考 @greyli 的《Flask Web开发实践》一书的bluelog相关章节编写一个博客,编写完“创建分类”和“创建文章”页面的数据模型、表单、HTML页面,运行并访问时遇到“TypeError: ‘NoneType’ object is not iterable” 的错误,无论百度、Google、贴吧、QQ群以及微信群都求助过,没有得到答案。希望能在这里得到答案,谢谢。
错误截图:


视图代码:

#创建分类视图
@admin_bp.route('/category/create', methods=['GET','POST'])
def new_category():
    form = Category_Form()
    if form.validate_on_submit():
        categoryname = form.category_name.data
        categoryaka = form.category_aka.data
        parentcategoryid = form.parent_category_id.data
        new_cat = Category(category_name=categoryname, category_aka=categoryaka, parent_category_id=parentcategoryid)
        db.session.add(new_cat)
        db.session.commit()
        return redirect(url_for('.category_list'))
    return render_template('admin/new_category.html',form=form)

#创建文章视图
@admin_bp.route('/post/new_post',methods=['GET','POST'])
@login_required
def new_post():
    form = Post_Form()
    if form.validate_on_submit():
        title = form.post_title.data
        category = Category.query.get(form.post_category.data)
        thumb = form.post_thumb.data
        text = form.post_content.data
        post = Post(post_title=title,category_id=category,post_thumb=thumb,post_content=text)
        db.session.add(post)
        db.session.commit()
        return redirect(url_for('.post_list'))
    return render_template('admin/create_post.html', form=form)

数据模型代码:

# 分类数据模型
class Category(db.Model):
    __tablename__='category'
    category_id = db.Column(db.Integer,primary_key=True,autoincrement=True,nullable=False,index=True,unique=True)
    category_name = db.Column(db.String(256),nullable=False,unique=True)
    category_aka = db.Column(db.String(256),nullable=False,unique=True)
    parent_category_id = db.Column(db.Integer)
    posts = db.relationship('Post',backref='category')

#文章数据模型
class Post(db.Model):
    __tablename__='post'
    post_id = db.Column(db.Integer,primary_key=True,autoincrement=True,nullable=False,index=True,unique=True)
    post_title = db.Column(db.String(256),nullable=False)
    post_thumb = db.Column(db.Text,nullable=False,default='https://api.uomg.com/api/rand.avatar?sort=女&format=jpg')
    publish_datetime = db.Column(db.DateTime,default=datetime.now())
    post_content = db.Column(db.Text,nullable=False)
    post_url = db.Column(db.String(256),nullable=False)
    post_view = db.Column(db.Integer,nullable=False,default=0)
    tag_id = db.relationship('Tags', secondary=post_tags, backref=db.backref('posts')) 
    category_id = db.Column(db.Integer, db.ForeignKey('category.category_id')) 
    # category = db.relationship('Category', back_populates='postss')
    user_id = db.Column(db.Integer, db.ForeignKey('user.user_id')) 
    comments = db.relationship('Comment',backref='thepost',cascade='all,delete-orphan')

表单代码:

#分类表单
class Category_Form(FlaskForm):
    category_name = StringField('分类名称',validators=[DataRequired(message='必填项')],render_kw={'placeholder':'分类名称'})
    category_aka = StringField('分类别名',validators=[DataRequired(message='必填项,必须是英文和字母的结合')],render_kw={'placeholder':'分类别名'})
    parent_category_id = SelectField('父级分类',validators=[DataRequired()],render_kw={'placeholder':'父级分类'})

#文章表单
class Post_Form(FlaskForm):
    post_title = StringField('文章标题',validators=[DataRequired(message='文章标题必填!')],render_kw={'placeholder':''})
    post_category = SelectField('文章分类', coerce=int,default=1,validators=[DataRequired(message='分类获取出错!')])
    post_content = CKEditorField('文章正文',validators=[DataRequired(message='文章内容必写!')],render_kw={'placeholder':'正文内容'})
    post_thumb = FileField('文章封面',validators=[DataRequired(message='必须上传缩略图!')])
    post_tag = StringField('文章标签',validators=[DataRequired(message='文章必须有标签!')])

    def __init__(self,*args,**kwargs):
        super(Post_Form, self).__init__(*args,**kwargs)
        self.post_category.choices = [(category.category_id,category.category_name)
                                      for category in Category.query.order_by(Category.category_name).all()]

经测试,数据库连接正常,写的查询页面(文章列表及分类列表)均有数据输出(数据库中直接写入)。
在视图代码中,使用print()语句作为判断,程序仅执行到form=Post_Form()就开始报错,后面的if form.validate_on_submit():并没有被运行。

希望 @greyli 在百忙之中帮忙指正错误,谢谢!
如果有其他小伙伴也能够指出,请您不吝赐教,万分感谢!


#2

我想你已经很接近问题原因了,报错那一行对应的代码(下次麻烦用纯文本贴出来所有的代码和报错,不要用截图,像这里我就需要一点一点打出来……而且还需要下载图片进行放大查看细节):

for value, label in self.choices

结合你的表单类 Post_Form 定义可以判断是分类下拉列表字段出了问题(因为只有这个字段包含 choices 属性)。再看到异常类和描述是:

TypeError: 'NoneType' object is not iterable

就可以推断是 self.post_category.choices 的值为 None,所以无法迭代,也就是列表为空,那么大概是因为分类记录没数据。

可以看看是不是分类表没数据,考虑设一个默认分类。


#3

这样写试试。


#4
class Post_Form(FlaskForm):
    ...
    post_category = SelectField('文章分类', coerce=int,default=1,validators=[DataRequired(message='分类获取出错!')])

另外,建议创建 SelectField() 对象时,最好直接设置 choices 其参数,如果后面再设置可能会引发意想不到的bug。


#5

谢谢,我大概找到方向,并试验出了一些结果,但是需要我把实验程序和主程序对比,更正后看结果再确定。非常感谢!


#6

好的,确实是个不错的思路,如果我的思路走不通的话,我会尝试你的方法。
非常感谢!:+1:


#7

:100:非常感谢两位 @greyli@AngelLiang 的帮助,最终我发现是因为 父级分类 的原因,从model、forms和HTML文件中删除关于父级分类的代码,程序即可正常渲染并完成数据的提交,虽然提示和处理方法对于我的理解来讲完全没有关系:joy: 但是也是个不错的经验。

:smiling_face_with_three_hearts:另外,我发现了在开发FLASK很好debug方法,可能对于大佬们来讲是基础,但对于小白来讲可能很有帮助,分享一下:

  • 在配置文件中开启SQLALCHEMY_ECHO,即SQLALCHEMY_ECHO=True
    示例(数据取自网络):
1. /home/python/Desktop/test/demo_sqlalchemy.py
2. 2018-12-27 17:46:54,205 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode'
3. 2018-12-27 17:46:54,205 INFO sqlalchemy.engine.base.Engine {}
2018-12-27 17:46:54,208 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names'
4. 2018-12-27 17:46:54,208 INFO sqlalchemy.engine.base.Engine {}
5. 2018-12-27 17:46:54,211 INFO sqlalchemy.engine.base.Engine SELECT DATABASE()
6. 2018-12-27 17:46:54,211 INFO sqlalchemy.engine.base.Engine {}

以上是示例代码,第1行是访问的python文件(或路由),第2行开始就是数据库操作的过程代码,在debug时真的非常有用。
每访问一个文件(或路由)都会有这么对应的执行结果显示,如果debug,注意看出错的文件(或路由)是哪一个,帮助快速锁定bug源头。

  • 如果表单能够渲染,但是点击提交无反应或者提交后提示TypeError:'NoneType' is not iterable,可以使用下面的代码块插入在form实例化后面,if form.validate_on_submit():前面,来准确获取具体原因:
def new_category():
    form = Category_Form()
    print(form.errors)          #先确认一下表单实例化之后有无错误
    if form.is_submitted():     #表单是否执行提交操作
        print('提交')
    if form.validate():         #表单数据是否进行了验证
        print('验证')
    print(form.errors)          #打印执行以上操作后出现的错误信息
    if form.validate_on_submit():

以上是我在遇到该问题时不断学习并总结出来的些许经验,为了使更多遇到同样错误的人少走弯路,分享一下。

最后希望 @greyli 大佬的新书尽快上市,期待!
(希望不要像上一本一样那么厚,也许因为这个原因,造成书页脱胶,成了两半~:sob: