使用SQLAlchemy提交表单会引发“列表”对象没有属性

时间:2019-03-14 12:49:35

标签: flask sqlalchemy flask-sqlalchemy flask-wtforms

对于Flask和SQLAlchemy以及编码我都是新手,请耐心等待。

我要做的是通过表单将数据发送到数据库。
正常工作直到我想要两个以上具有一对多关系的表,因为在植物中可以累积许多元素,而植物可以具有许多属性,并且烧瓶抛出错误 AttributeError:'list '对象没有属性'_sa_instance_state'
形式是:

from flask_wtf import FlaskForm
from wtforms.ext.sqlalchemy.fields import  QuerySelectMultipleField
from wtforms import StringField, PasswordField, SubmitField,BooleanField, TextAreaField

#Query for Dynamic Nutrient Accumulator Model
def enabled_dna():
    return DNA.query.all()
#Query for Nitrogen Fixers Nursing Model

def enabled_nfn():
    return NFN.query.all()

class NewPlantForm(FlaskForm):
    common_name = StringField('Common Name', render_kw={"placeholder": "Common name"},
                          validators=[DataRequired(), Length(min=2, max=40)])
    botanical_name = StringField('Botanical Name', render_kw={"placeholder": "Botanical name"},
                             validators=[DataRequired(), Length(min=2, max=80)])
    short_description = TextAreaField('Short Description', render_kw={"placeholder": "Please add a short description"},
                                  validators=[DataRequired()])
    medicinal = TextAreaField('Medicinal Use', render_kw={"placeholder": "Medicinal use"},
                        validators=[DataRequired()])
    dna = QuerySelectMultipleField('Select Element',query_factory=enabled_dna,allow_blank=True)
    nfn = QuerySelectMultipleField('Select Property',query_factory=enabled_nfn,allow_blank=True)
    submit = SubmitField('Add plant')

models.py看起来像这样:

