添加和删​​除具有一对多关系的行的问题

时间:2021-05-12 14:56:33

标签: python sqlalchemy flask-sqlalchemy

两个表都包含唯一约束。我已经为此苦苦挣扎了一段时间。添加和删​​除的命令应该是什么?

边做边添加

movie = Movie(title="transformers", director="mb")
movie2 = Movie(title="transformers2", director="mb")
genre = Genre(category="action")
db.session.add(movie)
db.session.add(movie2)
movie.genres.append(genre)
movie2.genres.append(genre)
db.session.commit()

给予

(sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('action',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)

边做边删除

db.session.delete(Movie.query.filter_by(title="t1").first())
db.session.commit()

不删除

预期:

  • 如果电影表中不存在标题则添加新行,如果流派不存在于流派表中则添加新行并在关联表中创建链接。

  • 从电影表中删除标题并从关联表中删除链接。什么都不做。

我需要改变我的模型吗?

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

class Config:
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:?charset=utf8'
    SQLALCHEMY_DATABASE_ECHO = False
    SQLALCHEMY_TRACK_MODIFICATIONS = 'False'
    FLASK_ENV = 'test'
    DEBUG = True


db = SQLAlchemy()
conf = Config()


def create_app():
    app = Flask(__name__)
    app.config.from_object(conf)
    db.init_app(app)

    return app


class Rating(db.Model):
    __tablename__ = 'ratings'
    id = db.Column(db.Integer, db.ForeignKey("movies.id"), primary_key=True,)
    rating = db.Column(db.String(4), index=True, nullable=False,)
    movie = db.relationship("Movie", back_populates="rating",)

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


Association = db.Table('association',
                       db.Column('movies_id', db.Integer,
                                 db.ForeignKey('movies.id'), index=True,),
                       db.Column(
                           'genres_id',
                           db.Integer, db.ForeignKey('genres.id'), index=True,),
                       )


class Movie(db.Model):
    __tablename__ = 'movies'
    id = db.Column(db.Integer, primary_key=True, index=True,)
    title = db.Column(db.String(80), index=True, unique=True, nullable=False,)
    director = db.Column(db.String(30), primary_key=False,
                         unique=False, nullable=False)
    rating = db.relationship(
        "Rating", uselist=False, back_populates="movie"
    )
    genres = db.relationship(
        "Genre", secondary='association', backref=db.backref('movies'),
    )

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


class Genre(db.Model):
    __tablename__ = 'genres'
    id = db.Column(
        db.Integer,
        primary_key=True,
        index=True,
    )
    category = db.Column(
        db.String(80),
        index=True,
        unique=True,
        nullable=False,
    )

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


app = create_app()
context = app.app_context()
context.push()
db.create_all()

movie = Movie(title="transformers", director="mb")
rating = Rating(rating="5", movie=movie)
genre = Genre(category="action")
db.session.add_all([movie, rating])
movie.genres.append(genre)

try:
    db.session.commit()
    print("commit successfull")
except Exception as e:
    print(f"{e}")
    db.session.rollback()

movie2 = Movie(title="transformers2", director="mb")
rating = Rating(rating="5", movie=movie2)
genre2 = Genre.query.filter_by(category="action").first()
db.session.add_all([movie2, rating])
movie2.genres.append(genre2)

try:
    db.session.commit()
    print("commit successfull")
except Exception as e:
    print(f"{e}")
    db.session.rollback()

to_delete = Movie.query.filter_by(title="transformers").first()

try:
    db.session.delete(to_delete)
    db.session.commit()
    print("deletion successfull")
except Exception as e:
    print(f"{e}")
    db.session.rollback()

context.pop()

结果是

commit successfull
commit successfull
Dependency rule tried to blank-out primary key column 'ratings.id' on instance '<Rating at 0x2022c81bdc8>'

2 个答案:

答案 0 :(得分:3)

出现 IntegrityError 是因为流派表中已经存在动作流派。在这种情况下,必须从数据库中获取现有的流派

movie = Movie(title="transformers", director="mb")
movie2 = Movie(title="transformers2", director="mb")
genre = Genre.query.filter_by(category="action").first()
db.session.add(movie)
db.session.add(movie2)
movie.genres.append(genre)
movie2.genres.append(genre)
db.session.commit()

我无法在问题的原始版本中重现删除问题。然而错误

Dependency rule tried to blank-out primary key column 'ratings.id' on instance '<Rating at 0x2022c81bdc8>'

可以通过在电影模型中的评分关系上配置删除 cascade 来删除。

    rating = db.relationship(
        "Rating", uselist=False, back_populates="movie",
        cascade='save-update, merge, delete',
    )

这可确保在删除电影时,相关的评级也会被删除。没有它,正如您所发现的,SQLAlchemy 不会尝试删除相关的评级,从而导致错误。

答案 1 :(得分:2)

如果你的模型确实像你写的一样简单,我会用 Association Proxy 来简化这种多对多的关系。

您的代码将发生以下变化:

  • 添加一个 _genre_find_or_create 函数:
def _genre_find_or_create(category):
    obj = Genre.query.filter_by(category=category).first()
    return obj or Genre(category=category)
  • Movie 定义更改为 wrap genres

class Movie(db.Model):
    # ...

    _genres = db.relationship(
        "Genre",
        secondary="association",
        backref=db.backref("movies"),
    )
    genres = association_proxy(
        "_genres",
        "category",
        creator=_genre_find_or_create,
    )

那么你就可以这样使用了:

# pre-adding data to ensure the category already exists
genre = Genre(category="action")
db.session.add(genre)
db.session.commit()

# adding new data
movie = Movie(title="transformers")  # , director="mb")
movie2 = Movie(title="transformers2")  # , director="mb")
db.session.add(movie)
db.session.add(movie2)
# OLD way:
# genre = Genre(category="action")
# movie.genres.append(genre)
# movie2.genres.append(genre)
# NEW way: use just 'category'
movie.genres.append("action")
movie2.genres.append("action")

db.session.commit()


另请参阅此解决方案的另一个 question


我仍然不明白您为什么对 DELETE 有问题。您的示例代码运行正确,运行时生成以下 SQL 语句:

DELETE FROM association WHERE association.movies_id = ? AND association.genres_id = ?
DELETE FROM movies WHERE movies.id = ?

更新:删除 给定更新后的模型,很明显删除与 MovieRating 之间的一对一关系有关。

简单的解决方案是将 cascade="delete" 添加到 Movie.rating 关系中,这将在删除 Rating 的情况下删除 Movie 相关实例:

rating = db.relationship(
    "Rating",
    uselist=False,
    back_populates="movie",
    cascade="delete",  # <- **NEW**
)

阅读更多关于 Cascades

相关问题