Upgrade from Django 0.96 to Django 1.0.2.

Risk: high (framework change)
Visibility: medium

Signed-off-by: James Ren <jamesren@google.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@3457 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/afe/model_logic.py b/frontend/afe/model_logic.py
index c94233b..f8dc06d 100644
--- a/frontend/afe/model_logic.py
+++ b/frontend/afe/model_logic.py
@@ -2,13 +2,17 @@
 Extensions to Django's model logic.
 """
 
+import re
+import django.core.exceptions
 from django.db import models as dbmodels, backend, connection
+from django.db.models.sql import query
 from django.utils import datastructures
 from autotest_lib.frontend.afe import readonly_connection
 
+
 class ValidationError(Exception):
     """\
-    Data validation error in adding or updating an object.  The associated
+    Data validation error in adding or updating an object. The associated
     value is a dictionary mapping field names to error strings.
     """
 
@@ -24,6 +28,11 @@
         return wrapper_method
 
 
+def _quote_name(name):
+    """Shorthand for connection.ops.quote_name()."""
+    return connection.ops.quote_name(name)
+
+
 def _wrap_generator_with_readonly(generator):
     """
     We have to wrap generators specially.  Assume it performs
@@ -64,18 +73,19 @@
     QuerySet object that performs all database queries with the read-only
     connection.
     """
-    def __init__(self, model=None):
-        super(ReadonlyQuerySet, self).__init__(model)
+    def __init__(self, model=None, *args, **kwargs):
+        super(ReadonlyQuerySet, self).__init__(model, *args, **kwargs)
         _make_queryset_readonly(self)
 
 
     def values(self, *fields):
-        return self._clone(klass=ReadonlyValuesQuerySet, _fields=fields)
+        return self._clone(klass=ReadonlyValuesQuerySet,
+                           setup=True, _fields=fields)
 
 
 class ReadonlyValuesQuerySet(dbmodels.query.ValuesQuerySet):
-    def __init__(self, model=None):
-        super(ReadonlyValuesQuerySet, self).__init__(model)
+    def __init__(self, model=None, *args, **kwargs):
+        super(ReadonlyValuesQuerySet, self).__init__(model, *args, **kwargs)
         _make_queryset_readonly(self)
 
 
@@ -84,57 +94,33 @@
     Extended manager supporting subquery filtering.
     """
 
-    class _CustomJoinQ(dbmodels.Q):
-        """
-        Django "Q" object supporting a custom suffix for join aliases.See
-        filter_custom_join() for why this can be useful.
-        """
+    class _CustomQuery(query.Query):
+        def clone(self, klass=None, **kwargs):
+            obj = super(ExtendedManager._CustomQuery, self).clone(
+                klass, _customSqlQ=self._customSqlQ)
 
-        def __init__(self, join_suffix, **kwargs):
-            super(ExtendedManager._CustomJoinQ, self).__init__(**kwargs)
-            self._join_suffix = join_suffix
+            customQ = kwargs.get('_customSqlQ', None)
+            if customQ is not None:
+                obj._customSqlQ._joins.update(customQ._joins)
+                obj._customSqlQ._where.extend(customQ._where)
+                obj._customSqlQ._params.extend(customQ._params)
 
+            return obj
 
-        @staticmethod
-        def _substitute_aliases(renamed_aliases, condition):
-            for old_alias, new_alias in renamed_aliases:
-                    condition = condition.replace(backend.quote_name(old_alias),
-                                                  backend.quote_name(new_alias))
-            return condition
+        def get_from_clause(self):
+            from_, params = super(
+                ExtendedManager._CustomQuery, self).get_from_clause()
 
+            join_clause = ''
+            for join_alias, join in self._customSqlQ._joins.iteritems():
+                join_table, join_type, condition = join
+                join_clause += ' %s %s AS %s ON (%s)' % (
+                    join_type, join_table, join_alias, condition)
 
-        @staticmethod
-        def _unquote_name(name):
-            'This may be MySQL specific'
-            if backend.quote_name(name) == name:
-                return name[1:-1]
-            return name
+            if join_clause:
+                from_.append(join_clause)
 
-
-        def get_sql(self, opts):
-            joins, where, params = (
-                super(ExtendedManager._CustomJoinQ, self).get_sql(opts))
-
-            new_joins = datastructures.SortedDict()
-
-            # rename all join aliases and correct references in later joins
-            renamed_tables = []
-            # using iteritems seems to mess up the ordering here
-            for alias, (table, join_type, condition) in joins.items():
-                alias = self._unquote_name(alias)
-                new_alias = alias + self._join_suffix
-                renamed_tables.append((alias, new_alias))
-                condition = self._substitute_aliases(renamed_tables, condition)
-                new_alias = backend.quote_name(new_alias)
-                new_joins[new_alias] = (table, join_type, condition)
-
-            # correct references in where
-            new_where = []
-            for clause in where:
-                new_where.append(
-                    self._substitute_aliases(renamed_tables, clause))
-
-            return new_joins, new_where, params
+            return from_, params
 
 
     class _CustomSqlQ(dbmodels.Q):
@@ -154,8 +140,22 @@
             self._params.extend(params)
 
 
