2012-03-29 9 views
9

Tengo una relación de varios a varios entre User sy Task s. Quiero que la "tabla secundaria" (es decir, la tabla que facilita la relación de muchos a muchos) se elimine cuando elimino un Task o User. ¿Cómo puedo configurar SQLAlchemy para esto?SQLAlchemy de Python no elimina la tabla secundaria (muchos a muchos)?

Aquí hay un ejemplo de código python que demuestra el problema que estoy teniendo. Nota: Este código es completamente autónomo y solo requiere el módulo sqlalchemy. Si copia y pega este código, debería poder ejecutarlo sin efectos secundarios y ver el mismo comportamiento usted mismo. La última línea del script muestra que la fila relevante en la "tabla secundaria" no se eliminó cuando eliminé la tarea correspondiente. Todas las aserciones pasan en este ejemplo.

from sqlalchemy import create_engine, Column, Integer, Text, Table, ForeignKey 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import Session, relationship 

Model = declarative_base() 

class User(Model): 
    __tablename__ = 'users' 
    id = Column('user_id', Integer, primary_key=True) 
    email = Column('email', Text, unique=True) 

    def __init__(self, email): 
     self.email = email 

user_tasks = Table('user_tasks', Model.metadata, 
    Column('user_id', Integer, ForeignKey('users.user_id')), 
    Column('task_id', Integer, ForeignKey('tasks.task_id'))) 

class Task(Model): 
    __tablename__ = 'tasks' 
    id = Column('task_id', Integer, primary_key=True) 
    description = Column('description', Text) 
    assigned_to = relationship('User', secondary=user_tasks, backref='tasks') 

    def __init__(self, description): 
     self.description = description 

if __name__ == '__main__': 
    engine = create_engine('sqlite:///:memory:') 
    Model.metadata.create_all(engine) 
    s = Session(engine) 
    the_user = User('user') 
    s.add(the_user) 
    s.commit() 
    assert s.query(User).all() == [the_user] 
    user_task = Task('user_one task') 
    user_task.assigned_to.append(the_user) 
    s.add(user_task) 
    s.commit() 
    assert s.query(Task).all() == [user_task] 
    assert s.query(user_tasks).all() == [(1,1)] 
    s.query(Task).delete() 
    s.commit() 
    assert s.query(Task).all() == [] 
    assert s.query(User).all() == [the_user] 
    assert s.query(user_tasks).all() == [(1,1)] # I was expecting [] . 

Respuesta

12

Ver delete(synchronize_session='evaluate'):

El método no ofrece en Python en cascada de las relaciones - se supone que ON DELETE CASCADE está configurado para cualquier referencia de clave externa que lo requieran. La sesión debe estar vencida (se produce automáticamente después de commit(), o call expire_all()) para que el estado de los objetos dependientes sujetos a delete o delete-huphan cascade se represente correctamente.

Es decir, SQLAlchemy no es capaz de encontrar todos los objetos de la tarea que va a eliminar y averiguar cada fila que desea borrar de user_tasks - la mejor manera de hacer esto es utilizar ON DELETE CASCADE en el extranjero llaves (no funciona con tablas MyISAM MySQL o SQLite si las claves externas no están habilitadas):

http://docs.sqlalchemy.org/en/latest/core/constraints.html#on-update-and-on-delete

+0

Creo que mi versión de sqlite3 (? 3.7.3 creo) soporta tablas extranjeras? ¿Debo activar esto específicamente? ¿O hay alguna otra forma de hacerlo funcionar con SQLite3? – Buttons840

+0

La característica de SQLite3 se describe en http://sqlite.org/foreignkeys.html. Si quisiera usar eso, usaría un detector de eventos en SQLAlchemy para configurar ese PRAGMA cada vez que se crea una nueva conexión (el evento "connect"). La otra forma de hacer esto es usar Session.delete() en cada objeto Task directamente, SQLA realizará el esfuerzo adicional para mantener la relación "assigned_to". – zzzeek

+0

