Flask SQLAlchemy 无法插入相同的数据

Posted

技术标签:

【中文标题】Flask SQLAlchemy 无法插入相同的数据【英文标题】:Flask SQLAlchemy unable to insert same data 【发布时间】:2022-01-09 20:29:59 【问题描述】:

我有一个函数可以在表中执行插入操作。所以基本上我每次用户吃食物时都会尝试插入数据。

我试图用正文发布邮递员的数据


    "id_menu" : 4,
    "id_user" : 4

它工作正常,但我第二次尝试发布相同的请求时,数据不会存储到数据库中。它返回状态码 200,并带有成功消息,即使数据没有到来。但是当我尝试使用不同的菜单或用户时,效果很好。

所以问题是,我无法发布数据重复数据。只有当用户已经吃过菜单时才会出现问题。每次我通过/addHidangan功能发布吃过的菜单,它都不会被存储。我做错了什么?是关系吗?还是代码?谢谢

使用 postgresql 作为我的数据库

我是通过下面的函数来存储数据的,这个函数会请求菜单的id和用户的id,并将其存储到表menu_hidangan_user

@app.route('/hidangan', methods=['POST'])
def addHidangan():
    try:
        id_menu = request.json['id_menu']
        id_user = request.json['id_user']
        menu = Resep.query.filter_by(id=id_menu).first()
        user = Users.query.filter_by(id=id_user).first()

        menu.menu_user.append(user)
        db.session.add(menu)
        db.session.commit()
        return jsonify('statusCode': 200, 'message': 'Success')
    except Exception as e:
        return jsonify('statusCode': 400, 'err': True, 'message': str(e))

以id为主键,menu_makanan为与表menu_hidangan_user的关系的一类Users

class Users(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(30), unique=True, nullable=False)
    nama = db.Column(db.String(30), nullable=False)
    password = db.Column(db.String(100), nullable=False)
    telepon = db.Column(db.String(20), nullable=False)
    usia = db.Column(db.Integer, nullable=False)
    status_diabetes = db.Column(db.String(30), nullable=False)
    berat_badan = db.Column(db.Integer, nullable=False)
    tinggi_badan = db.Column(db.Integer, nullable=False)
    timestamp = db.Column(db.DateTime, nullable=False,
                          server_default=db.func.current_timestamp())
    menu_makanan = db.relationship(
        'Resep', secondary=menu_hidangan_user, backref="menu_user")
    kadar_gula = db.relationship(
        "KadarGulaDarahUser", backref="kadar_gula_user")

一类包含食谱细节的食谱

