pythonflasksqlalchemyflask-wtforms

How to use back_populates reference brand_id in WTForm?


I have created relationship between two models - Addproduct and Brand. I want to add items in AddProducts form however I have no clue how to add brand_id in this AddProducts form. Kindly help me to get this sorted.

class Addproduct(db.Model):
    #__tablename__ = 'addproduct'
    id:Mapped[int] = mapped_column(Integer,primary_key=True)
    name:Mapped[str] = mapped_column(String(80),nullable=False, unique=True)
    price:Mapped[int] = mapped_column(Integer,nullable=False)
    brand_id:Mapped[int]= mapped_column(Integer, db.ForeignKey('brand.id'),nullable=False)
    brand = relationship('Brand', back_populates="product")

class Brand(db.Model):
    __tablename__ =  'brand'
    id:Mapped[int] = mapped_column(Integer,primary_key=True)
    name:Mapped[str] = mapped_column(String(30),nullable=False, unique=True)
    product = relationship('Addproduct', back_populates="brand")

class AddProducts(FlaskForm):
    name = StringField('Name',validators=[DataRequired()])
    price = IntegerField('Price',validators=[DataRequired()])
    brands = QuerySelectField('Add a brand', query_factory=brandchoices)
    Submit = SubmitField('Add Product')

I want to know how to add this brand_id in this below form:

@app.route('/addproduct', methods=['POST','GET'])
def addproduct():
    form = AddProducts()
    if form.validate_on_submit():
        name = form.name.data
        price = form.price.data
        brand = 
        addprod = Addproduct(
        name=name, price=price)
        db.session.add(addprod)
        db.session.commit()
        flash(f'The product {name} has been added.')
    return render_template('products/addproduct.html', form=form)

