a test case is attached, show me the failure please, thanks.
On 7/8/14, 10:59 AM, Brian Findlay wrote: > Hi Mike, > > I'm using your variant on the 'unique object' recipe (see previous > posting http://goo.gl/I1buRz) with some composite association proxies. > Recently, the data I've been working with introduced a duplicate in > the property I've been using with attribute_mapped_collection(), so > I'm trying to modify the collection class such that the key is based > on a column in the association object instead of a column in the > 'right' table. My modified mapping results in a FlushError when > attempting to update a UserCourse object because it conflicts with a > persistent instance. > > _*Basic model:*_ > class User(Base): > id = Column(Integer, primary_key=True) > name = Column(Text) > > class Course(Base): > id = Column(Integer, primary_key=True) > title = Column(Text, unique=True) > > class UserCourse(Base): > user_id = Column(Integer, ForeignKey(User.id), primary_key=True) > course_id = Column(Integer, ForeignKey(Course.id), primary_key=True) > grade = Column(Integer) > > _*Original use:*_ > user.courses['math'] = 100 # user.courses[course.name] = grade > > _*Desired use:*_ > user.courses['1'] = 100 # user.courses[course.id] = grade > > _*Original model:*_ > class User(Base): > __tablename__ = 'users' > id = Column(Integer, primary_key=True) > name = Column(Text) > courses = association_proxy('user_courses', 'grade', > creator=lambda k, v: UserCourse(course_title=k, grade=v)) > > def __init__(self, name): > self.name = name > > > class Course(Base): > __tablename__ = 'courses' > id = Column(Integer, primary_key=True) > title = Column(Text, unique=True) > > def __init__(self, title): > self.title = title > > class UserCourse(Base): > __tablename__ = 'user_courses' > user_id = Column(Integer, ForeignKey(User.id), primary_key=True) > course_id = Column(Integer, ForeignKey(Course.id), primary_key=True) > grade = Column(Integer) > user = relationship( > User, > backref=backref( > 'user_courses', > collection_class=attribute_mapped_collection('course_title'), > cascade='all, delete-orphan' > ) > ) > course = relationship(Course) > > def __init__(self, course_title, grade): > self._course_title = course_title > self.grade = grade > > @property > def course_title(self): > if self.course is not None: > return self.course.title > else: > return self._course_title > > @event.listens_for(Session, "after_attach") > def after_attach(session, instance): > if isinstance(instance, UserCourse): > with session.no_autoflush: > course = > session.query(Course).filter_by(title=instance._course_title).first() > if course is None: > course = Course(title=instance._course_title) > instance.course = course > > > _*Error-producing model modified for desired use:*_ > class User(Base): > __tablename__ = 'users' > id = Column(Integer, primary_key=True) > name = Column(Text) > courses = association_proxy('user_courses', 'grade', > creator=lambda k, v: UserCourse(course_id=k, grade=v)) > > def __init__(self, name): > self.name = name > > > class Course(Base): > __tablename__ = 'courses' > id = Column(Integer, primary_key=True) > title = Column(Text, unique=True) > > def __init__(self, title): > self.title = title > > class UserCourse(Base): > __tablename__ = 'user_courses' > user_id = Column(Integer, ForeignKey(User.id), primary_key=True) > course_id = Column(Integer, ForeignKey(Course.id), primary_key=True) > grade = Column(Integer) > user = relationship( > User, > backref=backref( > 'user_courses', > collection_class=attribute_mapped_collection('course_id'), > cascade='all, delete-orphan' > ) > ) > course = relationship(Course) > > def __init__(self, course_id, grade): > self._course_id = course_id > self.grade = grade > > @event.listens_for(Session, "after_attach") > def after_attach(session, instance): > if isinstance(instance, UserCourse): > with session.no_autoflush: > course = > session.query(Course).filter_by(id=instance._course_id).first() > # no way to create to Course object by id alone, but I > don't need that capability > # new UserCourse objects are limited to existing courses > instance.course = course > > > > Seems like there's a simple way to accomplish this as the dictionary > collection is now coming directly from the association object instead > of having to hop across it to the 'courses' table. Could you point me > in the right direction? Thanks. > > -- > 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 sqlalchemy+unsubscr...@googlegroups.com > <mailto:sqlalchemy+unsubscr...@googlegroups.com>. > To post to this group, send email to sqlalchemy@googlegroups.com > <mailto:sqlalchemy@googlegroups.com>. > Visit this group at http://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. -- 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 sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at http://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.
from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy import event Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(Text) courses = association_proxy('user_courses', 'grade', creator=lambda k, v: UserCourse(course_id=k, grade=v)) def __init__(self, name): self.name = name class Course(Base): __tablename__ = 'courses' id = Column(Integer, primary_key=True) title = Column(Text, unique=True) class UserCourse(Base): __tablename__ = 'user_courses' user_id = Column(Integer, ForeignKey(User.id), primary_key=True) course_id = Column(Integer, ForeignKey(Course.id), primary_key=True) grade = Column(Integer) user = relationship( User, backref=backref( 'user_courses', collection_class=attribute_mapped_collection('course_id'), cascade='all, delete-orphan' ) ) course = relationship(Course) def __init__(self, course_id, grade): self._course_id = course_id self.grade = grade @event.listens_for(Session, "after_attach") def after_attach(session, instance): if isinstance(instance, UserCourse): with session.no_autoflush: course = session.query(Course).filter_by(id=instance._course_id).first() # no way to create to Course object by id alone, but I don't need that capability # new UserCourse objects are limited to existing courses instance.course = course e = create_engine("sqlite://", echo=True) Base.metadata.create_all(e) sess = Session(e) course_one = Course(id=1, title='c1') course_two = Course(id=2, title='c2') sess.add_all([course_one, course_two]) sess.commit() u1 = User(name='u1') u2 = User(name='u1') sess.add_all([u1, u2]) sess.commit() u1.courses[1] = 100 u2.courses[1] = 90 sess.commit() assert u1.courses == {1: 100} assert u2.courses == {1: 90}