2016-01-21 90 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')) 
         ) 
    
    相关问题