У меня есть объектная модель, которая использует систему «действий» для отслеживания изменений в объекте. Эти действия могут быть добавлены и считаются «предложенными» до тех пор, пока они не будут утверждены с помощью действия «одобрить». Полный список действий сокращен до «состояний» объектов, которые также хранятся в базе данных, для более быстрого запроса.
Поскольку существуют «предлагаемые» действия, у каждого объекта также есть второй объект состояния, который отслеживает «diff», которые в основном являются изменениями состояния для всего в предлагаемом (все остальные поля установлены в NULL). Наконец, каждое состояние также отслеживает последнее действие для отображения «последнего обновления» в пользовательском интерфейсе.
Вот мой объект:
class Subject(Base):
__tablename__ = 'subjects'
id = Column(UUID(as_uuid=True), primary_key=True)
state_id = Column(UUID(as_uuid=True), ForeignKey('states.id', name="fk_state"))
diff_id = Column(UUID(as_uuid=True), ForeignKey('states.id', name="fk_state_diff"))
state = relationship('State', back_populates='subject', foreign_keys=[state_id], cascade='all, delete-orphan', lazy='joined', innerjoin=True, uselist=False, single_parent=True, post_update=True)
diff = relationship('State', back_populates='subject', foreign_keys=[diff_id], cascade='all, delete-orphan', lazy='joined', innerjoin=True, uselist=False, single_parent=True, post_update=True)
actions = relationship('Action', back_populates='subject', cascade='all, delete-orphan', order_by='Action.time', single_parent=True)
Как видите, он имеет отношение к этому объекту Action:
class Action(Base):
__tablename__ = 'actions'
id = Column(UUID(as_uuid=True), primary_key=True)
subject_id = Column(UUID(as_uuid=True), ForeignKey('subjects.id'), nullable=False, index=True)
time = Column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)
# Other action properties here, omitted for brevity.
# ...
subject = relationship('Subject', back_populates='actions')
и оба они относятся к объекту State с его предлагаемыми (многие-ко-многим) и свойствами last_action.
proposed_action_data = Table(
'proposed_actions',
Base.metadata,
Column('actions_id', UUID(as_uuid=True), ForeignKey('actions.id')),
Column('state_id', UUID(as_uuid=True), ForeignKey('states.id'))
)
class State(Base):
__tablename__ = 'states'
id = Column(UUID(as_uuid=True), primary_key=True)
last_action_id = Column(UUID(as_uuid=True), ForeignKey('actions.id', name="fk_state_last_action"), nullable=True)
subject_id = Column(UUID(as_uuid=True), ForeignKey('subjects.id', name="fk_state_subject_id"), nullable=False)
# State properties goes here, ommitted for brevity.
# ...
last_action = relationship('Action', lazy='joined', foreign_keys=[last_action_id], post_update=True)
proposed = relationship('Action', secondary=proposed_action_data)
subject = relationship('Subject', foreign_keys=[subject_id)
Вставки, кажется, работают просто отлично - я могу установить состояние и разность, и оператор UPDATE
выдается для субъекта после оператора INSERT
, поэтому идентификаторы установлены.
Но если я хочу удалить Subject, оператор pre-delete UPDATE
, чтобы очистить state_id и diff_id, не запускается, поэтому он пытается удалить State и завершается ошибкой, поскольку на него все еще ссылаются:
session.delete(subject)
session.commit()
поднимает:
sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) update or delete on table "states" violates foreign key constraint "fk_state_diff" on table "subjects"
DETAIL: Key (id)=(363eb5bb-4042-44a9-b3fe-567406008f9e) is still referenced from table "subjects".
[SQL: 'DELETE FROM states WHERE states.id = %(id)s'] [parameters: ({'id': UUID('363eb5bb-4042-44a9-b3fe-567406008f9e')}, {'id': UUID('b7ca4f02-0fac-41bf-ae8b-44049ceb1855')})]
Если я очищаю состояние и diff вручную, я не получаю никаких исключений:
subject.state = None
subject.diff = None
session.delete(subject)
session.commit()
Что я здесь не так делаю?