El enlace http://docs.sqlalchemy.org/en/latest/core/schema.html#on-update-and-on-delete es válido, pero no encuentra el delimitador en 'on-update-and- on-delete'. Parece estar perdido, al igual que en la versión 0.9 y 1.0. – fedorqui

1

Cuando probé el código con sqlite su error de no dar pero cuando, he intentado con MySQL base de datos que ha obtenido un error

2012-03-29 10:43:15,330 INFO sqlalchemy.engine.base.Engine SELECT DATABASE() 
2012-03-29 10:43:15,331 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,332 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'character_set%%' 
2012-03-29 10:43:15,332 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,333 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names' 
2012-03-29 10:43:15,333 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,334 INFO sqlalchemy.engine.base.Engine SHOW COLLATION 
2012-03-29 10:43:15,334 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,337 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode' 
2012-03-29 10:43:15,338 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,339 INFO sqlalchemy.engine.base.Engine DESCRIBE `user_tasks` 
2012-03-29 10:43:15,339 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,355 INFO sqlalchemy.engine.base.Engine DESCRIBE `users` 
2012-03-29 10:43:15,355 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,356 INFO sqlalchemy.engine.base.Engine DESCRIBE `tasks` 
2012-03-29 10:43:15,356 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,357 INFO sqlalchemy.engine.base.Engine 
DROP TABLE user_tasks 
2012-03-29 10:43:15,357 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,439 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,440 INFO sqlalchemy.engine.base.Engine 
DROP TABLE users 
2012-03-29 10:43:15,440 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,573 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,573 INFO sqlalchemy.engine.base.Engine 
DROP TABLE tasks 
2012-03-29 10:43:15,573 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,623 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,624 INFO sqlalchemy.engine.base.Engine DESCRIBE `tasks` 
2012-03-29 10:43:15,624 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,632 INFO sqlalchemy.engine.base.Engine ROLLBACK 
2012-03-29 10:43:15,633 INFO sqlalchemy.engine.base.Engine DESCRIBE `users` 
2012-03-29 10:43:15,633 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,634 INFO sqlalchemy.engine.base.Engine ROLLBACK 
2012-03-29 10:43:15,634 INFO sqlalchemy.engine.base.Engine DESCRIBE `user_tasks` 
2012-03-29 10:43:15,634 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,635 INFO sqlalchemy.engine.base.Engine ROLLBACK 
2012-03-29 10:43:15,635 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE tasks (
    task_id INTEGER NOT NULL AUTO_INCREMENT, 
    description TEXT, 
    PRIMARY KEY (task_id) 
) 


2012-03-29 10:43:15,635 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,732 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,733 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE users (
    user_id INTEGER NOT NULL AUTO_INCREMENT, 
    email VARCHAR(20), 
    PRIMARY KEY (user_id), 
    UNIQUE (email) 
) 


2012-03-29 10:43:15,733 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,841 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,842 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE user_tasks (
    user_id INTEGER, 
    task_id INTEGER, 
    FOREIGN KEY(user_id) REFERENCES users (user_id), 
    FOREIGN KEY(task_id) REFERENCES tasks (task_id) 
) 


