pythonflaskflask-sqlalchemywtformsflask-uploads

Flask SqlAlchemy AttributeError: 'str' object has no attribute '_sa_instance_state'


So, I am trying to add images name that I save in specified directory, but this error keeps coming and nothing is been added in the database, Although the images keep getting saved in the specified directory. Here are all my files Models.py

from shop import db
from datetime import datetime


class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    price = db.Column(db.Numeric(10,2), nullable=False)
    stock = db.Column(db.Integer, nullable=False)
    desc = db.Column(db.Text, nullable=False)
    pub_date = db.Column(db.DateTime, nullable=False,
        default=datetime.utcnow)

    brand_id = db.Column(db.Integer, db.ForeignKey('brand.id'),
        nullable=False)
    brand = db.relationship('Brand',
        backref=db.backref('brands', lazy=True))

    category_id = db.Column(db.Integer, db.ForeignKey('category.id'),
        nullable=False)
    category = db.relationship('Category',
        backref=db.backref('categories', lazy=True))

    image_1 = db.Column(db.String(256), nullable=False, default='image1.jpg')
    image_2 = db.Column(db.String(256), nullable=False, default='image2.jpg')
    image_3 = db.Column(db.String(256), nullable=False, default='image3.jpg')

    def __repr__(self):
        return '<Product %r>' % self.name

class Brand(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), nullable=False, unique=True)

class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), nullable=False, unique=True)

db.create_all()

forms.py

from flask_wtf.file import FileAllowed, FileField, FileRequired
from wtforms import Form,StringField, IntegerField, BooleanField, TextAreaField, validators

class AddProducts(Form):
    name = StringField("Name", [validators.DataRequired()])
    price = IntegerField("Price:RS ", [validators.DataRequired()])
    stock = IntegerField("Stock", [validators.DataRequired()])
    desc = TextAreaField("Description", [validators.DataRequired()])
    # colors = TextAreaField("Colors", [validators.DataRequired()])

    image_1 = FileField('Image 1', [FileRequired(), FileAllowed(['jpg, jpeg, png, svg, gif']), "Images Only please"])
    image_2 = FileField('Image 2', [FileRequired(), FileAllowed(['jpg, jpeg, png, svg, gif']), "Images Only please"])
    image_3 = FileField('Image 3', [FileRequired(), FileAllowed(['jpg, jpeg, png, svg, gif']), "Images Only please"]) 

*routes.py


@app.route('/addproduct', methods=["GET", "POST"])
def addproduct():
    brands = Brand.query.all()
    categories = Category.query.all()
    form = AddProducts(request.form)
    if request.method == "POST":
        name = form.name.data
        price = form.price.data
        stock = form.stock.data
        desc = form.desc.data
        brand = request.form.get('brand')
        category = request.form.get('category')
        image_1 = photos.save(request.files['image_1'] , name=secrets.token_hex(10) + '.')
        image_2 = photos.save(request.files['image_2'] , name=secrets.token_hex(10) + '.')
        image_3 = photos.save(request.files['image_3'] , name=secrets.token_hex(10) + '.')
        print(f"Image 1 name:{image_1}, its type:{type(image_1)}")
        product = Product(name=name, price=price, stock=stock, desc=desc, brand=brand, category=category, 
                                image_1=image_1,image_2=image_2, image_3=image_3)
        db.session.add(product)
        flash(f"{name} has been added to database.", 'success')
        db.session.commit()
        return redirect(url_for('admin'))
    return render_template('products/addproduct.html', title='Add Product', form=form, brands=brands, 
                            categories=categories)

All the images type are strings, and model fields are string too, still I keep getting this error. Here is my html page for this form

{% extends 'layout.html' %}

{% block body_block %}
{% include '_messages.html' %}
<div class="container">
    <div class="row">
    <div class="col-md-2"></div>
    <div class="col-md-8">
        <h2 class="text-center bg-info p-2">
            Add product
        </h2>
        {% from '_formhelpers.html' import render_field %}
        <form action="" method="POST" enctype="multipart/form-data">
            {{ render_field(form.name, class="form-control", placeholder="Product Name")}}
            {{ render_field(form.price, class="form-control", placeholder="Price") }}
            {{ render_field(form.stock, class="form-control", placeholder="Stock") }}
            <label for="brand">Add a Brand</label>
            <select class="form-control" name="brand" id="brand">
                <option value="" class="form-control" required> Select a Brand</option>
                {% for brand in brands%}
                    <option value="brand.id" class="form-control">{{brand.name}}</option>
                {% endfor %}
            </select>
            <label for="category">Add a Category</label>
            <select class="form-control" name="category" id="category">
                <option value="" class="form-control" required> Select a Category</option>
                {% for category in categories %}
                    <option value="category.id" class="form-control">{{category.name}}</option>
                {% endfor %}
            </select>
            {{ render_field(form.desc, class="form-control", placeholder="Product Description", rows=10) }}
            <div class="container">
                <div class="row">
                    <div class="col-md-4">
                        {{ render_field(form.image_1, class="form-control")}}
                    </div>
                    <div class="col-md-4">
                        {{ render_field(form.image_2, class="form-control")}}
                    </div>
                    <div class="col-md-4">
                        {{ render_field(form.image_3, class="form-control")}}
                    </div>
                </div>
            </div>

            <button type="submit" class="btn btn-outline-info mt-4">Add Product</button>
        </form>
    </div>
    <div class="col-md-2"></div>
</div>
</div>
{% endblock body_block %}

Solution

  • I've simplified your example below. The error is occurring on the 5th line:

    image_1 = photos.save()
    image_2 = photos.save()
    image_3 = photos.save()
    print(f"Image 1 name:{image_1}, its type:{type(image_1)}")
    product = Product(brand=brand, category=category)           # <- HERE
    db.session.add(product)
    

    Since you mentioned that it's saving the images to the directory, which indicates the first 3 lines are successful. And nothing gets added to the database, indicating that the error is between line 4 and 6.

    Since you've specified in your models.py that attributes brand and category are relationship attributes (db.relationship()) These attributes are expecting an ORM object, and you're passing them a string.

    Instantiate the ORM objects before passing them to Product like so:

    brand = Brand(name="brandname")
    category = Category(name="categoryname")
    
    product = Product(brand=brand, category=category)
    

    This is what's indicated in this answer, understandably it's a long read: https://stackoverflow.com/a/55877355/3407256