Solution

  • The QuerySelectField returns an object of type Addproduct upon request, which also gives you access to the attribute id.

    But you can also simplify the code a little.
    If the attributes in the database model and the form are named the same, you can use form.populate_obj(obj) to assign the properties to the new entry. This means that the object selected in the input field can be assigned directly to the relationship.

    The following example shows you how.

    from flask import (
        Flask, 
        flash, 
        redirect, 
        render_template, 
        request, 
        url_for
    )
    from flask_sqlalchemy import SQLAlchemy
    from sqlalchemy.orm import (
        DeclarativeBase, 
        Mapped 
    )
    from typing import List
    from flask_wtf import FlaskForm
    from wtforms import (
        StringField, 
        SubmitField, 
        IntegerField
    )
    from wtforms.validators import DataRequired
    from wtforms_sqlalchemy.fields import QuerySelectField
    
    class Base(DeclarativeBase):
        pass
    
    app = Flask(__name__)
    app.config.from_mapping(
        SECRET_KEY='your secret here', 
        SQLALCHEMY_DATABASE_URI='sqlite:///example.db'
    )
    db = SQLAlchemy(app, model_class=Base)
    
    class Product(db.Model):
        id:Mapped[int] = db.mapped_column(db.Integer, primary_key=True)
        name:Mapped[str] = db.mapped_column(db.String(80), nullable=False, unique=True)
        price:Mapped[int] = db.mapped_column(db.Integer, nullable=False)
        brand_id:Mapped[int]= db.mapped_column(db.Integer, db.ForeignKey('brand.id'), nullable=False)
        brand:Mapped['Brand'] = db.relationship(back_populates='product')
    
    class Brand(db.Model):
        id:Mapped[int] = db.mapped_column(db.Integer, primary_key=True)
        name:Mapped[str] = db.mapped_column(db.String(30), nullable=False, unique=True)
        product:Mapped[List['Product']] = db.relationship(back_populates='brand')
    
    class AddProductForm(FlaskForm):
        name = StringField('Name',validators=[DataRequired()])
        price = IntegerField('Price',validators=[DataRequired()])
        brand = QuerySelectField('Add a brand', 
            query_factory=lambda: db.session.execute(db.select(Brand)).scalars(), 
            get_label='name'
        )
        submit = SubmitField('Add Product')
    
    with app.app_context():
        db.drop_all()
        db.create_all()
    
        brands = [Brand(name=f'Brand-{i}') for i in range(1, 10)]
        db.session.add_all(brands)
        db.session.commit()
    
    @app.route('/addproduct', methods=['POST','GET'])
    def addproduct():
        form = AddProductForm()
        if form.validate_on_submit():
            product = Product()
            form.populate_obj(product)
            db.session.add(product)
            db.session.commit()
            flash(f'The product {product.name} has been added.')
            return redirect(url_for('addproduct'))
        return render_template('products/addproduct.html', **locals())
    

    There are two options for uploading files. Both require the enctype 'multipart/form-data' for the form element. The form is the same for both variants.

    class AddProductForm(FlaskForm):
        name = StringField('Name',validators=[DataRequired()])
        price = IntegerField('Price',validators=[DataRequired()])
        brand = QuerySelectField('Add a brand', 
            query_factory=lambda: db.session.execute(db.select(Brand)).scalars(), 
            get_label='name'
        )
        image_1 = FileField('First Image', 
            validators=[
                FileRequired(), 
                FileAllowed(['jpg','jpeg','png','gif'])
            ]
        )
        submit = SubmitField('Add Product')
    
    Upload the file to a folder and save the file name to the database.

    In this case, the files, provided they are saved in the static folder, can be accessed via the file name.

    <img src="{{ url_for('static', filename=product.image_ref1) }}"/>
    
    class Product(db.Model):
        id:Mapped[int] = db.mapped_column(db.Integer, primary_key=True)
        name:Mapped[str] = db.mapped_column(db.String(80), nullable=False, unique=True)
        price:Mapped[int] = db.mapped_column(db.Integer, nullable=False)
        brand_id:Mapped[int]= db.mapped_column(db.Integer, db.ForeignKey('brand.id'), nullable=False)
        brand:Mapped['Brand'] = db.relationship(back_populates='product')
        image_ref1:Mapped[str] = db.mapped_column(db.String)
    
    @app.route('/addproduct', methods=['POST','GET'])
    def addproduct():
        form = AddProductForm()
        if form.validate_on_submit():
            file = form.image_1.data
            filename = secure_filename(file.filename)
            file.save(os.path.join(
                app.static_folder, filename
            ))
            product = Product(image_ref1 = filename)
            form.populate_obj(product)
            db.session.add(product)
            db.session.commit()
            flash(f'The product {product.name} has been added.')
            return redirect(url_for('addproduct'))
        return render_template('products/addproduct.html', **locals())
    
    Upload and save the file as LargeBinary in the database.

    Two columns are required, one containing the data and one containing the mime type. In order to serve the image data again, an additional endpoint is required.

    class Product(db.Model):
        id:Mapped[int] = db.mapped_column(db.Integer, primary_key=True)
        name:Mapped[str] = db.mapped_column(db.String(80), nullable=False, unique=True)
        price:Mapped[int] = db.mapped_column(db.Integer, nullable=False)
        brand_id:Mapped[int]= db.mapped_column(db.Integer, db.ForeignKey('brand.id'), nullable=False)
        brand:Mapped['Brand'] = db.relationship(back_populates='product')
        image_mime:Mapped[str] = db.mapped_column(db.String)
        image_data:Mapped[bytes] = db.mapped_column(db.LargeBinary)
    
    @app.route('/addproduct', methods=['POST','GET'])
    def addproduct():
        form = AddProductForm()
        if form.validate_on_submit():
            product = Product()
            form.populate_obj(product)
            product.image_mime = form.image_1.data.mimetype
            product.image_data = form.image_1.data.read()
            db.session.add(product)
            db.session.commit()
            flash(f'The product {product.name} has been added.')
            return redirect(url_for('addproduct'))
        return render_template('products/addproduct.html', **locals())
    
    @app.route('/image/<int:product_id>')
    def download_image(product_id):
        product = db.get_or_404(Product, product_id)
        return send_file(
            io.BytesIO(product.image_data), 
            mimetype=product.image_mime
        )