Flask SQLAlchemy 无法插入相同的数据

Flask SQLAlchemy unable to insert same data

我有一个函数可以对 table 执行插入操作。所以基本上每次用户吃东西时我都会尝试插入数据。

我试图 post 来自 post 人的数据 body

{
    "id_menu" : 4,
    "id_user" : 4
}

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

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

使用 postgresql 作为我的数据库

我正在通过下面的函数存储数据,这个函数将请求菜单的 id 和用户的 id,并将其存储在 table 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)})

a class of Users with id as primary key and menu_makanan as relationship with the table menu_hidangan_user

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 个食谱,其中包含有关食谱的详细信息

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 的 table。一个用户可以吃一个食物,每次用户吃一个食物,用户id和食谱id将被存储在这个table.

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)

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

The “association table” above has foreign key constraints established that refer to the two entity tables on either side of the relationship. The data type of each of association.left_id and association.right_id is normally inferred from that of the referenced table and may be omitted. It is also recommended, though not in any way required by SQLAlchemy, that the columns which refer to the two entity tables are established within either a unique constraint or more commonly as the primary key constraint; this ensures that duplicate rows won’t be persisted within the table regardless of issues on the application side:

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 也不允许您直接编辑此关系 table(以便使用增量值手动设置 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层太通用了。