2012-03-29 10:43:15,842 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,959 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,964 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
2012-03-29 10:43:15,965 INFO sqlalchemy.engine.base.Engine INSERT INTO users (email) VALUES (%s) 
2012-03-29 10:43:15,965 INFO sqlalchemy.engine.base.Engine ('user',) 
2012-03-29 10:43:15,966 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:16,010 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
2012-03-29 10:43:16,010 INFO sqlalchemy.engine.base.Engine SELECT users.user_id AS users_user_id, users.email AS users_email 
FROM users 
2012-03-29 10:43:16,011 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:16,013 INFO sqlalchemy.engine.base.Engine INSERT INTO tasks (description) VALUES (%s) 
2012-03-29 10:43:16,014 INFO sqlalchemy.engine.base.Engine ('user_one task',) 
2012-03-29 10:43:16,015 INFO sqlalchemy.engine.base.Engine INSERT INTO user_tasks (user_id, task_id) VALUES (%s, %s) 
2012-03-29 10:43:16,016 INFO sqlalchemy.engine.base.Engine (1L, 1L) 
2012-03-29 10:43:16,016 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:16,085 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
2012-03-29 10:43:16,086 INFO sqlalchemy.engine.base.Engine SELECT tasks.task_id AS tasks_task_id, tasks.description AS tasks_description 
FROM tasks 
2012-03-29 10:43:16,086 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:16,087 INFO sqlalchemy.engine.base.Engine SELECT user_tasks.user_id AS user_tasks_user_id, user_tasks.task_id AS user_tasks_task_id 
FROM user_tasks 
2012-03-29 10:43:16,088 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:16,089 INFO sqlalchemy.engine.base.Engine SELECT user_tasks.user_id AS user_tasks_user_id, user_tasks.task_id AS user_tasks_task_id 
FROM user_tasks 
2012-03-29 10:43:16,089 INFO sqlalchemy.engine.base.Engine() 
[(1L, 1L)] 
2012-03-29 10:43:16,091 INFO sqlalchemy.engine.base.Engine DELETE FROM tasks 
2012-03-29 10:43:16,091 INFO sqlalchemy.engine.base.Engine() 
Traceback (most recent call last): 
    File "/tmp/test2.py", line 46, in <module> 
    s.query(Task).delete() 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2283, in delete 
    result = session.execute(delete_stmt, params=self._params) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 762, in execute 
    clause, params or {}) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1399, in execute 
    params) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1532, in _execute_clauseelement 
    compiled_sql, distilled_params 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1640, in _execute_context 
    context) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1633, in _execute_context 
    context) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 325, in do_execute 
    cursor.execute(statement, parameters) 
    File "/usr/lib64/python2.7/site-packages/MySQLdb/cursors.py", line 174, in execute 
    self.errorhandler(self, exc, value) 
    File "/usr/lib64/python2.7/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler 
    raise errorclass, errorvalue 
sqlalchemy.exc.IntegrityError: (IntegrityError) (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`test1`.`user_tasks`, CONSTRAINT `user_tasks_ibfk_2` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`task_id`))') 'DELETE FROM tasks'() 

Así que después de que vine a saber que sqlite no mantiene restricción de clave externa . Ahora cambio tu código y verifico el resultado.

from sqlalchemy import create_engine, Column, Integer, Text, Table, ForeignKey, String 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import Session, relationship 

Model = declarative_base() 


class User(Model): 
    __tablename__ = 'users' 
    id = Column('user_id', Integer, primary_key=True) 
    email = Column('email', String(length=20), unique=True) 

    def __init__(self, email): 
     self.email = email 

user_tasks = Table('user_tasks', Model.metadata, 
    Column('user_id', Integer, ForeignKey('users.user_id')), 
    Column('task_id', Integer, ForeignKey('tasks.task_id'))) 


class Task(Model): 
    __tablename__ = 'tasks' 
    id = Column('task_id', Integer, primary_key=True) 
    description = Column('description', Text) 
    assigned_to = relationship('User', secondary=user_tasks, backref='tasks') 

    def __init__(self, description): 
     self.description = description 

if __name__ == '__main__': 
    engine = create_engine('mysql://test:[email protected]/test', echo=True) 
    Model.metadata.drop_all(engine) 
    Model.metadata.create_all(engine) 
    s = Session(engine) 
    the_user = User('user') 
    s.add(the_user) 
    s.commit() 
    assert s.query(User).all() == [the_user] 
    user_task = Task('user_one task') 
    user_task.assigned_to.append(the_user) 
    s.add(user_task) 
    s.commit() 
    assert s.query(Task).all() == [user_task] 
    assert s.query(user_tasks).all() == [(1, 1)] 
    the_user.tasks = [] 
    s.query(Task).delete() 
    s.commit() 
    assert s.query(Task).all() == [] 
    assert s.query(User).all() == [the_user] 
    assert s.query(user_tasks).all() == [(1,1)] # I was expecting [] . 
Cuestiones relacionadas