-        def get_sql(self, opts):
-            return self._joins, self._where, self._params
+        def add_to_query(self, query, aliases):
+            if self._where:
+                where = ' AND '.join(self._where)
+                query.add_extra(None, None, (where,), self._params, None, None)
+
+
+    def _add_customSqlQ(self, query_set, filter_object):
+        """\
+        Add a _CustomSqlQ to the query set.
+        """
+        # Make a copy of the query set
+        query_set = query_set.all()
+
+        query_set.query = query_set.query.clone(
+            ExtendedManager._CustomQuery, _customSqlQ=filter_object)
+        return query_set.filter(filter_object)
 
 
     def add_join(self, query_set, join_table, join_key,
@@ -168,8 +168,8 @@
         @param join_condition extra condition for the ON clause of the join
         @param suffix suffix to add to join_table for the join alias
         @param exclude if true, exclude rows that match this join (will use a
-        LEFT JOIN and an appropriate WHERE condition)
-        @param force_left_join - if true, a LEFT JOIN will be used instead of an
+        LEFT OUTER JOIN and an appropriate WHERE condition)
+        @param force_left_join - if true, a LEFT OUTER JOIN will be used instead of an
         INNER JOIN regardless of other options
         """
         join_from_table = self.model._meta.db_table
@@ -181,9 +181,9 @@
         if join_condition:
             full_join_condition += ' AND (' + join_condition + ')'
         if exclude or force_left_join:
-            join_type = 'LEFT JOIN'
+            join_type = query_set.query.LOUTER
         else:
-            join_type = 'INNER JOIN'
+            join_type = query_set.query.INNER
 
         filter_object = self._CustomSqlQ()
         filter_object.add_join(join_table,
@@ -192,22 +192,13 @@
                                alias=join_alias)
         if exclude:
             filter_object.add_where(full_join_key + ' IS NULL')
-        return query_set.filter(filter_object).distinct()
 
-
-    def filter_custom_join(self, join_suffix, **kwargs):
-        """
-        Just like Django filter(), but allows the user to specify a custom
-        suffix for the join aliases involves in the filter.  This makes it
-        possible to join against a table multiple times (as long as a different
-        suffix is used each time), which is necessary for certain queries.
-        """
-        filter_object = self._CustomJoinQ(join_suffix, **kwargs)
-        return self.complex_filter(filter_object)
+        query_set = self._add_customSqlQ(query_set, filter_object)
+        return query_set.distinct()
 
 
     def _get_quoted_field(self, table, field):
-        return (backend.quote_name(table) + '.' + backend.quote_name(field))
+        return _quote_name(table) + '.' + _quote_name(field)
 
 
     def get_key_on_this_table(self, key_field=None):
@@ -222,12 +213,15 @@
 
 
     def _custom_select_query(self, query_set, selects):
-        query_selects, where, params = query_set._get_sql_clause()
-        if query_set._distinct:
+        sql, params = query_set.query.as_sql()
+        from_ = sql[sql.find(' FROM'):]
+
+        if query_set.query.distinct:
             distinct = 'DISTINCT '
         else:
             distinct = ''
-        sql_query = 'SELECT ' + distinct + ','.join(selects) + where
+
+        sql_query = ('SELECT ' + distinct + ','.join(selects) + from_)
         cursor = readonly_connection.connection().cursor()
         cursor.execute(sql_query, params)
         return cursor.fetchall()
@@ -549,8 +543,36 @@
         return errors
 
 
+    def _validate(self):
+        """
+        First coerces all fields on this instance to their proper Python types.
+        Then runs validation on every field. Returns a dictionary of
+        field_name -> error_list.
+
+        Based on validate() from django.db.models.Model in Django 0.96, which
+        was removed in Django 1.0. It should reappear in a later version. See:
+            http://code.djangoproject.com/ticket/6845
+        """
+        error_dict = {}
+        for f in self._meta.fields:
+            try:
+                python_value = f.to_python(
+                    getattr(self, f.attname, f.get_default()))
+            except django.core.exceptions.ValidationError, e:
+                error_dict[f.name] = str(e.message)
+                continue
+
+            if not f.blank and not python_value:
+                error_dict[f.name] = 'This field is required.'
+                continue
+
+            setattr(self, f.attname, python_value)
+
+        return error_dict
+
+
     def do_validate(self):
-        errors = self.validate()
+        errors = self._validate()
         unique_errors = self.validate_unique()
         for field_name, error in unique_errors.iteritems():
             errors.setdefault(field_name, error)
@@ -600,7 +622,7 @@
          field name changes the sort to descending order.
         -extra_args: keyword args to pass to query.extra() (see Django
          DB layer documentation)
-         -extra_where: extra WHERE clause to append
+        -extra_where: extra WHERE clause to append
         """
         filter_data = dict(filter_data) # copy so we don't mutate the original
         query_start = filter_data.pop('query_start', None)
@@ -774,7 +796,7 @@
     field.
     """
 
-    def save(self):
+    def save(self, *args, **kwargs):
         first_time = (self.id is None)
         if first_time:
             # see if this object was previously added and invalidated
@@ -787,7 +809,7 @@
                 # no existing object
                 pass
 
-        super(ModelWithInvalid, self).save()
+        super(ModelWithInvalid, self).save(*args, **kwargs)
 
 
     def clean_object(self):