Thank you andrija and Micheal (especially recommending getting it working first without associationproxy -- that really helped)
Here's a solution I've got that works (not exactly as I intended, but I can live with it). I just keep the section and question paired together. from sqlalchemy import Column, MetaData, Table, types, ForeignKey, func from sqlalchemy.orm import mapper, relation from sqlalchemy.sql.expression import select, and_ from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm.collections import attribute_mapped_collection from datetime import datetime from formencode import validators from smmodels import NoHTML, SuperDateValidator metadata = MetaData() class SaqType: def __init__(self): pass def __str__(self): return 'id: %s name: %s' % (self.id, self.name) def __repr__(self): return "<Questionnaire Type(%s, name:'%s')>" % (self.id, self.name) class SaqJoin: def __init__(self, type_obj=None, question_obj=None, section_obj=None): self.type = type_obj self.question = question_obj self.section = section_obj def __repr__(self): return '<SaqJoin type,question,section:(%s,%s,%s)>' % (self.type_id, self.question_id, self.section_id) class Questionnaire: def __str__(self): return 'id: %s customer_id: %s' % (self.id, self.customer_id) def __repr__(self): return "<Questionnaire(%s, customer_id:%s)>" % (self.id, self.customer_id) question_types = ['yn','yn_na','label','comment'] class Question: pass class Section: def __init__(self): pass class Answer: def __init__(self): pass class CustomerPart: def __init__(self): pass sections_by_type = select( [join_table.c.type_id, join_table.c.section_id], group_by=[join_table.c.section_id, join_table.c.type_id]).alias('sections_by_type') mapper(Question, questions_table) mapper(Section, sections_table) mapper(CustomerPart, customer_table_part) mapper(Answer, answers_table, properties={ 'question' :relation(Question, backref='answer', uselist=False) }) mapper(SaqJoin, join_table, properties={ 'type' :relation(SaqType), 'section' :relation(Section, backref='parent', order_by=join_table.c.ord), 'question' :relation(Question, backref='parent', order_by=join_table.c.ord), }) mapper(SaqType, types_table, order_by=types_table.c.id, properties={ 'qs' :relation(SaqJoin, order_by=join_table.c.ord), 'my_sections' :relation(Section, secondary=sections_by_type, primaryjoin = types_table.c.id == sections_by_type.c.type_id, backref='type', order_by=sections_table.c.secnum), }) mapper(Questionnaire, questionnaire_table, properties={ 'answers' :relation(Answer, backref='questionnaire'), 'type' :relation(SaqType), 'customer' :relation(CustomerPart, backref='questionnaires') }) On Jun 17, 9:26 am, Michael Bayer <[EMAIL PROTECTED]> wrote: > any table that has more than just two foreign keys to remote tables > does not normally qualify as a "secondary" table (its only allowed for > certain edge cases which are not present here). The "Join" class > mapped to the "join_table" is the right approach. In which case, you > *definitely* don't want to be using "secondary" or "secondary_join" in > any case here. Any access from A->(Join)->B where you don't want to > "see" the Join object, you should use the associationproxy at that > point - but get the entire thing to work first without using > associationproxy, as its only a convenience extension. > > On Jun 16, 5:11 pm, Matt Haggard <[EMAIL PROTECTED]> wrote: > > > I've got a triple join table (joining three things together) and I'm > > really struggling to get it to work as I intend. I've pasted the full > > model at the bottom. I've struggled with this off and on for months > > now... I don't really understand how I can get SQLAlchemy to do what I > > want. > > > The join table has 4 columns: > > > type_id | section_id | question_id | ord > > > What I'd like is the following: > > > Questionnaire object with: > > .sections list that contain > > .questions list that contain: > > .answer > > > So, something like: > > q = Questionnaire() > > print q.sections[2].questions[1].answer > > # yield the answer to question 1 of section 2 (or question 2 of > > section 3 depending on your indexing :) ) > > > I encounter a problem because a Question object doesn't know what the > > type_id is because that is stored with the Questionnaire object. > > Likewise from a section object. I'm pulling my hair out... > > > Thanks, > > > Matt Haggard > > > -------------------------------------------- > > from sqlalchemy import Column, MetaData, Table, types, ForeignKey, > > func > > from sqlalchemy.orm import mapper, relation > > from sqlalchemy.sql.expression import select > > from sqlalchemy.ext.associationproxy import association_proxy > > > from datetime import datetime > > > from formencode import validators > > from smmodels import NoHTML, SuperDateValidator > > > metadata = MetaData() > > > types_table = Table('q_type', metadata, > > Column('id', types.Integer, primary_key=True, index=True), > > Column('name', types.Unicode), > > Column('longname', types.Unicode), > > Column('validation_type', types.Unicode) > > ) > > > customer_table_part = Table('customer', metadata, > > Column('id', types.Integer, primary_key=True, index=True), > > Column('email', types.Unicode, unique=True, index=True), > > Column('validation_type', types.Unicode) > > ) > > > questionnaire_table = Table('q_questionnaire', metadata, > > Column('id', types.Integer, primary_key=True, index=True), > > Column('type_id', types.Integer, ForeignKey('q_type.id')), > > Column('started', types.DateTime, default=datetime.now), > > Column('firstpage_firstdone', types.DateTime), > > Column('firstpassed', types.DateTime), > > Column('customer_id', types.Integer, ForeignKey('customer.id'), > > index=True), > > Column('q_busdescrip', types.Unicode), > > Column('q_dsedescrip', types.Unicode), > > Column('q_providedallip', types.Unicode), > > Column('q_company_name', types.Unicode), > > Column('q_dba', types.Unicode), > > Column('q_contact_name', types.Unicode), > > Column('q_phone', types.Unicode), > > Column('q_trans_num_yearly', types.Unicode), > > Column('q_title', types.Unicode), > > Column('grade', types.Unicode), > > Column('failing_sections', types.Integer, default=0), > > Column('incomplete_sections', types.Integer, default=0), > > Column('number_blacklisted', types.Integer, default=0), > > Column('lastupdated', types.DateTime, default=datetime.now), > > Column('expiration_date', types.DateTime) > > ) > > > questions_table = Table('q_questions_new', metadata, > > Column('id', types.Integer, primary_key=True, index=True), > > Column('display_number', types.Unicode), > > Column('question', types.Unicode), > > Column('type', types.Unicode), > > Column('weight', types.Integer), > > Column('correct_answer', types.Unicode, default=''), > > Column('dft_order', types.Integer) > > ) > > > sections_table = Table('q_sections_new', metadata, > > Column('id', types.Integer, primary_key=True, index=True), > > Column('secnum', types.Unicode), > > Column('name', types.Unicode), > > Column('longname', types.Unicode) > > ) > > > join_table = Table('q_join', metadata, > > Column('type_id', types.Integer, ForeignKey('q_type.id'), > > index=True, primary_key=True), > > Column('question_id', types.Integer, > > ForeignKey('q_questions_new.id'), index=True, primary_key=True), > > Column('section_id', types.Integer, > > ForeignKey('q_sections_new.id'), index=True, primary_key=True), > > Column('ord', types.Integer) > > ) > > > answers_table = Table('q_answers_new', metadata, > > Column('id', types.Integer, primary_key=True, index=True), > > Column('question_id', types.Integer, > > ForeignKey('q_questions_new.id'), index=True), > > Column('q_id', types.Integer, ForeignKey('q_questionnaire.id'), > > index=True), > > Column('answer', types.Unicode), > > Column('answerdate', types.DateTime, default=datetime.now), > > Column('markedby', types.Unicode, default='hand'), > > Column('visible', types.Boolean, default=True) > > ) > > > class qType: > > > allquestions = association_proxy('joinObj', 'questions') > > sections = association_proxy('joinObj', 'sections') > > > def __init__(self): > > pass > > > def __str__(self): > > return 'id: %s name: %s' % (self.id, self.name) > > > def __repr__(self): > > return "<Questionnaire Type(%s, name:'%s')>" % (self.id, > > self.name) > > > class qJoin: > > > def __init__(self, type_obj=None, question_obj=None, > > section_obj=None): > > self.type = type_obj > > self.question = question_obj > > self.section = section_obj > > > def __repr__(self): > > return '<qJoin type,question,section:(%s,%s,%s)>' % > > (self.type_id, self.question_id, self.section_id) > > > class Questionnaire: > > > def __str__(self): > > return 'id: %s customer_id: %s' % (self.id, self.customer_id) > > > def __repr__(self): > > return "<Questionnaire(%s, customer_id:%s)>" % (self.id, > > self.customer_id) > > > question_types = ['yn','yn_na','label','comment'] > > class Question: > > > #section = association_proxy('joinObj', 'section') > > > class Section: > > > def __init__(self): > > pass > > > def getQuestions(self, type_id): > > '''Query for questions belonging to both this section and > > type_id''' > > q = select([join_table.c.question_id, > > join_table.c.ord], > > whereclause=[join_table.c.type_id==type_id,join_table.c.section_id==self.id]) > > return q > > > class Answer: > > > def __init__(self): > > pass > > > class CustomerPart: > > > def __init__(self): > > pass > > > sections_by_type = select( > > [join_table.c.type_id, join_table.c.section_id], > > group_by=[join_table.c.section_id, > > join_table.c.type_id]).alias('sections_by_type') > > > questions_sections_by_tyoe = select( > > [join_table.c.type_id, join_table.c.question_id, > > join_table.c.section_id], > > group_by=[join_table.c.question_id, join_table.c.section_id, > > join_table.c.type_id]).alias('q_s_by_type') > > > mapper(Question, questions_table) > > mapper(Section, sections_table, properties={ > > 'questions' :relation(Question, secondary=join_table, > > primaryjoin = sections_table.c.id == join_table.c.section_id, > > secondaryjoin = join_table.c.question_id == > > questions_table.c.id, > > backref='section', order_by=join_table.c.ord),}) > > > mapper(CustomerPart, customer_table_part) > > mapper(Answer, answers_table, properties={ > > 'question' :relation(Question, backref='answer', uselist=False)}) > > > mapper(Join, join_table, properties={ > > 'type' :relation(qType), > > 'sections' :relation(Section, backref='parent'), > > 'questions' :relation(Question, backref='parent'),}) > > > mapper(qType, types_table, order_by=types_table.c.id, properties={ > > 'joinObj' :relation(qJoin), > > 'my_sections' :relation(Section, secondary=sections_by_type, > > primaryjoin = types_table.c.id == sections_by_type.c.type_id, > > backref='type', order_by=sections_table.c.secnum),}) > > > mapper(Questionnaire, questionnaire_table, properties={ > > 'answers' :relation(Answer, backref='questionnaire'), > > 'type' :relation(qType), > > 'customer' :relation(CustomerPart, backref='questionnaires') > > > }) > > --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---