Thanks.
I didn't use association proxy.
> "because you can't add Tool to Person.tools". This is real code and I can
directly append the tool instance.
class Association(db.Model):
...
id = db.Column(Integer, primary_key=True, index=True)
tool_id = db.Column(Integer, ForeignKey('tools.id'))
user_id = db.Column(Integer, ForeignKey(users.id'))
user = db.relationship('User', back_populates='tool')
tool = db.relationship('Tool', back_populates='user')
On Friday, March 8, 2019 at 8:34:53 AM UTC-5, Mike Bayer wrote:
>
> On Thu, Mar 7, 2019 at 4:46 PM Conferency <[email protected] <javascript:>>
> wrote:
> >
> > Hi, I have 3 classes: two have many to many relationship between them,
> one is association class.
> >
> > class Person:
> > ...
> > tools = relationship('Association', back_populates='user',
> lazy=True, cascade='all, delete-orphan')
> >
> > class Tool:
> > ...
> > users = relationship('Association', back_populates='tool', lazy=True,
> cascade='all, delete-orphan')
> >
> >
> > One person object has 3 tools [<Tool id=1>, <Tool id=2>, <Tool id=3>].
> When I update the tool of the person I did
> >
> > person.tools = []
> > for tool_id in [2, 4, 5]:
> > tool = Tool.query.get(tool_id)
> > if tool:
> > person.tools.append(tool)
> >
> >
> > This operation fails, since when I set tools to an empty list, tool
> 1,2,3 are deleted, so only tool 4,5 are added into the list. If I remove
> the delete-orphan in the cascade, I will have some redundant data in the
> database. I can remove the unwanted tools from person.tools first instead
> of setting it to empty list. I'd like to know if there is a simple way to
> achieve same result. Thank you.
>
> that's not the real code because you can't add Tool to Person.tools,
> it refers to an "Association" object which is not pictured. Are you
> using the association proxy?
>
> If I take the example from the docs at
>
> https://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html#simplifying-association-objects,
>
>
> I can run the same code you have and at the end the state is correct.
> It does delete the association objects that are removed, but it then
> recreates them on the append pass. If you want it to maintain the
> association objects that are already there, you need to manipulate the
> association objects directly. Try out the script below.
>
> from sqlalchemy import Column, Integer, String, ForeignKey
> from sqlalchemy.orm import relationship, backref
>
> from sqlalchemy.ext.associationproxy import association_proxy
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy import create_engine
> from sqlalchemy.orm import Session
>
> Base = declarative_base()
>
>
> class User(Base):
> __tablename__ = "user"
> id = Column(Integer, primary_key=True)
> name = Column(String(64))
>
> # association proxy of "user_keywords" collection
> # to "keyword" attribute
> keywords = association_proxy("user_keywords", "keyword")
>
> def __init__(self, name):
> self.name = name
>
>
> class UserKeyword(Base):
> __tablename__ = "user_keyword"
> user_id = Column(Integer, ForeignKey("user.id"), primary_key=True)
> keyword_id = Column(Integer, ForeignKey("keyword.id"),
> primary_key=True)
> special_key = Column(String(50))
>
> # bidirectional attribute/collection of "user"/"user_keywords"
> user = relationship(
> User, backref=backref("user_keywords", cascade="all,
> delete-orphan")
> )
>
> # reference to the "Keyword" object
> keyword = relationship("Keyword")
>
> def __init__(self, keyword=None, user=None, special_key=None):
> self.user = user
> self.keyword = keyword
> self.special_key = special_key
>
>
> class Keyword(Base):
> __tablename__ = "keyword"
> id = Column(Integer, primary_key=True)
> keyword = Column("keyword", String(64))
>
> def __init__(self, keyword):
> self.keyword = keyword
>
> def __repr__(self):
> return "Keyword(%s)" % repr(self.keyword)
>
>
> e = create_engine("sqlite://", echo=True)
> Base.metadata.create_all(e)
>
> s = Session(e)
>
> u1 = User("foo")
> k1, k2, k3, k4, k5 = (
> Keyword("k1"),
> Keyword("k2"),
> Keyword("k3"),
> Keyword("k4"),
> Keyword("k5"),
> )
>
> u1.keywords = [k1, k2, k3]
> s.add_all([u1, k1, k2, k3, k4, k5])
> s.commit()
>
>
> u1.keywords = []
> for kname in ["k2", "k4", "k5"]:
> k = s.query(Keyword).filter_by(keyword=kname).one()
> u1.keywords.append(k)
>
> s.commit()
>
> print(u1.keywords)
>
>
> output at the end:
>
> [Keyword('k2'), Keyword('k4'), Keyword('k5')]
>
>
>
>
> >
> > --
> > SQLAlchemy -
> > The Python SQL Toolkit and Object Relational Mapper
> >
> > http://www.sqlalchemy.org/
> >
> > To post example code, please provide an MCVE: Minimal, Complete, and
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full
> description.
> > ---
> > You received this message because you are subscribed to the Google
> Groups "sqlalchemy" group.
> > To unsubscribe from this group and stop receiving emails from it, send
> an email to [email protected] <javascript:>.
> > To post to this group, send email to [email protected]
> <javascript:>.
> > Visit this group at https://groups.google.com/group/sqlalchemy.
> > For more options, visit https://groups.google.com/d/optout.
>
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.