2016-01-21 111 views
3

(我使用SQLAlchemy的,sqlite3的,燒瓶SQLAlchemy的,瓶,& Python)的的SQLAlchemy從刪除許多一對多的關係

我實現一個待辦事項列表供稿用戶可以在其中創建帖子( class Post)並附加任務(class Task)到每個帖子。每個任務可以有很多帖子。每篇文章可以有很多任務。我遇到SQLAlchemy問題並從表中刪除。這裏是有趣的是:

  • 當用戶刪除已在它職位從數據庫中刪除任務(task.posts.count() == 0)成功
  • 當用戶刪除具有一個或多個任務的帖子(task.posts.count() > 0)從數據庫中刪除會引發錯誤。

這裏的錯誤:

sqlalchemy.exc.InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. 
To begin a new transaction with this Session, first issue Session.rollback(). 
Original exception was: DELETE statement on table 'tasks_posts' expected to delete 1 row(s); Only 0 were matched. 

這裏的郵政&任務模型& tasks_posts表:

class Post(db.Model): 
    __tablename__ = 'posts' 
    id = db.Column(db.Integer, primary_key=True) 
    body = db.Column(db.Text) 
    tasks = db.relationship('Task', secondary='tasks_posts', \ 
      backref=db.backref('post', lazy='joined'), \ 
      lazy='dynamic', cascade='all, delete-orphan', \ 
      single_parent=True) 
    user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 

class Task(db.Model): 
    __tablename__ = 'tasks' 
    id = db.Column(db.Integer, primary_key=True) 
    title = db.Column(db.String(24)) 
    description = db.Column(db.String(64)) 
    user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 
    posts = db.relationship('Post', secondary='tasks_posts', \ 
      backref=db.backref('task', lazy='joined'), \ 
      lazy='dynamic', cascade='all, delete-orphan', \ 
      single_parent=True) 

tasks_posts = db.Table('tasks_posts',\ 
     db.Column('task_id', db.Integer, db.ForeignKey('tasks.id')),\ 
     db.Column('post_id', db.Integer, db.ForeignKey('posts.id'))\ 
     ) 

這裏的視圖功能:

@main.route('/edit-task/delete/<int:id>', methods=['GET', 'POST']) 
def delete_task(id): 
    task = Task.query.get_or_404(id) 
    db.session.delete(task) 
    db.session.commit() 
    return redirect(url_for('.user', username=current_user.username)) 

我假設問題是,我錯誤地執行:

  • SQLAlchemy的
  • 許多一對多的關係
  • 或視圖功能

這裏的堆棧跟蹤的「級聯」功能:

File "...venv/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__ 
    return self.wsgi_app(environ, start_response) 
    File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app 
    response = self.make_response(self.handle_exception(e)) 
    File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception 
    reraise(exc_type, exc_value, tb) 
    File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app 
    response = self.full_dispatch_request() 
    File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request 
    rv = self.handle_user_exception(e) 
    File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception 
    reraise(exc_type, exc_value, tb) 
    File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1473, in full_dispatch_request 
    rv = self.preprocess_request() 
    File ".../venv/lib/python2.7/site-packages/flask/app.py", line 1666, in preprocess_request 
    rv = func() 
    File ".../app/auth/views.py", line 12, in before_request 
    if current_user.is_authenticated: 
    File ".../venv/lib/python2.7/site-packages/werkzeug/local.py", line 342, in __getattr__ 
    return getattr(self._get_current_object(), name) 
    File ".../venv/lib/python2.7/site-packages/werkzeug/local.py", line 301, in _get_current_object 
    return self.__local() 
    File ".../venv/lib/python2.7/site-packages/flask_login.py", line 47, in <lambda> 
    current_user = LocalProxy(lambda: _get_user()) 
    File ".../venv/lib/python2.7/site-packages/flask_login.py", line 858, in _get_user 
    current_app.login_manager._load_user() 
    File ".../venv/lib/python2.7/site-packages/flask_login.py", line 389, in _load_user 
    return self.reload_user() 
    File ".../venv/lib/python2.7/site-packages/flask_login.py", line 351, in reload_user 
    user = self.user_callback(user_id) 
    File ".../app/models.py", line 235, in load_user 
    return User.query.get(int(user_id)) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 829, in get 
    return self._get_impl(ident, loading.load_on_ident) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 853, in _get_impl 
    self.session, key, attributes.PASSIVE_OFF) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/loading.py", line 152, in get_from_identity 
    state._load_expired(state, passive) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/state.py", line 474, in _load_expired 
    self.manager.deferred_scalar_loader(self, toload) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/loading.py", line 664, in load_scalar_attributes 
    only_load_props=attribute_names) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/loading.py", line 219, in load_on_ident 
    return q.one() 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2528, in one 
    ret = list(self) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2571, in __iter__ 
    return self._execute_and_instances(context) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2584, in _execute_and_instances 
    close_with_result=True) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2575, in _connection_from_session 
    **kw) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 893, in connection 
    execution_options=execution_options) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 898, in _connection_for_bind 
    engine, execution_options) 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 313, in _connection_for_bind 
    self._assert_active() 
    File ".../venv/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 214, in _assert_active 
    % self._rollback_exception 
InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: DELETE statement on table 'tasks_posts' expected to delete 1 row(s); Only 0 were matched. 
+1

你不需要'backref'。你只需要在關係的每一邊都有一個'back_populates'。你基本上在兩邊都有同樣的事情。我懷疑這是搞亂了次表。 – univerio

+0

我已經改變了它,現在得到這個錯誤:InvalidRequestError:Mapper'Mapper | Post | posts'沒有屬性'任務' –

+0

您可能需要發佈完整的堆棧跟蹤。您發佈的代碼沒有提及「習慣」。 – univerio

