1from django.db import models as dbmodels, connection 2from autotest_lib.frontend.afe import model_logic, readonly_connection 3 4_quote_name = connection.ops.quote_name 5 6class TempManager(model_logic.ExtendedManager): 7 """A Temp Manager.""" 8 _GROUP_COUNT_NAME = 'group_count' 9 10 def _get_key_unless_is_function(self, field): 11 if '(' in field: 12 return field 13 return self.get_key_on_this_table(field) 14 15 16 def _get_field_names(self, fields, extra_select_fields={}): 17 field_names = [] 18 for field in fields: 19 if field in extra_select_fields: 20 field_names.append(extra_select_fields[field][0]) 21 else: 22 field_names.append(self._get_key_unless_is_function(field)) 23 return field_names 24 25 26 def _get_group_query_sql(self, query, group_by): 27 compiler = query.query.get_compiler(using=query.db) 28 sql, params = compiler.as_sql() 29 30 31 # insert GROUP BY clause into query 32 group_fields = self._get_field_names(group_by, query.query.extra_select) 33 group_by_clause = ' GROUP BY ' + ', '.join(group_fields) 34 group_by_position = sql.rfind('ORDER BY') 35 if group_by_position == -1: 36 group_by_position = len(sql) 37 sql = (sql[:group_by_position] + 38 group_by_clause + ' ' + 39 sql[group_by_position:]) 40 41 return sql, params 42 43 44 def _get_column_names(self, cursor): 45 """Gets the column names from the cursor description. 46 47 This method exists so that it can be mocked in the unit test for 48 sqlite3 compatibility. 49 50 """ 51 return [column_info[0] for column_info in cursor.description] 52 53 54 def execute_group_query(self, query, group_by): 55 """Performs the given query grouped by the specified fields. 56 57 The given query's extra select fields are added. 58 59 @param query: The query to perform. 60 @param group_by: The fields by which to group. 61 62 @return A list of dicts, where each dict corresponds to single row and 63 contains a key for each grouped field as well as all of the extra 64 select fields. 65 66 """ 67 sql, params = self._get_group_query_sql(query, group_by) 68 cursor = readonly_connection.cursor() 69 cursor.execute(sql, params) 70 field_names = self._get_column_names(cursor) 71 row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()] 72 return row_dicts 73 74 75 def get_count_sql(self, query): 76 """Get SQL to select a per-group count of unique matches for a query. 77 78 @param query: The query to use. 79 80 @return A tuple (field alias, field SQL). 81 82 """ 83 if query.query.distinct: 84 pk_field = self.get_key_on_this_table() 85 count_sql = 'COUNT(DISTINCT %s)' % pk_field 86 else: 87 count_sql = 'COUNT(1)' 88 return self._GROUP_COUNT_NAME, count_sql 89 90 91 def _get_num_groups_sql(self, query, group_by): 92 group_fields = self._get_field_names(group_by, query.query.extra_select) 93 query = query.order_by() # this can mess up the query and isn't needed 94 95 compiler = query.query.get_compiler(using=query.db) 96 sql, params = compiler.as_sql() 97 from_ = sql[sql.find(' FROM'):] 98 return ('SELECT DISTINCT %s %s' % (','.join(group_fields), 99 from_), 100 params) 101 102 103 def _cursor_rowcount(self, cursor): 104 """To be stubbed by tests""" 105 return cursor.rowcount 106 107 108 def get_num_groups(self, query, group_by): 109 """Gets the number of distinct groups for a query. 110 111 @param query: The query to use. 112 @param group_by: The fields by which to group. 113 114 @return The number of distinct groups for the given query grouped by 115 the fields in group_by. 116 117 """ 118 sql, params = self._get_num_groups_sql(query, group_by) 119 cursor = readonly_connection.cursor() 120 cursor.execute(sql, params) 121 return self._cursor_rowcount(cursor) 122 123 124class Machine(dbmodels.Model): 125 """Models a machine.""" 126 machine_idx = dbmodels.AutoField(primary_key=True) 127 hostname = dbmodels.CharField(unique=True, max_length=255) 128 machine_group = dbmodels.CharField(blank=True, max_length=240) 129 owner = dbmodels.CharField(blank=True, max_length=240) 130 131 class Meta: 132 """Metadata for class Machine.""" 133 db_table = 'tko_machines' 134 135 136class Kernel(dbmodels.Model): 137 """Models a kernel.""" 138 kernel_idx = dbmodels.AutoField(primary_key=True) 139 kernel_hash = dbmodels.CharField(max_length=105, editable=False) 140 base = dbmodels.CharField(max_length=90) 141 printable = dbmodels.CharField(max_length=300) 142 143 class Meta: 144 """Metadata for class Kernel.""" 145 db_table = 'tko_kernels' 146 147 148class Patch(dbmodels.Model): 149 """Models a patch.""" 150 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx') 151 name = dbmodels.CharField(blank=True, max_length=240) 152 url = dbmodels.CharField(blank=True, max_length=900) 153 the_hash = dbmodels.CharField(blank=True, max_length=105, db_column='hash') 154 155 class Meta: 156 """Metadata for class Patch.""" 157 db_table = 'tko_patches' 158 159 160class Status(dbmodels.Model): 161 """Models a status.""" 162 status_idx = dbmodels.AutoField(primary_key=True) 163 word = dbmodels.CharField(max_length=30) 164 165 class Meta: 166 """Metadata for class Status.""" 167 db_table = 'tko_status' 168 169 170class Job(dbmodels.Model, model_logic.ModelExtensions): 171 """Models a job.""" 172 job_idx = dbmodels.AutoField(primary_key=True) 173 tag = dbmodels.CharField(unique=True, max_length=100) 174 label = dbmodels.CharField(max_length=300) 175 username = dbmodels.CharField(max_length=240) 176 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx') 177 queued_time = dbmodels.DateTimeField(null=True, blank=True) 178 started_time = dbmodels.DateTimeField(null=True, blank=True) 179 finished_time = dbmodels.DateTimeField(null=True, blank=True) 180 afe_job_id = dbmodels.IntegerField(null=True, default=None) 181 182 objects = model_logic.ExtendedManager() 183 184 class Meta: 185 """Metadata for class Job.""" 186 db_table = 'tko_jobs' 187 188 189class JobKeyval(dbmodels.Model): 190 """Models a job keyval.""" 191 job = dbmodels.ForeignKey(Job) 192 key = dbmodels.CharField(max_length=90) 193 value = dbmodels.CharField(blank=True, max_length=300) 194 195 class Meta: 196 """Metadata for class JobKeyval.""" 197 db_table = 'tko_job_keyvals' 198 199 200class Test(dbmodels.Model, model_logic.ModelExtensions, 201 model_logic.ModelWithAttributes): 202 """Models a test.""" 203 test_idx = dbmodels.AutoField(primary_key=True) 204 job = dbmodels.ForeignKey(Job, db_column='job_idx') 205 test = dbmodels.CharField(max_length=300) 206 subdir = dbmodels.CharField(blank=True, max_length=300) 207 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx') 208 status = dbmodels.ForeignKey(Status, db_column='status') 209 reason = dbmodels.CharField(blank=True, max_length=3072) 210 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx') 211 finished_time = dbmodels.DateTimeField(null=True, blank=True) 212 started_time = dbmodels.DateTimeField(null=True, blank=True) 213 invalid = dbmodels.BooleanField(default=False) 214 invalidates_test = dbmodels.ForeignKey( 215 'self', null=True, db_column='invalidates_test_idx', 216 related_name='invalidates_test_set') 217 218 objects = model_logic.ExtendedManager() 219 220 def _get_attribute_model_and_args(self, attribute): 221 return TestAttribute, dict(test=self, attribute=attribute, 222 user_created=True) 223 224 225 def set_attribute(self, attribute, value): 226 # ensure non-user-created attributes remain immutable 227 try: 228 TestAttribute.objects.get(test=self, attribute=attribute, 229 user_created=False) 230 raise ValueError('Attribute %s already exists for test %s and is ' 231 'immutable' % (attribute, self.test_idx)) 232 except TestAttribute.DoesNotExist: 233 super(Test, self).set_attribute(attribute, value) 234 235 class Meta: 236 """Metadata for class Test.""" 237 db_table = 'tko_tests' 238 239 240class TestAttribute(dbmodels.Model, model_logic.ModelExtensions): 241 """Models a test attribute.""" 242 test = dbmodels.ForeignKey(Test, db_column='test_idx') 243 attribute = dbmodels.CharField(max_length=90) 244 value = dbmodels.CharField(blank=True, max_length=300) 245 user_created = dbmodels.BooleanField(default=False) 246 247 objects = model_logic.ExtendedManager() 248 249 class Meta: 250 """Metadata for class TestAttribute.""" 251 db_table = 'tko_test_attributes' 252 253 254class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions): 255 """Models an iteration attribute.""" 256 # This isn't really a primary key, but it's necessary to appease Django 257 # and is harmless as long as we're careful. 258 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True) 259 iteration = dbmodels.IntegerField() 260 attribute = dbmodels.CharField(max_length=90) 261 value = dbmodels.CharField(blank=True, max_length=300) 262 263 objects = model_logic.ExtendedManager() 264 265 class Meta: 266 """Metadata for class IterationAttribute.""" 267 db_table = 'tko_iteration_attributes' 268 269 270class IterationResult(dbmodels.Model, model_logic.ModelExtensions): 271 """Models an iteration result.""" 272 # See comment on IterationAttribute regarding primary_key=True. 273 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True) 274 iteration = dbmodels.IntegerField() 275 attribute = dbmodels.CharField(max_length=256) 276 value = dbmodels.FloatField(null=True, blank=True) 277 278 objects = model_logic.ExtendedManager() 279 280 class Meta: 281 """Metadata for class IterationResult.""" 282 db_table = 'tko_iteration_result' 283 284 285class TestLabel(dbmodels.Model, model_logic.ModelExtensions): 286 """Models a test label.""" 287 name = dbmodels.CharField(max_length=80, unique=True) 288 description = dbmodels.TextField(blank=True) 289 tests = dbmodels.ManyToManyField(Test, blank=True, 290 db_table='tko_test_labels_tests') 291 292 name_field = 'name' 293 objects = model_logic.ExtendedManager() 294 295 class Meta: 296 """Metadata for class TestLabel.""" 297 db_table = 'tko_test_labels' 298 299 300class SavedQuery(dbmodels.Model, model_logic.ModelExtensions): 301 """Models a saved query.""" 302 # TODO: change this to foreign key once DBs are merged. 303 owner = dbmodels.CharField(max_length=80) 304 name = dbmodels.CharField(max_length=100) 305 url_token = dbmodels.TextField() 306 307 class Meta: 308 """Metadata for class SavedQuery.""" 309 db_table = 'tko_saved_queries' 310 311 312class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions): 313 """Models an embedded graphing query.""" 314 url_token = dbmodels.TextField(null=False, blank=False) 315 graph_type = dbmodels.CharField(max_length=16, null=False, blank=False) 316 params = dbmodels.TextField(null=False, blank=False) 317 last_updated = dbmodels.DateTimeField(null=False, blank=False, 318 editable=False) 319 # refresh_time shows the time at which a thread is updating the cached 320 # image, or NULL if no one is updating the image. This is used so that only 321 # one thread is updating the cached image at a time (see 322 # graphing_utils.handle_plot_request). 323 refresh_time = dbmodels.DateTimeField(editable=False) 324 cached_png = dbmodels.TextField(editable=False) 325 326 class Meta: 327 """Metadata for class EmbeddedGraphingQuery.""" 328 db_table = 'tko_embedded_graphing_queries' 329 330 331# Views. 332 333class TestViewManager(TempManager): 334 """A Test View Manager.""" 335 336 def get_query_set(self): 337 query = super(TestViewManager, self).get_query_set() 338 339 # add extra fields to selects, using the SQL itself as the "alias" 340 extra_select = dict((sql, sql) 341 for sql in self.model.extra_fields.iterkeys()) 342 return query.extra(select=extra_select) 343 344 345 def _get_include_exclude_suffix(self, exclude): 346 if exclude: 347 return '_exclude' 348 return '_include' 349 350 351 def _add_attribute_join(self, query_set, join_condition, 352 suffix=None, exclude=False): 353 if suffix is None: 354 suffix = self._get_include_exclude_suffix(exclude) 355 return self.add_join(query_set, 'tko_test_attributes', 356 join_key='test_idx', 357 join_condition=join_condition, 358 suffix=suffix, exclude=exclude) 359 360 361 def _add_label_pivot_table_join(self, query_set, suffix, join_condition='', 362 exclude=False, force_left_join=False): 363 return self.add_join(query_set, 'tko_test_labels_tests', 364 join_key='test_id', 365 join_condition=join_condition, 366 suffix=suffix, exclude=exclude, 367 force_left_join=force_left_join) 368 369 370 def _add_label_joins(self, query_set, suffix=''): 371 query_set = self._add_label_pivot_table_join( 372 query_set, suffix=suffix, force_left_join=True) 373 374 # since we're not joining from the original table, we can't use 375 # self.add_join() again 376 second_join_alias = 'tko_test_labels' + suffix 377 second_join_condition = ('%s.id = %s.testlabel_id' % 378 (second_join_alias, 379 'tko_test_labels_tests' + suffix)) 380 query_set.query.add_custom_join('tko_test_labels', 381 second_join_condition, 382 query_set.query.LOUTER, 383 alias=second_join_alias) 384 return query_set 385 386 387 def _get_label_ids_from_names(self, label_names): 388 label_ids = list( # listifying avoids a double query below 389 TestLabel.objects.filter(name__in=label_names) 390 .values_list('name', 'id')) 391 if len(label_ids) < len(set(label_names)): 392 raise ValueError('Not all labels found: %s' % 393 ', '.join(label_names)) 394 return dict(name_and_id for name_and_id in label_ids) 395 396 397 def _include_or_exclude_labels(self, query_set, label_names, exclude=False): 398 label_ids = self._get_label_ids_from_names(label_names).itervalues() 399 suffix = self._get_include_exclude_suffix(exclude) 400 condition = ('tko_test_labels_tests%s.testlabel_id IN (%s)' % 401 (suffix, 402 ','.join(str(label_id) for label_id in label_ids))) 403 return self._add_label_pivot_table_join(query_set, 404 join_condition=condition, 405 suffix=suffix, 406 exclude=exclude) 407 408 409 def _add_custom_select(self, query_set, select_name, select_sql): 410 return query_set.extra(select={select_name: select_sql}) 411 412 413 def _add_select_value(self, query_set, alias): 414 return self._add_custom_select(query_set, alias, 415 _quote_name(alias) + '.value') 416 417 418 def _add_select_ifnull(self, query_set, alias, non_null_value): 419 select_sql = "IF(%s.id IS NOT NULL, '%s', NULL)" % (_quote_name(alias), 420 non_null_value) 421 return self._add_custom_select(query_set, alias, select_sql) 422 423 424 def _join_test_label_column(self, query_set, label_name, label_id): 425 alias = 'test_label_' + label_name 426 label_query = TestLabel.objects.filter(name=label_name) 427 query_set = Test.objects.join_custom_field(query_set, label_query, 428 alias) 429 430 query_set = self._add_select_ifnull(query_set, alias, label_name) 431 return query_set 432 433 434 def _join_test_label_columns(self, query_set, label_names): 435 label_id_map = self._get_label_ids_from_names(label_names) 436 for label_name in label_names: 437 query_set = self._join_test_label_column(query_set, label_name, 438 label_id_map[label_name]) 439 return query_set 440 441 442 def _join_test_attribute(self, query_set, attribute, alias=None, 443 extra_join_condition=None): 444 """ 445 Join the given TestView QuerySet to TestAttribute. The resulting query 446 has an additional column for the given attribute named 447 "attribute_<attribute name>". 448 """ 449 if not alias: 450 alias = 'test_attribute_' + attribute 451 attribute_query = TestAttribute.objects.filter(attribute=attribute) 452 if extra_join_condition: 453 attribute_query = attribute_query.extra( 454 where=[extra_join_condition]) 455 query_set = Test.objects.join_custom_field(query_set, attribute_query, 456 alias) 457 458 query_set = self._add_select_value(query_set, alias) 459 return query_set 460 461 462 def _join_machine_label_columns(self, query_set, machine_label_names): 463 for label_name in machine_label_names: 464 alias = 'machine_label_' + label_name 465 condition = "FIND_IN_SET('%s', %s)" % ( 466 label_name, _quote_name(alias) + '.value') 467 query_set = self._join_test_attribute( 468 query_set, 'host-labels', 469 alias=alias, extra_join_condition=condition) 470 query_set = self._add_select_ifnull(query_set, alias, label_name) 471 return query_set 472 473 474 def _join_one_iteration_key(self, query_set, result_key, first_alias=None): 475 alias = 'iteration_result_' + result_key 476 iteration_query = IterationResult.objects.filter(attribute=result_key) 477 if first_alias: 478 # after the first join, we need to match up iteration indices, 479 # otherwise each join will expand the query by the number of 480 # iterations and we'll have extraneous rows 481 iteration_query = iteration_query.extra( 482 where=['%s.iteration = %s.iteration' 483 % (_quote_name(alias), _quote_name(first_alias))]) 484 485 query_set = Test.objects.join_custom_field(query_set, iteration_query, 486 alias, left_join=False) 487 # select the iteration value and index for this join 488 query_set = self._add_select_value(query_set, alias) 489 if not first_alias: 490 # for first join, add iteration index select too 491 query_set = self._add_custom_select( 492 query_set, 'iteration_index', 493 _quote_name(alias) + '.iteration') 494 495 return query_set, alias 496 497 498 def _join_iteration_results(self, test_view_query_set, result_keys): 499 """Join the given TestView QuerySet to IterationResult for one result. 500 501 The resulting query looks like a TestView query but has one row per 502 iteration. Each row includes all the attributes of TestView, an 503 attribute for each key in result_keys and an iteration_index attribute. 504 505 We accomplish this by joining the TestView query to IterationResult 506 once per result key. Each join is restricted on the result key (and on 507 the test index, like all one-to-many joins). For the first join, this 508 is the only restriction, so each TestView row expands to a row per 509 iteration (per iteration that includes the key, of course). For each 510 subsequent join, we also restrict the iteration index to match that of 511 the initial join. This makes each subsequent join produce exactly one 512 result row for each input row. (This assumes each iteration contains 513 the same set of keys. Results are undefined if that's not true.) 514 """ 515 if not result_keys: 516 return test_view_query_set 517 518 query_set, first_alias = self._join_one_iteration_key( 519 test_view_query_set, result_keys[0]) 520 for result_key in result_keys[1:]: 521 query_set, _ = self._join_one_iteration_key(query_set, result_key, 522 first_alias=first_alias) 523 return query_set 524 525 526 def _join_job_keyvals(self, query_set, job_keyvals): 527 for job_keyval in job_keyvals: 528 alias = 'job_keyval_' + job_keyval 529 keyval_query = JobKeyval.objects.filter(key=job_keyval) 530 query_set = Job.objects.join_custom_field(query_set, keyval_query, 531 alias) 532 query_set = self._add_select_value(query_set, alias) 533 return query_set 534 535 536 def _join_iteration_attributes(self, query_set, iteration_attributes): 537 for attribute in iteration_attributes: 538 alias = 'iteration_attribute_' + attribute 539 attribute_query = IterationAttribute.objects.filter( 540 attribute=attribute) 541 query_set = Test.objects.join_custom_field(query_set, 542 attribute_query, alias) 543 query_set = self._add_select_value(query_set, alias) 544 return query_set 545 546 547 def get_query_set_with_joins(self, filter_data): 548 """Add joins for querying over test-related items. 549 550 These parameters are supported going forward: 551 * test_attribute_fields: list of attribute names. Each attribute will 552 be available as a column attribute_<name>.value. 553 * test_label_fields: list of label names. Each label will be available 554 as a column label_<name>.id, non-null iff the label is present. 555 * iteration_result_fields: list of iteration result names. Each 556 result will be available as a column iteration_<name>.value. 557 Note that this changes the semantics to return iterations 558 instead of tests -- if a test has multiple iterations, a row 559 will be returned for each one. The iteration index is also 560 available as iteration_<name>.iteration. 561 * machine_label_fields: list of machine label names. Each will be 562 available as a column machine_label_<name>.id, non-null iff the 563 label is present on the machine used in the test. 564 * job_keyval_fields: list of job keyval names. Each value will be 565 available as a column job_keyval_<name>.id, non-null iff the 566 keyval is present in the AFE job. 567 * iteration_attribute_fields: list of iteration attribute names. Each 568 attribute will be available as a column 569 iteration_attribute<name>.id, non-null iff the attribute is 570 present. 571 572 These parameters are deprecated: 573 * include_labels 574 * exclude_labels 575 * include_attributes_where 576 * exclude_attributes_where 577 578 Additionally, this method adds joins if the following strings are 579 present in extra_where (this is also deprecated): 580 * test_labels 581 * test_attributes_host_labels 582 583 @param filter_data: Data by which to filter. 584 585 @return A QuerySet. 586 587 """ 588 query_set = self.get_query_set() 589 590 test_attributes = filter_data.pop('test_attribute_fields', []) 591 for attribute in test_attributes: 592 query_set = self._join_test_attribute(query_set, attribute) 593 594 test_labels = filter_data.pop('test_label_fields', []) 595 query_set = self._join_test_label_columns(query_set, test_labels) 596 597 machine_labels = filter_data.pop('machine_label_fields', []) 598 query_set = self._join_machine_label_columns(query_set, machine_labels) 599 600 iteration_keys = filter_data.pop('iteration_result_fields', []) 601 query_set = self._join_iteration_results(query_set, iteration_keys) 602 603 job_keyvals = filter_data.pop('job_keyval_fields', []) 604 query_set = self._join_job_keyvals(query_set, job_keyvals) 605 606 iteration_attributes = filter_data.pop('iteration_attribute_fields', []) 607 query_set = self._join_iteration_attributes(query_set, 608 iteration_attributes) 609 610 # everything that follows is deprecated behavior 611 612 joined = False 613 614 extra_where = filter_data.get('extra_where', '') 615 if 'tko_test_labels' in extra_where: 616 query_set = self._add_label_joins(query_set) 617 joined = True 618 619 include_labels = filter_data.pop('include_labels', []) 620 exclude_labels = filter_data.pop('exclude_labels', []) 621 if include_labels: 622 query_set = self._include_or_exclude_labels(query_set, 623 include_labels) 624 joined = True 625 if exclude_labels: 626 query_set = self._include_or_exclude_labels(query_set, 627 exclude_labels, 628 exclude=True) 629 joined = True 630 631 include_attributes_where = filter_data.pop('include_attributes_where', 632 '') 633 exclude_attributes_where = filter_data.pop('exclude_attributes_where', 634 '') 635 if include_attributes_where: 636 query_set = self._add_attribute_join( 637 query_set, 638 join_condition=self.escape_user_sql(include_attributes_where)) 639 joined = True 640 if exclude_attributes_where: 641 query_set = self._add_attribute_join( 642 query_set, 643 join_condition=self.escape_user_sql(exclude_attributes_where), 644 exclude=True) 645 joined = True 646 647 if not joined: 648 filter_data['no_distinct'] = True 649 650 if 'tko_test_attributes_host_labels' in extra_where: 651 query_set = self._add_attribute_join( 652 query_set, suffix='_host_labels', 653 join_condition='tko_test_attributes_host_labels.attribute = ' 654 '"host-labels"') 655 656 return query_set 657 658 659 def query_test_ids(self, filter_data, apply_presentation=True): 660 """Queries for test IDs. 661 662 @param filter_data: Data by which to filter. 663 @param apply_presentation: Whether or not to apply presentation 664 parameters. 665 666 @return A list of test IDs. 667 668 """ 669 query = self.model.query_objects(filter_data, 670 apply_presentation=apply_presentation) 671 dicts = query.values('test_idx') 672 return [item['test_idx'] for item in dicts] 673 674 675 def query_test_label_ids(self, filter_data): 676 """Queries for test label IDs. 677 678 @param filter_data: Data by which to filter. 679 680 @return A list of test label IDs. 681 682 """ 683 query_set = self.model.query_objects(filter_data) 684 query_set = self._add_label_joins(query_set, suffix='_list') 685 rows = self._custom_select_query(query_set, ['tko_test_labels_list.id']) 686 return [row[0] for row in rows if row[0] is not None] 687 688 689 def escape_user_sql(self, sql): 690 sql = super(TestViewManager, self).escape_user_sql(sql) 691 return sql.replace('test_idx', self.get_key_on_this_table('test_idx')) 692 693 694class TestView(dbmodels.Model, model_logic.ModelExtensions): 695 """Models a test view.""" 696 extra_fields = { 697 'DATE(job_queued_time)': 'job queued day', 698 'DATE(test_finished_time)': 'test finished day', 699 } 700 701 group_fields = [ 702 'test_name', 703 'status', 704 'kernel', 705 'hostname', 706 'job_tag', 707 'job_name', 708 'platform', 709 'reason', 710 'job_owner', 711 'job_queued_time', 712 'DATE(job_queued_time)', 713 'test_started_time', 714 'test_finished_time', 715 'DATE(test_finished_time)', 716 ] 717 718 test_idx = dbmodels.IntegerField('test index', primary_key=True) 719 job_idx = dbmodels.IntegerField('job index', null=True, blank=True) 720 test_name = dbmodels.CharField(blank=True, max_length=90) 721 subdir = dbmodels.CharField('subdirectory', blank=True, max_length=180) 722 kernel_idx = dbmodels.IntegerField('kernel index') 723 status_idx = dbmodels.IntegerField('status index') 724 reason = dbmodels.CharField(blank=True, max_length=3072) 725 machine_idx = dbmodels.IntegerField('host index') 726 test_started_time = dbmodels.DateTimeField(null=True, blank=True) 727 test_finished_time = dbmodels.DateTimeField(null=True, blank=True) 728 job_tag = dbmodels.CharField(blank=True, max_length=300) 729 job_name = dbmodels.CharField(blank=True, max_length=300) 730 job_owner = dbmodels.CharField('owner', blank=True, max_length=240) 731 job_queued_time = dbmodels.DateTimeField(null=True, blank=True) 732 job_started_time = dbmodels.DateTimeField(null=True, blank=True) 733 job_finished_time = dbmodels.DateTimeField(null=True, blank=True) 734 afe_job_id = dbmodels.IntegerField(null=True) 735 hostname = dbmodels.CharField(blank=True, max_length=300) 736 platform = dbmodels.CharField(blank=True, max_length=240) 737 machine_owner = dbmodels.CharField(blank=True, max_length=240) 738 kernel_hash = dbmodels.CharField(blank=True, max_length=105) 739 kernel_base = dbmodels.CharField(blank=True, max_length=90) 740 kernel = dbmodels.CharField(blank=True, max_length=300) 741 status = dbmodels.CharField(blank=True, max_length=30) 742 invalid = dbmodels.BooleanField(blank=True) 743 invalidates_test_idx = dbmodels.IntegerField(null=True, blank=True) 744 745 objects = TestViewManager() 746 747 def save(self): 748 raise NotImplementedError('TestView is read-only') 749 750 751 def delete(self): 752 raise NotImplementedError('TestView is read-only') 753 754 755 @classmethod 756 def query_objects(cls, filter_data, initial_query=None, 757 apply_presentation=True): 758 if initial_query is None: 759 initial_query = cls.objects.get_query_set_with_joins(filter_data) 760 return super(TestView, cls).query_objects( 761 filter_data, initial_query=initial_query, 762 apply_presentation=apply_presentation) 763 764 class Meta: 765 """Metadata for class TestView.""" 766 db_table = 'tko_test_view_2' 767