class Resep(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nama = db.Column(db.String(30), unique=True, nullable=False)
    detail = db.Column(db.Text, nullable=False)
    gula = db.Column(db.Integer, nullable=False)
    kalori = db.Column(db.Integer, nullable=False)
    protein = db.Column(db.Integer, nullable=False)
    lemak = db.Column(db.Integer, nullable=False)
    kategori = db.Column(db.String(25), nullable=False)
    img_url = db.Column(db.Text, nullable=False)
    timestamp = db.Column(db.DateTime, nullable=False,
                          server_default=db.func.current_timestamp())

还有一个名为menu_hidangan_user 的表。一个用户可以吃一个食物,每次用户吃一个食物,用户id和菜谱id都会存储在这个表中。

menu_hidangan_user = db.Table('menu_hidangan_user',
                          db.Column('id', db.Integer,
                                    primary_key=True),
                          db.Column('id_resep', db.Integer, db.ForeignKey(
                              'resep.id'), nullable=False, unique=False),
                          db.Column('id_user', db.Integer, db.ForeignKey(
                              'users.id'), nullable=False, unique=False),
                          db.Column('timestamp', db.DateTime, nullable=False,
                                    server_default=db.func.current_timestamp())
                          )

如果你需要复制,这里是完整的代码

from flask import Flask, json, request, jsonify, session
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from dotenv import load_dotenv
import os
import datetime

# Init
load_dotenv()
POSTGRESQL_USERNAME = os.getenv("POSTGRESQL_USERNAME")
POSTGRESQL_PASSWORD = os.getenv("POSTGRESQL_PASSWORD")
POSTGRESQL_HOST = os.getenv("POSTGRESQL_HOST")
POSTGRESQL_DB_NAME = os.getenv("POSTGRESQL_DB_NAME")
ENDPOINT = '/api'
POSTGRESQL_URI = f"postgresql://POSTGRESQL_USERNAME:POSTGRESQL_PASSWORD@POSTGRESQL_HOST/POSTGRESQL_DB_NAME"

app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.secret_key = "hehe"

# Database
app.config['SQLALCHEMY_DATABASE_URI'] = POSTGRESQL_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Init DB
db = SQLAlchemy(app)
# Init ma
ma = Marshmallow(app)

# Many to Many Relationship
menu_hidangan_user = db.Table('menu_hidangan_user',
                              db.Column('id', db.Integer,
                                        primary_key=True),
                              db.Column('id_resep', db.Integer, db.ForeignKey(
                                  'resep.id'), nullable=False, unique=False),
                              db.Column('id_user', db.Integer, db.ForeignKey(
                                  'users.id'), nullable=False, unique=False),
                              db.Column('timestamp', db.DateTime, nullable=False,
                                        server_default=db.func.current_timestamp())
                              )


## == 1. User Class ========= ##
class Users(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(30), unique=True, nullable=False)
    nama = db.Column(db.String(30), nullable=False)
    password = db.Column(db.String(100), nullable=False)
    telepon = db.Column(db.String(20), nullable=False)
    usia = db.Column(db.Integer, nullable=False)
    status_diabetes = db.Column(db.String(30), nullable=False)
    berat_badan = db.Column(db.Integer, nullable=False)
    tinggi_badan = db.Column(db.Integer, nullable=False)
    timestamp = db.Column(db.DateTime, nullable=False,
                          server_default=db.func.current_timestamp())
    menu_makanan = db.relationship(
        'Resep', secondary=menu_hidangan_user, backref="menu_user")
    kadar_gula = db.relationship(
        "KadarGulaDarahUser", backref="kadar_gula_user")

    def __init__(self, email, nama, password, telepon, usia, status_diabetes, berat_badan, tinggi_badan):
        self.email = email
        self.nama = nama
        self.password = password
        self.telepon = telepon
        self.usia = usia
        self.status_diabetes = status_diabetes
        self.berat_badan = berat_badan
        self.tinggi_badan = tinggi_badan


# User Schema
class UserSchema(ma.Schema):
    class Meta:
        fields = ('email', 'nama', 'telepon', 'usia',
                  'status_diabetes', 'berat_badan', 'tinggi_badan', 'timestamp')


# Init Schema
user_schema = UserSchema()
users_schema = UserSchema(many=True)

## == End of User Class ========= ##


## == 2. Resep Class ========= ##
class Resep(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    nama = db.Column(db.String(30), unique=True, nullable=False)
    detail = db.Column(db.Text, nullable=False)
    gula = db.Column(db.Integer, nullable=False)
    kalori = db.Column(db.Integer, nullable=False)
    protein = db.Column(db.Integer, nullable=False)
    lemak = db.Column(db.Integer, nullable=False)
    kategori = db.Column(db.String(25), nullable=False)
    img_url = db.Column(db.Text, nullable=False)
    timestamp = db.Column(db.DateTime, nullable=False,
                          server_default=db.func.current_timestamp())

    def __init__(self, nama, detail, gula, kalori, protein, lemak, kategori, img_url):
        self.nama = nama
        self.detail = detail
        self.gula = gula
        self.kalori = kalori
        self.protein = protein
        self.lemak = lemak
        self.kategori = kategori
        self.img_url = img_url


# Resep Schema
class ResepSchema(ma.Schema):
    class Meta:
        fields = ('id', 'nama', 'detail', 'gula',
                  'kalori', 'protein', 'lemak', 'kategori', 'img_url', 'timestamp')


# Init Schema
resep_schema = ResepSchema()
reseps_schema = ResepSchema(many=True)

## == End of Resep Class ========= ##


## == 3. KadarGulaDarahUser Class ========= ##
class KadarGulaDarahUser(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    id_user = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    kadar_gula_darah = db.Column(db.Integer, nullable=False)
    timestamp = db.Column(db.DateTime, nullable=False,
                          server_default=db.func.current_timestamp())

    def __init__(self, id_user, kadar_gula_darah):
        self.id_user = id_user
        self.kadar_gula_darah = kadar_gula_darah


# KadarGulaDarahUser Schema
class KadarGulaDarahUserSchema(ma.Schema):
    class Meta:
        fields = ('id', 'id_user', 'kadar_gula_darah', 'timestamp')


# Init Schema
kadarguladarahuser_schema = KadarGulaDarahUserSchema()
kadarguladarahusers_schema = KadarGulaDarahUserSchema(many=True)


class KonsumsiGulaUserSchema(ma.Schema):
    class Meta:
        fields = ('total_gula', 'tanggal')


konsumsigulausers_schema = KonsumsiGulaUserSchema(many=True)
## == End of KadarGulaDarah Class ========= ##


# FR-03. User dapat menambahkan hidangan untuk hari ini
@app.route(f'ENDPOINT/hidangan', methods=['POST'])
def addHidangan():
    try:
        id_menu = request.json['id_menu']
        id_user = request.json['id_user']
        menu = Resep.query.filter_by(id=id_menu).first()
        user = Users.query.filter_by(id=id_user).first()
        print(type(menu.menu_user))
        menu.menu_user.append(user)
        db.session.add(menu)
        db.session.commit()
        return jsonify('statusCode': 200, 'message': 'Success')
    except Exception as e:
        return jsonify('statusCode': 400, 'err': True, 'message': str(e))


if __name__ == "__main__":
    app.run(debug=True)

【问题讨论】:

没有错误信息,API返回200成功但数据没有来 多对多关系在每对链接行的链接表上只有一条记录。创建该链接记录后,进一步尝试创建新记录只会覆盖您找到的现有记录。您可能需要考虑使用 association object 模式并增加计数器列,而不是尝试添加新行。 【参考方案1】:

由于与 SQLAlchemy 的多对多关系,无法插入重复数据是正常的。 ORM 认为外键是主键:在数据建模中,关系行有一个 id 是不好的!见https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html#many-to-many:

上面的“关联表”建立了引用关系两侧的两个实体表的外键约束。关联.left_id 和关联.right_id 的数据类型通常是从​​被引用的表中推断出来的,可以省略。尽管 SQLAlchemy 不以任何方式要求,但也建议将引用两个实体表的列建立在唯一约束或更常见的主键约束中;这可确保无论应用程序端出现什么问题,重复行都不会保留在表中:

association_table = Table('association', Base.metadata,
    Column('left_id', ForeignKey('left.id'), primary_key=True),
    Column('right_id', ForeignKey('right.id'), primary_key=True)
)

ORM 也不允许你直接编辑这个关系表(为了手动设置 menu_hidangan_user.id 字段的值)。 我找到的唯一解决方案是不使用 ORM 并跳过他的语法和帮助。

@app.route(f'ENDPOINT/hidangan', methods=['POST'])
def addHidangan():
    try:
        id_menu = (request.get_json(force=True))['id_menu']
        id_user = (request.get_json(force=True))['id_user']
        menu = Resep.query.filter_by(id=id_menu).first()
        user = Users.query.filter_by(id=id_user).first()
        current_id = db.session.execute("select id from menu_hidangan_user order by id desc").fetchall()
        db.session.execute("insert into menu_hidangan_user (id, id_user, id_resep) values ("+( str( current_id[0][0]+ 1) if current_id else str(1))+","+str(user.id)+","+str(menu.id)+")")
        db.session.commit()
        return jsonify('statusCode': 200, 'message': 'Success')
    except Exception as e:
        return jsonify('statusCode': 400, 'err': True, 'message': str(e))

结果:

 id | id_resep | id_user |         timestamp          
----+----------+---------+----------------------------
  1 |        1 |       1 | 2021-12-04 10:45:54.009602
  2 |        1 |       1 | 2021-12-04 10:45:59.244704
  3 |        1 |       1 | 2021-12-04 10:46:00.185482
  4 |        1 |       1 | 2021-12-04 10:46:00.84306
(4 rows)

最后一条评论:ORM 可能非常无用,因为您无法直接与数据库对话。 ORM 层太通用了。

【讨论】:

谢谢你的回答

以上是关于Flask SQLAlchemy 无法插入相同的数据的主要内容,如果未能解决你的问题,请参考以下文章

Flask-SQLAlchemy:如何有条件地插入或更新一行

Flask-SQLAlchemy 多对多插入

Flask sqlalchemy 多对多插入数据

使用 flask_sqlalchemy 将 HTML 表单中的日期插入 SQlite 数据库

在 flask-sqlalchemy 中使用具有相同模型的多个数据库

当 2 个模型在 flask-sqlalchemy 中继承相同的对象时收到警告