回答

0

好的,所以我認爲這裏有一些事情可能會導致您的問題。第一件事是錯誤信息本身。這意味着數據庫認爲它應該刪除一些東西,但它不在那裏。我相信這是由您的delete-all orphansingle_parent=True引起的。

這是告訴sqlalchemy,PostTask都有一個令人困惑的single_parent!所以我相信你需要做的才能讓它起作用

  1. 定義只有一個模型的關係。現在你有兩種類定義關係的方式就是使用你的代碼。我建議是這樣的:
  class Post(db.Model): 
       __tablename__ = 'posts' 
       id = db.Column(db.Integer, primary_key=True) 
       body = db.Column(db.Text) 
       tasks = db.relationship('Task', secondary='tasks_posts', \ 
         backref=db.backref('post', lazy='joined'), \ 
         lazy='dynamic', cascade='all, delete-orphan', \ 
         single_parent=True) 
       user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 



      class Task(db.Model): 
       __tablename__ = 'tasks' 
       id = db.Column(db.Integer, primary_key=True) 
       title = db.Column(db.String(24)) 
       description = db.Column(db.String(64)) 
       user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 
  • 弄清楚你想如何將數據模型的工作。任何任務都可以在任何帖子中,任何帖子都可以有任何數量的任務?我認爲你應該重新思考Post自己的任務。您仍然可以共享不同的任務,但您需要清楚瞭解未來的數據模型。

  • 明確你要刪除的內容。我知道,當任務被刪除時,任務所在的每個帖子都應該被刪除,但對於我來說這是沒有意義的。循環播放正確的帖子和要刪除的任務。通過這種方式,您將更好地理解刪除和更乾淨的代碼。

  • 更新:

    documentation

    There are several possibilities here:

    • If there is a relationship() from Parent to Child, but there is not a reverse-relationship that links a particular Child to each Parent, SQLAlchemy will not have any awareness that when deleting this particular Child object, it needs to maintain the 「secondary」 table that links it to the Parent. No delete of the 「secondary」 table will occur.

    • If there is a relationship that links a particular Child to each Parent, suppose it’s called Child.parents, SQLAlchemy by default will load in the Child.parents collection to locate all Parent objects, and remove each row from the 「secondary」 table which establishes this link. Note that this relationship does not need to be bidrectional; SQLAlchemy is strictly looking at every relationship() associated with the Child object being deleted.

    • A higher performing option here is to use ON DELETE CASCADE directives with the foreign keys used by the database. Assuming the database supports this feature, the database itself can be made to automatically delete rows in the 「secondary」 table as referencing rows in 「child」 are deleted. SQLAlchemy can be instructed to forego actively loading in the Child.parents collection in this case using the passive_deletes directive on relationship(); see Using Passive Deletes for more details on this. Note again, these behaviors are only relevant to the secondary option used with relationship(). If dealing with association tables that are mapped explicitly and are not present in the secondary option of a relevant relationship(), cascade rules can be used instead to automatically delete entities in reaction to a related entity being deleted - see Cascades for information on this feature.

    +0

    感謝您的有益建議。這個想法是,一個帖子可以有零到多個任務(用戶可以一次完成多個任務)。用戶可以查看單個任務中的所有帖子。如果用戶決定刪除任務,則該任務中的帖子保持不變。 –

    +0

    好吧,於是我將SQLAlchemy的相關文檔添加到了我的答案中。但是在我看來,實際上你可能會談論一對多的關係(一個職位可能有很多任務)。然後你可以說'task.post'來獲取它所在的帖子。讓我知道這是否合理。 – pech0rin

    0

    它看起來像通過設置刪除級聯功能,您的意思是從tasks_posts刪除記錄。 這不是必需的,sql的鍊金術會自動完成。

    一般來說,你嘗試過配置你的關係,我建議先從簡單的設置是這樣的:

    class Post(ModelBase): 
        __tablename__ = 'posts' 
        id = Column(Integer, primary_key=True) 
        body = Column(Text) 
        user_id = Column(Integer, ForeignKey('users.id')) 
    
    
    class Task(ModelBase): 
        __tablename__ = 'tasks' 
        id = Column(Integer, primary_key=True) 
        title = Column(String(24)) 
        description = Column(String(64)) 
        user_id = Column(Integer, ForeignKey('users.id')) 
        posts = relationship(
         'Post', 
         secondary='tasks_posts', 
         backref='tasks') 
    

    backref,如已經在評論中提到的,只有在表中的一個需要。 以上我爲帖子指定了backref='tasks',這會自動在Post類中創建tasks關係。

    旁註:你沒有在relationship塊線的末端,在tasks_posts需要斜線,因爲這些數據塊自然地裹成括號

    0

    多虧了大家的幫助,我似乎已經想通了。我試圖實現的想法是單個帖子可以有零到多個任務(用戶可以一次完成多個任務)。用戶可以查看單個任務中的所有帖子。如果用戶決定刪除任務,則該任務中的帖子保持不變。

    class Post(db.Model): 
        __tablename__ = 'posts' 
        id = db.Column(db.Integer, primary_key=True) 
        body = db.Column(db.Text) 
        user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 
        tasks = db.relationship('Task', secondary='tasks_posts', backref='post', lazy='dynamic') 
    
    class Task(db.Model): 
        __tablename__ = 'tasks' 
        id = db.Column(db.Integer, primary_key=True) 
        title = db.Column(db.String(24)) 
        description = db.Column(String(64)) 
        user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 
    
    tasks_posts = db.Table('tasks_posts', 
         db.Column('task_id', db.Integer, db.ForeignKey('tasks.id')), 
         db.Column('post_id', db.Integer, db.ForeignKey('posts.id')) 
         ) 
    
    相關問題