m2m_versus_sqljoin.py
author Luke Opperman <luke@loppear.com>
Fri May 16 23:54:31 2008 -0500 (3 months ago)
changeset 0 5cf57a8ce206
permissions -rw-r--r--
SQLObject: Using ManyToMany and SQLRelatedJoin
        1 """
        2     >>> sqlhub.processConnection = connectionForURI('sqlite:/:memory:')
        3 
        4     >>> Term.createTable()
        5     []
        6     >>> Question.createTable(createJoinTables=False)
        7     []
        8     >>> tea = Term(name='Tea')
        9     >>> age = Term(name='Age')
       10     >>> why = Question(name='Why?')
       11     >>> what = Question(name='What?')
       12 
       13     >>> tea, age
       14     (<Term 1 name='Tea'>, <Term 2 name='Age'>)
       15     >>> why, what
       16     (<Question 1 name='Why?'>, <Question 2 name='What?'>)
       17 
       18 
       19     #### First, SQLRelatedJoin
       20 
       21     >>> list(tea.questions_srj)
       22     []
       23     >>> tea.addQuestion_srj(why)
       24     >>> tea.addQuestion_srj(what)
       25     >>> list(tea.questions_srj)
       26     [<Question 1 name='Why?'>, <Question 2 name='What?'>]
       27 
       28     >>> tea.removeQuestion_srj(what)
       29     >>> list(tea.questions_srj)
       30     [<Question 1 name='Why?'>]
       31 
       32     >>> list(why.terms_srj)
       33     [<Term 1 name='Tea'>]
       34 
       35 
       36     #### Second, ManyToMany
       37     
       38     >>> list(age.questions_m2m)
       39     []
       40     >>> age.questions_m2m.add(why)
       41     >>> age.questions_m2m.add(what)
       42     >>> list(age.questions_m2m)
       43     [<Question 1 name='Why?'>, <Question 2 name='What?'>]
       44 
       45     >>> age.questions_m2m.remove(what)
       46     >>> list(age.questions_m2m)
       47     [<Question 1 name='Why?'>]
       48     
       49     # Note, these are accessing the same underlying table, interchangeably
       50     >>> list(why.terms_m2m)
       51     [<Term 1 name='Tea'>, <Term 2 name='Age'>]
       52 
       53     # ManyToMany also has a convenience for creating new objects
       54     #  related to an existing one:
       55     >>> cat = what.terms_m2m.create(name='Cat')
       56     >>> cat
       57     <Term 3 name='Cat'>
       58     >>> list(cat.questions_srj)
       59     [<Question 2 name='What?'>]
       60     
       61     
       62     #### Selection filtering, aggregates etc is the same for both
       63     #     as a SelectResults we've been forcing with list()
       64     
       65     >>> list(why.terms_m2m.filter(Term.q.name.startswith('T')))
       66     [<Term 1 name='Tea'>]
       67     >>> why.terms_srj.count()
       68     2
       69     
       70     # Historical detail that should probably be cleaned up:
       71     #  these generate functionally-equivalent but not identical SQL clauses
       72     >>> print why.terms_srj
       73     SELECT term.id, term.name FROM term, question, question_term WHERE ((term.id = question_term.term_id) AND ((question_term.question_id = question.id) AND (question.id = 1)))
       74     >>> print why.terms_m2m
       75     SELECT term.id, term.name FROM term, question_term WHERE (((term.id) = (question_term.term_id)) AND ((question_term.question_id) = (1)))
       76 
       77 """
       78 
       79 from sqlobject import *
       80 
       81 
       82 class Term(SQLObject):
       83     name = StringCol()
       84 
       85     questions_m2m = ManyToMany('Question')
       86     questions_srj = SQLRelatedJoin('Question', addRemoveName='Question_srj')
       87     
       88     term_questions_o2m = OneToMany('TermQuestion')
       89     term_questions_smj = SQLMultipleJoin('TermQuestion')
       90 
       91 class Question(SQLObject):
       92     name = StringCol()
       93 
       94     terms_m2m = ManyToMany('Term')
       95     terms_srj = SQLRelatedJoin('Term')
       96 
       97     question_terms_o2m = OneToMany('TermQuestion')
       98     question_terms_smj = SQLMultipleJoin('TermQuestion')
       99 
      100 class TermQuestion(SQLObject):
      101     """
      102      Finally, you can define a class for the intermediate table
      103           that uses OneToMany / SQLMultipleJoin for the references
      104           and directly create instances of it, but if you add any
      105           additional columns to this table you can no longer use the
      106           add/remove/create convenience methods. You can still use
      107           the ManyToMany / SQLRelatedJoins to select.
      108     
      109     This requires that we create the tables a little differently,
      110           as SQLObject wants a single primary key column for its classes.
      111     
      112     >>> TermQuestion.dropTable()
      113     >>> TermQuestion.createTable()
      114     []
      115     >>> dog = Term(name='Dog')
      116     >>> why = Question.selectBy(name='Why?').getOne()
      117     >>> list(dog.term_questions_smj)
      118     []
      119     >>> TermQuestion(term=dog, question=why)
      120     <TermQuestion 1 termID=4 questionID=1>
      121     >>> list(dog.term_questions_o2m)
      122     [<TermQuestion 1 termID=4 questionID=1>]
      123 
      124     """
      125     class sqlmeta:
      126         table = 'question_term'
      127 
      128     term = ForeignKey('Term')
      129     question = ForeignKey('Question')
      130