Hi Mike (et al.),

I'm searching for a way to achieve defaultdict-like functionality for 
association proxies, so that a function that refers to a collection (or key 
within that collection) before it exists can create the collection/key with 
a default value.

In a previous post 
(https://groups.google.com/forum/#!msg/sqlalchemy/kxU-FaDGO2Q/b8ScnTXvPyIJ) 
you helped me to set up a composite association proxy, where I had a User 
object, a Course object, and a UserCourse object with keys to the User and 
Course objects as well as users' grades for each course.


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 proxy linking users and courses with grade
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


I've since added an event listener to perform a calculation each time a 
UserCourse object is set:


# Recalculate 'bar' after updating UserCourse
@event.listens_for(UserCourse.grade, 'set')
def foo(target, value, oldvalue, initiator):
    courses = DBSession.query(Course).all()
    user = User.from_id(target.user_id)
    bar = 0
    for course in courses:
        bar += user.courses[course.title]
    user.bar = bar


Here, 'bar' is some calculation involving a user's grade for each course. 
This is a somewhat contrived model (my application isn't really about 
courses and grades), but I thought it'd help to simplify my use case.

There are no issues when a user, the courses, and the user's grades already 
exist in the database. However, when a new user submits a form with course 
grades in it, the 'foo' function is triggered and I get

AttributeError: 'NoneType' object has no attribute 'courses'

with the traceback pointing to the line in the 'foo' function that refers 
to user.courses[course.title]. I understand that columns default to the 
NoneType type when the type is None or omitted, so is this a 
timing/sequencing issue with my listener? Should I be using something other 
than 'set' (or add another listener that is triggered first)?

If I manually enter some course grades into the database with psql, I get a 
KeyError on the first course I didn't manually input, hence the request for 
defaultdict-like functionality. That would at least help with the KeyError.

How would you recommend tackling these problems?

Thanks,
Brian

-- 
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.

Reply via email to