OK well to do it exactly the way the example does it, each time we create a 
UserCourse, it will also create a Course.  That’s pretty simple, we use two 
association proxies, one for User.courses and the other for UserCourse.course, 
mappings are like this:

class User(Base):
    __tablename__ = 'users'

    # Columns
    id = Column(Integer, primary_key=True)
    name = Column(Text)

    # Relations
    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'

    # Columns
    id = Column(Integer, primary_key=True)
    title = Column(Text, unique=True)

    def __init__(self, title):
        self.title = title


# Composite association proxies linking users and preferences
class UserCourse(Base):
    __tablename__ = 'user_courses'

    # Columns
    user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
    course_id = Column(Integer, ForeignKey(Course.id), primary_key=True)
    grade = Column(Integer)

    # Relations
    user = relationship(
        User,
        backref=backref(
            'user_courses',
            collection_class=attribute_mapped_collection('course_title'),
            cascade='all, delete-orphan'
            )
        )
    course = relationship(Course)

    course_title = association_proxy("course", "title”)  # will create a new 
Course object when course_title is set

    def __init__(self, course_title, grade):
        self.course_title = course_title
        self.grade = grade

the other way that’s maybe a little more “real world” is that if two different 
UserCourse objects are for “math”, we’d want only one Course object with 
“math”.  There’s a few ways to go about making those unique Course objects - 
one common one is the “unique object” recipe at 
http://www.sqlalchemy.org/trac/wiki/UsageRecipes/UniqueObject .

A variant on that which I’ve been using lately doesn’t rely upon any kind of 
global session and instead uses events.    In this approach, we modify the 
above so that UserCourse.course_title temporarily points to a plain string, 
then when attached to a Session looks up and/or creates the unique Course 
object, looks like this:

from sqlalchemy import event

# same User and Course...

# Composite association proxies linking users and preferences
class UserCourse(Base):
    __tablename__ = 'user_courses'

    # Columns
    user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
    course_id = Column(Integer, ForeignKey(Course.id), primary_key=True)
    grade = Column(Integer)

    # Relations
    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  # temporary, will turn into a
                                           # Course when we attach to a Session
        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):
        # when UserCourse objects are attached to a Session,
        # figure out what Course in the database it should point to,
        # or create a new one.
        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


with either of these, a simple test run is like:

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)

user = User(name='u1')
s.add(user)
s.commit()

user.courses['math'] = 100
s.commit()

assert user.courses['math'] == 100





On Jan 8, 2014, at 6:32 PM, Brian Findlay <brian.m.find...@gmail.com> wrote:

> Hi, all. I've been trying to modify the example of a composite association 
> proxy 
> (http://www.sqlalchemy.org/docs/orm/extensions/associationproxy.html#composite-association-proxies)
>  to fit my needs.
> 
> In the documentation example, there is a User object, a Keyword object, and a 
> UserKeyword association object that stores a 'special_key' for each of a 
> user's keywords. In the provided example, the result is a collection of 
> dictionaries where the 'special_key' is the key and the 'keyword' is the 
> value. I'm trying to inverse that mapping.
> 
> In my particular use case (which I've simplified so as to make it as clear as 
> possible...I hope), I have a User object (a student), a Course object (an 
> academic course), and a UserCourse association object that stores each user's 
> grade for each course. My goal is to be able to set a student's grade 
> something like this:
> 
> user.course['math'] = 100
> 
> This is what I've come up with, but it (obviously) isn't working yet.
> 
> 
> from sqlalchemy import Column, Integer, Text, ForeignKey
> from sqlalchemy.ext.associationproxy import association_proxy
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.orm.collections import attribute_mapped_collection
> from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
> 
> 
> Base = declarative_base()
> 
> 
> class User(Base):
>     __tablename__ = 'users'
> 
>     # Columns
>     id = Column(Integer, primary_key=True)
>     name = Column(Text)
> 
>     # Relations
>     courses = association_proxy(
>         'user_courses',
>         'course',
>         creator=lambda k, v: UserCourse(course=k, grade=v)
>         )
> 
>     def __init__(self, name):
>         self.name = name
> 
> 
> class Course(Base):
>     __tablename__ = 'courses'
> 
>     # Columns
>     id = Column(Integer, primary_key=True)
>     title = Column(Text, unique=True)
> 
>     def __init__(self, title):
>         self.title = title
> 
> 
> # Composite association proxies linking users and preferences
> class UserCourse(Base):
>     __tablename__ = 'user_courses'
> 
>     # Columns
>     user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
>     course_id = Column(Integer, ForeignKey(Course.id), primary_key=True)
>     grade = Column(Integer)
> 
>     # Relations
>     user = relationship(
>         User,
>         backref=backref(
>             'user_courses',
>             collection_class=attribute_mapped_collection('grade'),
>             cascade='all, delete-orphan'
>             )
>         )
>     c = relationship('Course')
>     course = association_proxy('c', 'title')
> 
> 
> 
> I'd really appreciate anyone's help here, even if it's just showing me how to 
> modify the example in the documentation.
> 
> -- 
> 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/groups/opt_out.

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to