При попытке сохранить и зафиксировать новый родительский объект, имеющий отношение (дочерний), как я могу избежать сохранения дублированных дочерних объектов? - PullRequest
1 голос
/ 14 марта 2020

Итак, у меня есть приложение, использующее Flask -SQLAlchemy. Это база данных пустяков, и у меня есть 3 таблицы (которые актуальны здесь). trivia для вопросов, users для пользователей, которые могут публиковать новые вопросы, и categories. Таким образом, у пустяков есть один столбец author_id, указывающий на таблицу users (извините, я так путаю, что я использую автора и пользователя) и category_id на категорию.

Соответствующие таблицы + схемы зефира:

class Category(db.Model):
    __tablename__ = 'trivia_categories'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    category = db.Column(db.String(50), unique=True)
    questions = db.relationship('Trivia', backref='category', single_parent=True)

class CategorySchema(ma.ModelSchema):
    class Meta:
        model = Category
        sqla_session = db.session

    questions = fields.List(fields.Nested("TriviaSchemaCondensed", exclude=('category',)), many=True)

class Trivia(db.Model):
    __tablename__ = 'trivia_questions'
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(db.Integer, db.ForeignKey('trivia_users.id'), nullable=True)
    validated = db.Column(db.Boolean)
    category_id = db.Column(db.Integer, db.ForeignKey('trivia_categories.id'))
    difficulty = db.Column(db.String(10), nullable=False)
    type = db.Column(db.String(10), nullable=False)
    question = db.Column(db.String(200), nullable=False)
    correct_answer = db.Column(db.String(75), nullable=False)
    incorrect_answers = db.relationship(
        'IncorrectAnswer',
        backref='related_question',
        cascade='all, delete, delete-orphan',
        single_parent=True,
    )

class TriviaSchema(ma.ModelSchema):
    class Meta:
        model = Trivia
        sqla_session = db.session

    category = fields.Nested("CategorySchema", exclude=('questions',), many=False)
    author = fields.Pluck("UserSchema", 'nickname')
    incorrect_answers = fields.List(fields.Nested("IncorrectAnswerSchema", exclude=("related_question",)))

Обратите внимание на соответствующий relationship в Category, указывающий на Trivia, и что Trivia имеет поле category_id.

Когда вставляется новый вопрос, я добавляю объект user к объекту trivia. Затем я проверяю, существует ли желаемая категория (у категорий в основном только столбец id в качестве первичного ключа и столбец category, который имеет уникальное ограничение). Я делаю это, опрашивая БД. Если я получаю результат обратно, я просто присоединяю его к новому trivia вопросу, в противном случае я сначала создаю и фиксирую новый category в БД, а затем присоединяю его к вопросу.

Наконец, я совершить вопрос. Соответствующая часть контроллера:

def create(user, body):
    """
    This function creates a new trivia question in the trivia structure
    based on the passed in trivia data
    :param trivia:  trivia question to create in trivia structure
    :return:        201 on success
    """

    trivia = body

    retrieved_user = User.query.filter_by(id=user).first()

    # Create a new trivia instance using the schema and the passed in trivia
    trivia_schema = TriviaSchema()
    incorrect_answer_schema = IncorrectAnswerSchema()

    # Save category for manipulations down the road
    raw_category = trivia.get("category")

    transform_incorrect_a = lambda raw_answer: { "answer": raw_answer }
    trivia["incorrect_answers"] = map(transform_incorrect_a, trivia.get("incorrect_answers"))
    trivia["category"] = { "category": raw_category }
    trivia["validated"] = True

    new_trivia = trivia_schema.load(trivia, session=db.session)

    new_trivia.author = retrieved_user

    # Check if category already exists
    category = (
      Category.query.filter(Category.category == raw_category)
      .one_or_none()
    )

    if category is None:
        category = Category(category=raw_category)
        db.session.add(category)


    new_trivia.category = category


    # Add the trivia question to the database
    db.session.add(new_trivia)
    db.session.commit()

    # Serialize and return the newly created trivia question in the response
    data = trivia_schema.dump(new_trivia)

    return data, 201

Моя проблема: сохранение всегда терпит неудачу, если категория уже существует. Я получаю IntegrityError, потому что нарушено ограничение уникальности:

sqlalchemy.exc.IntegrityError: (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely)
(MySQLdb._exceptions.IntegrityError) (1062, "Duplicate entry 'stringg' for key 'category'")
[SQL: INSERT INTO trivia_categories (category) VALUES (%s)]

Тем не менее, он не должен создавать новую запись категории в первую очередь (учитывая, что я уже извлекаю существующую запись, если это так)! Как будто SQLAlchemy всегда хочет создать новую запись категории, независимо от того, существует ли она и независимо от того, запросил ли я для этого базу данных.

Интересно, что с автором (пользователем) все в порядке - это не пытаюсь создавать новых пользователей!

Я буду очень признателен, если у кого-то есть какие-либо идеи или идеи. Я гуглил как сумасшедший и не могу найти решение.

Большое спасибо!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...