Сделайте так, чтобы первый экземпляр модели имел уникальные поля в SQLAlchemy

#python #sqlalchemy

#python #sqlalchemy

Вопрос:

У меня есть приложение Flask SQLAlchemy с User моделью. Я хочу применить эту бизнес-логику: первым User созданным в приложении должен быть администратор (т.Е. Поле is_admin=True ), но все остальные пользователи по умолчанию не должны быть администраторами.

Модель выглядит примерно так (упрощенно):

 class User(db.Model):
    __tablename__ = "users"
    user_id = Column(Integer, primary_key=True)
    username = Column(UnicodeText, unique=True, nullable=False)
    email = Column(UnicodeText, unique=True, nullable=False)
    password = Column(UnicodeText, nullable=True)
    is_admin = Column(Boolean(), default=False)
  

И я хочу, чтобы этот тестовый пример был успешным:

 first = User(username="foo", email="foo@foo.com", password="foobarbaz123")
second = User(username="bar", email="bar@bar.com", password="foobarbaz123")

db.session.add_all([first, second])
db.session.commit()

assert first.is_admin
assert not second.is_admin
  

Я попробовал несколько подходов:

  • Поместите эту бизнес-логику в сам маршрут API. У этого есть основной недостаток, заключающийся в том, что он не интегрирован в уровень модели, поэтому его легко обойти, и вам нужно поддерживать эту функциональность в нескольких местах.
  • Поместите эту логику в конструктор модели, например
     def __init__(self):
         user_count = db.session.query(User).count()
         if user_count == 0:
             self.is_admin = True
      

    Это не работает, потому что мы не можем предположить, что у нас всегда есть доступ к сеансу базы данных. Например, при построении тестовых данных потребуется обращение к базе данных, когда данные находятся в нестабильном состоянии.

  • Поместите эту логику в after_insert событие, например
     @event.listens_for(User, 'after_insert')
    def receive_after_insert(mapper, connection, target):
         user_count = db.session.query(User).count()
         if user_count == 0:
             target.is_admin = True
      

    Однако это не проходит тестовый пример, потому что, похоже, он обновляет модель асинхронно.

Как можно реализовать эту бизнес-логику?

Комментарии:

1. могу я спросить «почему» вы пытаетесь реализовать это таким образом? Особенно после того, как вы сами нашли его, есть так много недостатков или препятствий для того, чтобы сделать это таким образом . И что должно произойти, если этот первый пользователь будет удален из базы данных? Должен ли другой быть назначен администратором? Какой из них?

2. Я не совсем понимаю. У меня есть некоторая логика, которую мне нужно реализовать, но я не знаю, как ее реализовать. Я не пытаюсь реализовать это каким-либо конкретным способом, метод — это то, о чем я спрашиваю.

3. Привет @Migwell. Я не думаю, что существует «лучшее» решение для вашего вопроса, но было бы очень любопытно узнать, как вы решили это сделать и почему. Спасибо.

4. Единственный (более) надежный способ, которым, на мой взгляд, это можно сделать, — это установить AFTER INSERT триггер на уровне базы данных, который установил is_admin бы TRUE значение, если это первая (единственная) запись в базе данных.