#Plants Table
class Plants(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    common_name = db.Column(db.String(40), nullable=False)
    botanical_name = db.Column(db.String(80), unique=True, nullable=False)
    short_description = db.Column(db.Text, nullable=False)
    medicinal = db.Column(db.Text, nullable=False)
    image_file = db.Column(db.String(20), default='default_plant_pic.jpg')
    date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    dna_id = db.Column(db.Integer, db.ForeignKey('DNA.id'))
    dna = db.relationship('DNA', backref=db.backref('plant_dna', lazy='dynamic'))  # Dynamic_Nutrient_Accumulated
    nfn_id = db.Column(db.Integer, db.ForeignKey('NFN.id'))
    nfn = db.relationship('NFN', backref=db.backref('plant_nfn', lazy='dynamic'))  # Nitrogen_Fixers_Nursing

    def __repr__(self):
        return f"Plants('{self.common_name}', '{self.botanical_name}', '{self.short_description}'," \
        f" '{self.medicinal}', '{self.dna}', '{self.nfn}' )"

#Dynamic_Nutrient_Accumulated
class DNA(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    element = db.Column(db.String(15))

    def __repr__(self):
        return '[ {}]'.format(self.element)
#Nitrogen_Fixers_Nursing
class NFN(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    plant_extra = db.Column(db.String(40))

    def __repr__(self):
        return '[ {}]'.format(self.plant_extra)

路由和表单与仅包含一个表的字段的表单配合良好。但是,当我添加了包含其他表(form.dna.data和form.nfn.data)中的数据的第二和第三字段时,它现在不起作用。

我去新工厂的路线是:

@app.route("/plants/new/", methods=['GET', 'POST'])
@login_required# User must be logged in to create a new plant
def new_plant():
    form = NewPlantForm()

    if form.validate_on_submit():
        new_plant = Plants(common_name=form.common_name.data,
                           botanical_name=form.botanical_name.data,
                           short_description=form.short_description.data,
                           medicinal=form.medicinal.data,
                           dna=form.dna.data,
                           nfn=form.nfn.data,
                           author=current_user)
        db.session.add(new_plant)
        db.session.commit()
        flash('Thank you ! You have successfully added a plant '
              'to the database!', 'success')

        return redirect(url_for('plants'))
    image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')

    return render_template('new_plant.html', title='Add new plant',
                       image_file=image_file, form=form)

绘制植物信息的途径是:

@app.route("/plants")
def plants():
    plants = Plants.query.all()
    return render_template('plants.html', title= 'Plants Database', plants=plants)

我尝试从终端在本地使用它,但是它可以工作,但是我不知道我缺少什么,或者关系模型是否错误,无法从flask应用程序中使用它。

预先感谢您的耐心和帮助。

更新

经过反复试验后,现在似乎可以正常使用(将具有选定字段的工厂添加到数据库,将工厂数据正确呈现到模板中,将工厂正确添加到使用DB浏览器的数据库视图中(对于SQLite),将 QuerySelectMultipleField 更改为 QuerySelectField 之后。但是,我的意思是能够选择并呈现多种选择。

我注意到的另一件事是,在使用 QuerySelectField 时,模板会正确呈现一个下拉列表,但在尝试使用 QuerySelectMultipleField 时em> ,它仅呈现一个包含元素的列表,没有下拉列表。

这是模板的一小部分,具有选择字段 form.dna form.nfn

<div class="form-group">
  {{ form.dna(class="form-control form-control-sm") }}
</div>
<div class="form-group">
      {{ form.nfn(class="form-control form-control-sm")}}
</div>

我正在使用Bootstrap。这可能与未正确为多次选择编写的模板格式有关吗? 谢谢。

Update2

我设法通过遍历这样的表单数据来使 QuerySelectMultipleField 正常工作:

 @app.route("/plants/new/", methods=['GET', 'POST'])
 @login_required# User must be logged in to create a new plant
 def new_plant():
     form = NewPlantForm()

    if form.validate_on_submit():
       new_plant = Plants(common_name = form.common_name.data, botanical_name = form.botanical_name.data,
                       short_description = form.short_description.data, medicinal=form.medicinal.data,
                       author=current_user)
      **for dna_element in form.dna.data:
          new_plant.dna = dna_element
      for nfn_element in form.nfn.data:
          new_plant.nfn = nfn_element**

    db.session.add(new_plant)
    db.session.commit()
    flash(f'Thank you ! You have successfully added a plant to the database!', 'success')
    return redirect(url_for('plants'))
image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')
return render_template('new_plant.html', title='Add new plant',
                       image_file=image_file, form=form)

我不再收到错误 AttributeError:'list'对象没有属性'_sa_instance_state',并且该工厂已成功添加到数据库中,但是当我在数据库中查找时,我可以看到只选择了一个选项,而不是多个选择。 根据我在这里读到的内容:Flask App Using WTForms with SelectMultipleField,我应该使用 form.something.data 来获取我已完成但无法使用的项目列表,我只拿一件 请帮忙。 谢谢!

更新3并解决问题

实施 sleblanc的响应后,我现在拥有以下适用于表单并正确显示的代码: ** models.py:**

plants_dna_table = db.Table(
'plants_dna',
db.Column('plants_id', db.Integer, db.ForeignKey('plants.id'), nullable=False),
db.Column('dna_id', db.Integer, db.ForeignKey('DNA.id'), nullable=False),
db.UniqueConstraint('plants_id', 'dna_id'))

plants_nfn_table = db.Table(
'plants_nfn',
db.Column('plants_id', db.Integer, db.ForeignKey('plants.id'), nullable=False),
db.Column('nfn_id', db.Integer, db.ForeignKey('NFN.id'), nullable=False),
db.UniqueConstraint('plants_id', 'nfn_id'))

#Plants Table
class Plants(db.Model):
id = db.Column(db.Integer, primary_key=True)
common_name = db.Column(db.String(40), nullable=False)
botanical_name = db.Column(db.String(80), unique=True, nullable=False)
short_description = db.Column(db.Text, nullable=False)
medicinal = db.Column(db.Text, nullable=False)
image_file = db.Column(db.String(20), default='default_plant_pic.jpg')
date_added = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
dna = db.relationship('DNA', secondary = plants_dna_table)  # Dynamic_Nutrient_Accumulated
nfn = db.relationship('NFN', secondary = plants_nfn_table)  # Nitrogen_Fixers_Nursing

def __repr__(self):
    return f"Plants('{self.common_name}', '{self.botanical_name}', '{self.short_description}'," \
        f" '{self.medicinal}', '{self.dna}', '{self.nfn}' )"

#Dynamic_Nutrient_Accumulated
class DNA(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   element = db.Column(db.String(15))

   def __repr__(self):
       return '{}'.format(self.element)
#Nitrogen_Fixers_Nursing
class NFN(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   plant_extra = db.Column(db.String(40))

   def __repr__(self):
   return '{}'.format(self.plant_extra)

以大写字母显示的 db.ForeignKey('DNA.id')可以完成此工作,并且不会因找不到表DNA而产生错误。

** routes.py:**

#Route for users to add a plant to the database
@app.route("/plants/new/", methods=['GET', 'POST'])
@login_required# User must be logged in to create a new plant
def new_plant():
form = NewPlantForm()

if form.validate_on_submit():
    new_plant = Plants(common_name = form.common_name.data, botanical_name = form.botanical_name.data,
                       short_description = form.short_description.data, medicinal=form.medicinal.data,
                       author=current_user)
    for dna_element in form.dna.data:
        new_plant.dna.append(dna_element)

    for nfn_element in form.nfn.data:
        new_plant.nfn.append(nfn_element)

    print(new_plant)
    db.session.add(new_plant)
    db.session.commit()
    flash(f'Thank you ! You have successfully added a plant to the database!', 'success')
    return redirect(url_for('plants'))
image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')
return render_template('new_plant.html', title='Add new plant',
                       image_file=image_file, form=form)

感谢@sleblanc!

1 个答案:

答案 0 :(得分:0)

您已经建立了反向关系模型。

如果我没记错的话,您的植物与DNA和NFN之间存在一对多的关系,这意味着一个“植物”物体将具有多个DNA和多个NFN。按照定义,您的“植物”模型只有一个DNA字段和一个NFN字段,这意味着无法将关联存储在数据库中。

您将需要修改Plants模型以表示该关系。您必须首先确定一个“ DNA”或一个“ NFN”是否可以被多个“植物”共享,或者它们对于每个“植物”实例是否唯一,因为它们涉及不同的方案。

class Plants(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    ...

# second option, exclusive relationship
class DNA(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    ...
    plants_id = db.Column(db.ForeignKey(Plants.id), nullable=False)
    ...

class NFN(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    ...
    plants_id = db.Column(db.ForeignKey(Plants.id), nullable=False)
    ...


# first option, non-exclusive relationship
class Plants(db.Model):
    ...
    dna = relationship("DNA", secondary=plants_dna_table)
    nfn = relationship("NFN", secondary=plants_nfn_table) 


plants_dna_table = db.Table(
    'plants_dna',
    db.Column('plants_id', db.ForeignKey('plants.id'), nullable=False),
    db.Column('dna_id', db.ForeignKey('dna.id'), nullable=False),
    db.UniqueConstraint('plants_id', 'dna_id'),
    )

plants_nfn_table = db.Table(
    'plants_nfn',
    db.Column('plants_id', db.ForeignKey('plants.id'), nullable=False),
    db.Column('nfn_id', db.ForeignKey('nfn.id'), nullable=False),
    db.UniqueConstraint('plants_id', 'nfn_id'),
    )

修改Plants模型并在两者之间建立关系之后,请记住.dna和.nfn属性将具有列表类型。您可以通过append到列表来为实例添加关系。

我强烈建议您使用FlaskForm.populate_obj:

@app.route("/plants/new/", methods=['GET', 'POST'])
@login_required# User must be logged in to create a new plant
def new_plant():
obj = Plants()
form = NewPlantForm()

if form.validate_on_submit():
    form.populate_obj(obj)

    print(obj) # debugging
    db.session.add(obj)
    db.session.commit()
    flash('Thank you ! You have successfully added '
          'a plant to the database!', 'success')
    return redirect(url_for('plants'))

image_file = url_for('static', filename='img/plants/default_plant_pic.jpg')
return render_template('new_plant.html', title='Add new plant',
                   image_file=image_file, form=form)

此外,它还允许您通过添加几行来合并将来的Plant更新视图:

@app.route('/plant/<id>/edit', methods=('GET', 'POST',))
@app.route('/plant/new', methods=('GET', 'POST'))
def create_plant(id=None):
    if id is not None:
        obj = Plant.query.get(id)
    else:
        obj = Plant()

    form = PlantForm(request.form, obj=obj)

    if form.validate_on_submit():
        form.populate_obj(obj)

        db.session.add(obj)
        db.session.commit()
        flash('Thank you ! You have successfully added '
              'a plant to the database!', 'success')
        return redirect(url_for('get_plant', id=obj.id))

    else:
        image_file = url_for(
            'static', 
            filename='img/plants/default_plant_pic.jpg')
        return render_template('plant.html', title='Add new plant',
                               image_file=image_file,
                               form=form, obj=obj)