Initial checkin of new TKO interface.



git-svn-id: http://test.kernel.org/svn/autotest/trunk@1959 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/new_tko/tko/models.py b/new_tko/tko/models.py
new file mode 100644
index 0000000..4bb9a59
--- /dev/null
+++ b/new_tko/tko/models.py
@@ -0,0 +1,339 @@
+from django.db import models as dbmodels, connection
+from django.utils import datastructures
+from autotest_lib.frontend.afe import model_logic, readonly_connection
+
+class TempManager(model_logic.ExtendedManager):
+    _GROUP_COUNT_NAME = 'group_count'
+
+    def _get_key_unless_is_function(self, field):
+        if '(' in field:
+            return field
+        return self._get_key_on_this_table(field)
+
+
+    def _get_field_names(self, fields):
+        return [self._get_key_unless_is_function(field) for field in fields]
+
+
+    def _get_group_query_sql(self, query, group_by, extra_select_fields):
+        group_fields = self._get_field_names(group_by)
+        if query._distinct:
+            pk_field = self._get_key_on_this_table(self.model._meta.pk.name)
+            count_sql = 'COUNT(DISTINCT %s)' % pk_field
+        else:
+            count_sql = 'COUNT(1)'
+        select_fields = (group_fields +
+                         [count_sql + ' AS ' + self._GROUP_COUNT_NAME] +
+                         extra_select_fields)
+
+        # add the count field and all group fields to the query selects, so
+        # they'll be sortable and Django won't mess with any of them
+        #for field in group_fields + [self._GROUP_COUNT_NAME]:
+        #    query._select[field] = ''
+        query._select[self._GROUP_COUNT_NAME] = count_sql
+
+        # Inject the GROUP_BY clause into the query by adding it to the end of
+        # the queries WHERE clauses. We need it to come before the ORDER BY and
+        # LIMIT clauses.
+        num_real_where_clauses = len(query._where)
+        query._where.append('GROUP BY ' + ', '.join(group_fields))
+        _, where, params = query._get_sql_clause()
+        if num_real_where_clauses == 0:
+            # handle the special case where there were no actual WHERE clauses
+            where = where.replace('WHERE GROUP BY', 'GROUP BY')
+        else:
+            where = where.replace('AND GROUP BY', 'GROUP BY')
+
+        return ('SELECT ' + ', '.join(select_fields) + where), params
+
+
+    def get_group_counts(self, query, group_by, extra_select_fields=[]):
+        """
+        Performs the given query grouped by the fields in group_by.  Returns a
+        list of rows, where each row is a list containing the value of each
+        field in group_by, followed by the group count.
+        """
+        sql, params = self._get_group_query_sql(query, group_by,
+                                                extra_select_fields)
+        cursor = readonly_connection.connection.cursor()
+        num_rows = cursor.execute(sql, params)
+        return cursor.fetchall()
+
+
+    def _get_num_groups_sql(self, query, group_by):
+        group_fields = self._get_field_names(group_by)
+        query._order_by = None # this can mess up the query is isn't needed
+        _, where, params = query._get_sql_clause()
+        return ('SELECT COUNT(DISTINCT %s) %s' % (','.join(group_fields),
+                                                  where),
+                params)
+
+
+    def get_num_groups(self, query, group_by):
+        """
+        Returns the number of distinct groups for the given query grouped by the
+        fields in group_by.
+        """
+        sql, params = self._get_num_groups_sql(query, group_by)
+        cursor = readonly_connection.connection.cursor()
+        cursor.execute(sql, params)
+        return cursor.fetchone()[0]
+
+
+class Machine(dbmodels.Model):
+    machine_idx = dbmodels.IntegerField(primary_key=True)
+    hostname = dbmodels.CharField(unique=True, maxlength=300)
+    machine_group = dbmodels.CharField(blank=True, maxlength=240)
+    owner = dbmodels.CharField(blank=True, maxlength=240)
+
+    class Meta:
+        db_table = 'machines'
+
+
+class Kernel(dbmodels.Model):
+    kernel_idx = dbmodels.IntegerField(primary_key=True)
+    kernel_hash = dbmodels.CharField(maxlength=105, editable=False)
+    base = dbmodels.CharField(maxlength=90)
+    printable = dbmodels.CharField(maxlength=300)
+
+    class Meta:
+        db_table = 'kernels'
+
+
+class Patch(dbmodels.Model):
+    kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
+    name = dbmodels.CharField(blank=True, maxlength=240)
+    url = dbmodels.CharField(blank=True, maxlength=900)
+    hash_ = dbmodels.CharField(blank=True, maxlength=105, db_column='hash')
+
+    class Meta:
+        db_table = 'patches'
+
+
+class Status(dbmodels.Model):
+    status_idx = dbmodels.IntegerField(primary_key=True)
+    word = dbmodels.CharField(maxlength=30)
+
+    class Meta:
+        db_table = 'status'
+
+
+class Job(dbmodels.Model):
+    job_idx = dbmodels.IntegerField(primary_key=True)
+    tag = dbmodels.CharField(unique=True, maxlength=300)
+    label = dbmodels.CharField(maxlength=300)
+    username = dbmodels.CharField(maxlength=240)
+    machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
+    queued_time = dbmodels.DateTimeField(null=True, blank=True)
+    started_time = dbmodels.DateTimeField(null=True, blank=True)
+    finished_time = dbmodels.DateTimeField(null=True, blank=True)
+
+    class Meta:
+        db_table = 'jobs'
+
+
+class TestManager(dbmodels.Manager):
+    'Custom manager for Test model'
+    def query_using_test_view(self, filter_data):
+        test_ids = [test_view.test_idx for test_view
+                    in TestView.query_objects(filter_data)]
+        return self.in_bulk(test_ids).values()
+
+
+class Test(dbmodels.Model):
+    test_idx = dbmodels.IntegerField(primary_key=True)
+    job = dbmodels.ForeignKey(Job, db_column='job_idx')
+    test = dbmodels.CharField(maxlength=90)
+    subdir = dbmodels.CharField(blank=True, maxlength=180)
+    kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
+    status = dbmodels.ForeignKey(Status, db_column='status')
+    reason = dbmodels.CharField(blank=True, maxlength=3072)
+    machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
+    finished_time = dbmodels.DateTimeField(null=True, blank=True)
+    started_time = dbmodels.DateTimeField(null=True, blank=True)
+
+    objects = TestManager()
+
+    class Meta:
+        db_table = 'tests'
+
+
+class TestAttribute(dbmodels.Model):
+    test = dbmodels.ForeignKey(Test, db_column='test_idx')
+    attribute = dbmodels.CharField(maxlength=90)
+    value = dbmodels.CharField(blank=True, maxlength=300)
+
+    class Meta:
+        db_table = 'test_attributes'
+
+
+class IterationAttribute(dbmodels.Model):
+    test = dbmodels.ForeignKey(Test, db_column='test_idx')
+    iteration = dbmodels.IntegerField()
+    attribute = dbmodels.CharField(maxlength=90)
+    value = dbmodels.CharField(blank=True, maxlength=300)
+
+    class Meta:
+        db_table = 'iteration_attributes'
+
+
+class IterationResult(dbmodels.Model):
+    test = dbmodels.ForeignKey(Test, db_column='test_idx')
+    iteration = dbmodels.IntegerField()
+    attribute = dbmodels.CharField(maxlength=90)
+    value = dbmodels.FloatField(null=True, max_digits=12, decimal_places=31,
+                              blank=True)
+
+    class Meta:
+        db_table = 'iteration_result'
+
+
+class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
+    name = dbmodels.CharField(maxlength=80)
+    description = dbmodels.TextField(blank=True)
+    tests = dbmodels.ManyToManyField(Test, blank=True,
+                                     filter_interface=dbmodels.HORIZONTAL)
+
+    name_field = 'name'
+
+    class Meta:
+        db_table = 'test_labels'
+
+
+class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
+    # TODO: change this to foreign key once DBs are merged
+    owner = dbmodels.CharField(maxlength=80)
+    name = dbmodels.CharField(maxlength=100)
+    url_token = dbmodels.TextField()
+
+    class Meta:
+        db_table = 'saved_queries'
+
+
+# views
+
+class TestViewManager(TempManager):
+    class _JoinQ(dbmodels.Q):
+        def __init__(self):
+            self._joins = datastructures.SortedDict()
+
+
+        def add_join(self, table, condition, join_type, alias=None):
+            if alias is None:
+                alias = table
+            self._joins[alias] = (table, join_type, condition)
+
+
+        def get_sql(self, opts):
+            return self._joins, [], []
+
+
+    def get_query_set(self):
+        query = super(TestViewManager, self).get_query_set()
+        
+        # add extra fields to selects, using the SQL itself as the "alias"
+        extra_select = dict((sql, sql)
+                            for sql in self.model.extra_fields.iterkeys())
+        return query.extra(select=extra_select)
+
+
+    def get_query_set_with_labels(self, filter_data):
+        query_set = self.get_query_set()
+        # TODO: make this check more thorough if necessary
+        if 'test_labels' in filter_data.get('extra_where', ''):
+            filter_object = self._JoinQ()
+            filter_object.add_join(
+                'test_labels_tests',
+                'test_labels_tests.test_id = test_view.test_idx',
+                'LEFT JOIN')
+            filter_object.add_join(
+                'test_labels',
+                'test_labels.id = test_labels_tests.testlabel_id',
+                'LEFT JOIN')
+            query_set = query_set.complex_filter(filter_object).distinct()
+        else:
+            filter_data['no_distinct'] = True
+        return query_set
+
+
+class TestView(dbmodels.Model, model_logic.ModelExtensions):
+    extra_fields = {
+        'DATE(test_finished_time)' : 'test finished day',
+    }
+
+    group_fields = [
+        'test_name',
+        'status',
+        'kernel',
+        'hostname',
+        'job_tag',
+        'job_name',
+        'platform',
+        'reason',
+        'job_owner',
+        'test_finished_time',
+        'DATE(test_finished_time)',
+    ]
+
+    test_idx = dbmodels.IntegerField('test index', primary_key=True)
+    job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
+    test_name = dbmodels.CharField(blank=True, maxlength=90)
+    subdir = dbmodels.CharField('subdirectory', blank=True, maxlength=180)
+    kernel_idx = dbmodels.IntegerField('kernel index')
+    status_idx = dbmodels.IntegerField('status index')
+    reason = dbmodels.CharField(blank=True, maxlength=3072)
+    machine_idx = dbmodels.IntegerField('host index')
+    test_started_time = dbmodels.DateTimeField(null=True, blank=True)
+    test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
+    job_tag = dbmodels.CharField(blank=True, maxlength=300)
+    job_name = dbmodels.CharField(blank=True, maxlength=300)
+    job_owner = dbmodels.CharField('owner', blank=True, maxlength=240)
+    job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
+    job_started_time = dbmodels.DateTimeField(null=True, blank=True)
+    job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
+    hostname = dbmodels.CharField(blank=True, maxlength=300)
+    platform = dbmodels.CharField(blank=True, maxlength=240)
+    machine_owner = dbmodels.CharField(blank=True, maxlength=240)
+    kernel_hash = dbmodels.CharField(blank=True, maxlength=105)
+    kernel_base = dbmodels.CharField(blank=True, maxlength=90)
+    kernel = dbmodels.CharField(blank=True, maxlength=300)
+    status = dbmodels.CharField(blank=True, maxlength=30)
+
+    objects = TestViewManager()
+
+    def save(self):
+        raise NotImplementedError('TestView is read-only')
+
+
+    def delete(self):
+        raise NotImplementedError('TestView is read-only')
+
+
+    @classmethod
+    def query_objects(cls, filter_data, initial_query=None):
+        if initial_query is None:
+            initial_query = cls.objects.get_query_set_with_labels(filter_data)
+        return super(TestView, cls).query_objects(filter_data,
+                                                  initial_query=initial_query)
+
+
+    @classmethod
+    def list_objects(cls, filter_data, initial_query=None):
+        """
+        Django's ValuesQuerySet (used when you call query.values()) doesn't
+        support custom select fields, so we have to basically reimplement it
+        here.
+        TODO: merge this up to ModelExtensions after some settling time.
+        """
+        query = cls.query_objects(filter_data, initial_query=initial_query)
+        object_dicts = []
+        for model_object in query:
+            object_dict = model_object.get_object_dict()
+            for sql in cls.extra_fields.iterkeys():
+                object_dict[sql] = getattr(model_object, sql)
+            object_dicts.append(object_dict)
+        return object_dicts
+
+
+    class Meta:
+        db_table = 'test_view_2'