New code for performing explicit joins with custom join conditions.
* added ExtendedManager.join_custom_field(), which uses the introspection magic from populate_relationships (now factored out) to infer the type of relationship between two models and construct the correct join.  join_custom_field() presents a much simpler, more Django-y interface for doing this sort of thing -- compare with add_join() above it.
* changed TKO custom fields code to use join_custom_field()
* added some cases to AFE rpc_interface_unittest to ensure populate_relationships() usage didn't break
* simplified _CustomQuery and got rid of _CustomSqlQ.  _CustomQuery can do the work itself and its cleaner this way.
* added add_where(), an alternative to extra(where=...) that fits more into Django's normal representation of WHERE clauses, and therefore supports & and | operators later

Signed-off-by: Steve Howard <showard@google.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@4155 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/tko/models.py b/frontend/tko/models.py
index 429473d..7348b07 100644
--- a/frontend/tko/models.py
+++ b/frontend/tko/models.py
@@ -327,12 +327,11 @@
         second_join_condition = ('%s.id = %s.testlabel_id' %
                                  (second_join_alias,
                                   'tko_test_labels_tests' + suffix))
-        filter_object = self._CustomSqlQ()
-        filter_object.add_join('tko_test_labels',
-                               second_join_condition,
-                               query_set.query.LOUTER,
-                               alias=second_join_alias)
-        return self._add_customSqlQ(query_set, filter_object)
+        query_set.query.add_custom_join('tko_test_labels',
+                                        second_join_condition,
+                                        query_set.query.LOUTER,
+                                        alias=second_join_alias)
+        return query_set
 
 
     def _get_label_ids_from_names(self, label_names):
@@ -373,12 +372,10 @@
 
 
     def _join_label_column(self, query_set, label_name, label_id):
-        table_name = TestLabel.tests.field.m2m_db_table()
         alias = 'label_' + label_name
-        condition = "%s.testlabel_id = %s" % (_quote_name(alias), label_id)
-        query_set = self.add_join(query_set, table_name,
-                                  join_key='test_id', join_condition=condition,
-                                  alias=alias, force_left_join=True)
+        label_query = TestLabel.objects.filter(name=label_name)
+        query_set = Test.objects.join_custom_field(query_set, label_query,
+                                                   alias)
 
         query_set = self._add_select_ifnull(query_set, alias, label_name)
         return query_set
@@ -392,23 +389,21 @@
         return query_set
 
 
-    def _join_attribute(self, test_view_query_set, attribute,
-                        alias=None, extra_join_condition=None):
+    def _join_attribute(self, query_set, attribute, alias=None,
+                        extra_join_condition=None):
         """
         Join the given TestView QuerySet to TestAttribute.  The resulting query
         has an additional column for the given attribute named
         "attribute_<attribute name>".
         """
-        table_name = TestAttribute._meta.db_table
         if not alias:
             alias = 'attribute_' + attribute
-        condition = "%s.attribute = '%s'" % (_quote_name(alias),
-                                             self.escape_user_sql(attribute))
+        attribute_query = TestAttribute.objects.filter(attribute=attribute)
         if extra_join_condition:
-            condition += ' AND (%s)' % extra_join_condition
-        query_set = self.add_join(test_view_query_set, table_name,
-                                  join_key='test_idx', join_condition=condition,
-                                  alias=alias, force_left_join=True)
+            attribute_query = attribute_query.extra(
+                    where=[extra_join_condition])
+        query_set = Test.objects.join_custom_field(query_set, attribute_query,
+                                                   alias)
 
         query_set = self._add_select_value(query_set, alias)
         return query_set
@@ -427,23 +422,18 @@
 
 
     def _join_one_iteration_key(self, query_set, result_key, first_alias=None):
-        table_name = IterationResult._meta.db_table
         alias = 'iteration_' + result_key
-        condition_parts = ["%s.attribute = '%s'" %
-                           (_quote_name(alias),
-                            self.escape_user_sql(result_key))]
+        iteration_query = IterationResult.objects.filter(attribute=result_key)
         if first_alias:
             # after the first join, we need to match up iteration indices,
             # otherwise each join will expand the query by the number of
             # iterations and we'll have extraneous rows
-            condition_parts.append('%s.iteration = %s.iteration' %
-                                   (_quote_name(alias),
-                                    _quote_name(first_alias)))
+            iteration_query = iteration_query.extra(
+                    where=['%s.iteration = %s.iteration'
+                           % (_quote_name(alias), _quote_name(first_alias))])
 
-        condition = ' and '.join(condition_parts)
-        # add a join to IterationResult
-        query_set = self.add_join(query_set, table_name, join_key='test_idx',
-                                  join_condition=condition, alias=alias)
+        query_set = Test.objects.join_custom_field(query_set, iteration_query,
+                                                   alias, left_join=False)
         # select the iteration value and index for this join
         query_set = self._add_select_value(query_set, alias)
         if not first_alias: