简体   繁体   中英

SQLAlchemy how to update children when old = [1, 2] new = [2, 3]

I have two models:

class Person(Model):
    id
    name
    skills = relationship(Skill)

class Skill(Model):
    id
    skill
    person_id

At the beginning, for example:

jack = Person(name='jack')
jack.skills = [Skill(s) for s in ['python', 'ruby']]
jack.save()

Then, one day, jack lost his skill 'ruby' but earned 'swift' so his skill is ['python', 'swift'].

My current way of doing this update is:

  1. look for existing skills, i get old = ['python', 'ruby']
  2. get the new list new = ['python', 'swift']
  3. make old, new to set(old), set(new)
  4. unchanged = old.intersection(new) , so i get the skill that does not change
  5. I add every skill in set(new - unchanged)
  6. I delete every skill in set(old-unchanged)

Is there a easier way to do this?

Use collection_class=set on the relationship to treat it as a set instead of a list.

Here's a working example of how to relate people with skills. This is a many-to-many relationship, instead of each skill being related to one person_id, each skill can be related to many people through the person_skill table. The relationship collection is a set , and Skill has a __hash__ function to make skills with the same name hash the same.

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

engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base(bind=engine)
Session = sessionmaker(bind=engine)
session = Session()


class Person(Base):
    __tablename__ = 'person'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    # many-to-many relation, as a set
    skills = relationship('Skill', 'person_skill', collection_class=set)


class Skill(Base):
    __tablename__ = 'skill'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)

    def __hash__(self):
        # so that the set collection will handle duplicate entries
        return hash((self.__class__, self.name))


# many-to-many table, relate a person to a skill
person_skill = Table(
    'person_skill', Base.metadata,
    Column('person_id', Integer, ForeignKey(Person.id), primary_key=True),
    Column('skill_id', Integer, ForeignKey(Skill.id), primary_key=True)
)


# create the tables
Base.metadata.create_all()

# populate some skills and people
s1 = Skill(name='python')
s2 = Skill(name='sqlalchemy')
s3 = Skill(name='questions')
s4 = Skill(name='ruby')

p1 = Person(name='davidism', skills={s1, s2, s4})
p2 = Person(name='user2653947', skills={s3})

session.add_all([p1, p2])
session.commit()

# change some skills on people
p1.skills.discard(s4)
p2.skills.add(s2)
session.commit()

This is not a complete solution. You could for instance plug in the unique object pattern demonstrated in this answer to make sure the skills you create are never duplicated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM