diff options
| -rw-r--r-- | bitbake/lib/toaster/toastergui/api.py | 108 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/urls.py | 3 | ||||
| -rw-r--r-- | bitbake/lib/toaster/toastergui/widgets.py | 141 |
3 files changed, 127 insertions, 125 deletions
diff --git a/bitbake/lib/toaster/toastergui/api.py b/bitbake/lib/toaster/toastergui/api.py index 8876409964..5589118027 100644 --- a/bitbake/lib/toaster/toastergui/api.py +++ b/bitbake/lib/toaster/toastergui/api.py | |||
| @@ -33,10 +33,8 @@ from bldcontrol import bbcontroller | |||
| 33 | from django.http import HttpResponse, JsonResponse | 33 | from django.http import HttpResponse, JsonResponse |
| 34 | from django.views.generic import View | 34 | from django.views.generic import View |
| 35 | from django.core.urlresolvers import reverse | 35 | from django.core.urlresolvers import reverse |
| 36 | from django.utils import timezone | ||
| 37 | from django.db.models import Q, F | 36 | from django.db.models import Q, F |
| 38 | from django.db import Error | 37 | from django.db import Error |
| 39 | from toastergui.templatetags.projecttags import json, sectohms, get_tasks | ||
| 40 | from toastergui.templatetags.projecttags import filtered_filesizeformat | 38 | from toastergui.templatetags.projecttags import filtered_filesizeformat |
| 41 | 39 | ||
| 42 | logger = logging.getLogger("toaster") | 40 | logger = logging.getLogger("toaster") |
| @@ -227,112 +225,6 @@ class XhrLayer(View): | |||
| 227 | }) | 225 | }) |
| 228 | 226 | ||
| 229 | 227 | ||
| 230 | class MostRecentBuildsView(View): | ||
| 231 | def _was_yesterday_or_earlier(self, completed_on): | ||
| 232 | now = timezone.now() | ||
| 233 | delta = now - completed_on | ||
| 234 | |||
| 235 | if delta.days >= 1: | ||
| 236 | return True | ||
| 237 | |||
| 238 | return False | ||
| 239 | |||
| 240 | def get(self, request, *args, **kwargs): | ||
| 241 | """ | ||
| 242 | Returns a list of builds in JSON format. | ||
| 243 | """ | ||
| 244 | project = None | ||
| 245 | |||
| 246 | project_id = request.GET.get('project_id', None) | ||
| 247 | if project_id: | ||
| 248 | try: | ||
| 249 | project = Project.objects.get(pk=project_id) | ||
| 250 | except: | ||
| 251 | # if project lookup fails, assume no project | ||
| 252 | pass | ||
| 253 | |||
| 254 | recent_build_objs = Build.get_recent(project) | ||
| 255 | recent_builds = [] | ||
| 256 | |||
| 257 | for build_obj in recent_build_objs: | ||
| 258 | dashboard_url = reverse('builddashboard', args=(build_obj.pk,)) | ||
| 259 | buildtime_url = reverse('buildtime', args=(build_obj.pk,)) | ||
| 260 | rebuild_url = \ | ||
| 261 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
| 262 | cancel_url = \ | ||
| 263 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
| 264 | |||
| 265 | build = {} | ||
| 266 | build['id'] = build_obj.pk | ||
| 267 | build['dashboard_url'] = dashboard_url | ||
| 268 | |||
| 269 | buildrequest_id = None | ||
| 270 | if hasattr(build_obj, 'buildrequest'): | ||
| 271 | buildrequest_id = build_obj.buildrequest.pk | ||
| 272 | build['buildrequest_id'] = buildrequest_id | ||
| 273 | |||
| 274 | build['recipes_parsed_percentage'] = \ | ||
| 275 | int((build_obj.recipes_parsed / | ||
| 276 | build_obj.recipes_to_parse) * 100) | ||
| 277 | |||
| 278 | tasks_complete_percentage = 0 | ||
| 279 | if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED): | ||
| 280 | tasks_complete_percentage = 100 | ||
| 281 | elif build_obj.outcome == Build.IN_PROGRESS: | ||
| 282 | tasks_complete_percentage = build_obj.completeper() | ||
| 283 | build['tasks_complete_percentage'] = tasks_complete_percentage | ||
| 284 | |||
| 285 | build['state'] = build_obj.get_state() | ||
| 286 | |||
| 287 | build['errors'] = build_obj.errors.count() | ||
| 288 | build['dashboard_errors_url'] = dashboard_url + '#errors' | ||
| 289 | |||
| 290 | build['warnings'] = build_obj.warnings.count() | ||
| 291 | build['dashboard_warnings_url'] = dashboard_url + '#warnings' | ||
| 292 | |||
| 293 | build['buildtime'] = sectohms(build_obj.timespent_seconds) | ||
| 294 | build['buildtime_url'] = buildtime_url | ||
| 295 | |||
| 296 | build['rebuild_url'] = rebuild_url | ||
| 297 | build['cancel_url'] = cancel_url | ||
| 298 | |||
| 299 | build['is_default_project_build'] = build_obj.project.is_default | ||
| 300 | |||
| 301 | build['build_targets_json'] = \ | ||
| 302 | json(get_tasks(build_obj.target_set.all())) | ||
| 303 | |||
| 304 | # convert completed_on time to user's timezone | ||
| 305 | completed_on = timezone.localtime(build_obj.completed_on) | ||
| 306 | |||
| 307 | completed_on_template = '%H:%M' | ||
| 308 | if self._was_yesterday_or_earlier(completed_on): | ||
| 309 | completed_on_template = '%d/%m/%Y ' + completed_on_template | ||
| 310 | build['completed_on'] = completed_on.strftime( | ||
| 311 | completed_on_template) | ||
| 312 | |||
| 313 | targets = [] | ||
| 314 | target_objs = build_obj.get_sorted_target_list() | ||
| 315 | for target_obj in target_objs: | ||
| 316 | if target_obj.task: | ||
| 317 | targets.append(target_obj.target + ':' + target_obj.task) | ||
| 318 | else: | ||
| 319 | targets.append(target_obj.target) | ||
| 320 | build['targets'] = ' '.join(targets) | ||
| 321 | |||
| 322 | # abbreviated form of the full target list | ||
| 323 | abbreviated_targets = '' | ||
| 324 | num_targets = len(targets) | ||
| 325 | if num_targets > 0: | ||
| 326 | abbreviated_targets = targets[0] | ||
| 327 | if num_targets > 1: | ||
| 328 | abbreviated_targets += (' +%s' % (num_targets - 1)) | ||
| 329 | build['targets_abbreviated'] = abbreviated_targets | ||
| 330 | |||
| 331 | recent_builds.append(build) | ||
| 332 | |||
| 333 | return JsonResponse(recent_builds, safe=False) | ||
| 334 | |||
| 335 | |||
| 336 | class XhrCustomRecipe(View): | 228 | class XhrCustomRecipe(View): |
| 337 | """ Create a custom image recipe """ | 229 | """ Create a custom image recipe """ |
| 338 | 230 | ||
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index 1232611e2e..0002a5a2ee 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py | |||
| @@ -24,6 +24,7 @@ from toastergui import tables | |||
| 24 | from toastergui import buildtables | 24 | from toastergui import buildtables |
| 25 | from toastergui import typeaheads | 25 | from toastergui import typeaheads |
| 26 | from toastergui import api | 26 | from toastergui import api |
| 27 | from toastergui import widgets | ||
| 27 | 28 | ||
| 28 | urlpatterns = patterns('toastergui.views', | 29 | urlpatterns = patterns('toastergui.views', |
| 29 | # landing page | 30 | # landing page |
| @@ -224,7 +225,7 @@ urlpatterns = patterns('toastergui.views', | |||
| 224 | api.XhrProject.as_view(), | 225 | api.XhrProject.as_view(), |
| 225 | name='xhr_project'), | 226 | name='xhr_project'), |
| 226 | 227 | ||
| 227 | url(r'^mostrecentbuilds$', api.MostRecentBuildsView.as_view(), | 228 | url(r'^mostrecentbuilds$', widgets.MostRecentBuildsView.as_view(), |
| 228 | name='most_recent_builds'), | 229 | name='most_recent_builds'), |
| 229 | 230 | ||
| 230 | # default redirection | 231 | # default redirection |
diff --git a/bitbake/lib/toaster/toastergui/widgets.py b/bitbake/lib/toaster/toastergui/widgets.py index 005a5620db..026903d35d 100644 --- a/bitbake/lib/toaster/toastergui/widgets.py +++ b/bitbake/lib/toaster/toastergui/widgets.py | |||
| @@ -22,23 +22,24 @@ | |||
| 22 | from django.views.generic import View, TemplateView | 22 | from django.views.generic import View, TemplateView |
| 23 | from django.views.decorators.cache import cache_control | 23 | from django.views.decorators.cache import cache_control |
| 24 | from django.shortcuts import HttpResponse | 24 | from django.shortcuts import HttpResponse |
| 25 | from django.http import HttpResponseBadRequest | ||
| 26 | from django.core import serializers | ||
| 27 | from django.core.cache import cache | 25 | from django.core.cache import cache |
| 28 | from django.core.paginator import Paginator, EmptyPage | 26 | from django.core.paginator import Paginator, EmptyPage |
| 29 | from django.db.models import Q | 27 | from django.db.models import Q |
| 30 | from orm.models import Project, ProjectLayer, Layer_Version | 28 | from orm.models import Project, Build |
| 31 | from django.template import Context, Template | 29 | from django.template import Context, Template |
| 32 | from django.template import VariableDoesNotExist | 30 | from django.template import VariableDoesNotExist |
| 33 | from django.template import TemplateSyntaxError | 31 | from django.template import TemplateSyntaxError |
| 34 | from django.core.serializers.json import DjangoJSONEncoder | 32 | from django.core.serializers.json import DjangoJSONEncoder |
| 35 | from django.core.exceptions import FieldError | 33 | from django.core.exceptions import FieldError |
| 36 | from django.conf.urls import url, patterns | 34 | from django.utils import timezone |
| 35 | from toastergui.templatetags.projecttags import sectohms, get_tasks | ||
| 36 | from toastergui.templatetags.projecttags import json as template_json | ||
| 37 | from django.http import JsonResponse | ||
| 38 | from django.core.urlresolvers import reverse | ||
| 37 | 39 | ||
| 38 | import types | 40 | import types |
| 39 | import json | 41 | import json |
| 40 | import collections | 42 | import collections |
| 41 | import operator | ||
| 42 | import re | 43 | import re |
| 43 | 44 | ||
| 44 | try: | 45 | try: |
| @@ -55,6 +56,7 @@ from toastergui.tablefilter import TableFilterMap | |||
| 55 | class NoFieldOrDataName(Exception): | 56 | class NoFieldOrDataName(Exception): |
| 56 | pass | 57 | pass |
| 57 | 58 | ||
| 59 | |||
| 58 | class ToasterTable(TemplateView): | 60 | class ToasterTable(TemplateView): |
| 59 | def __init__(self, *args, **kwargs): | 61 | def __init__(self, *args, **kwargs): |
| 60 | super(ToasterTable, self).__init__() | 62 | super(ToasterTable, self).__init__() |
| @@ -81,7 +83,7 @@ class ToasterTable(TemplateView): | |||
| 81 | def get_context_data(self, **kwargs): | 83 | def get_context_data(self, **kwargs): |
| 82 | context = super(ToasterTable, self).get_context_data(**kwargs) | 84 | context = super(ToasterTable, self).get_context_data(**kwargs) |
| 83 | context['title'] = self.title | 85 | context['title'] = self.title |
| 84 | context['table_name'] = type(self).__name__.lower() | 86 | context['table_name'] = type(self).__name__.lower() |
| 85 | context['empty_state'] = self.empty_state | 87 | context['empty_state'] = self.empty_state |
| 86 | 88 | ||
| 87 | return context | 89 | return context |
| @@ -406,7 +408,6 @@ class ToasterTable(TemplateView): | |||
| 406 | return data | 408 | return data |
| 407 | 409 | ||
| 408 | 410 | ||
| 409 | |||
| 410 | class ToasterTypeAhead(View): | 411 | class ToasterTypeAhead(View): |
| 411 | """ A typeahead mechanism to support the front end typeahead widgets """ | 412 | """ A typeahead mechanism to support the front end typeahead widgets """ |
| 412 | MAX_RESULTS = 6 | 413 | MAX_RESULTS = 6 |
| @@ -427,34 +428,142 @@ class ToasterTypeAhead(View): | |||
| 427 | error = "ok" | 428 | error = "ok" |
| 428 | 429 | ||
| 429 | search_term = request.GET.get("search", None) | 430 | search_term = request.GET.get("search", None) |
| 430 | if search_term == None: | 431 | if search_term is None: |
| 431 | # We got no search value so return empty reponse | 432 | # We got no search value so return empty reponse |
| 432 | return response({'error' : error , 'results': []}) | 433 | return response({'error': error, 'results': []}) |
| 433 | 434 | ||
| 434 | try: | 435 | try: |
| 435 | prj = Project.objects.get(pk=kwargs['pid']) | 436 | prj = Project.objects.get(pk=kwargs['pid']) |
| 436 | except KeyError: | 437 | except KeyError: |
| 437 | prj = None | 438 | prj = None |
| 438 | 439 | ||
| 439 | results = self.apply_search(search_term, prj, request)[:ToasterTypeAhead.MAX_RESULTS] | 440 | results = self.apply_search(search_term, |
| 441 | prj, | ||
| 442 | request)[:ToasterTypeAhead.MAX_RESULTS] | ||
| 440 | 443 | ||
| 441 | if len(results) > 0: | 444 | if len(results) > 0: |
| 442 | try: | 445 | try: |
| 443 | self.validate_fields(results[0]) | 446 | self.validate_fields(results[0]) |
| 444 | except MissingFieldsException as e: | 447 | except self.MissingFieldsException as e: |
| 445 | error = e | 448 | error = e |
| 446 | 449 | ||
| 447 | data = { 'results' : results, | 450 | data = {'results': results, |
| 448 | 'error' : error, | 451 | 'error': error} |
| 449 | } | ||
| 450 | 452 | ||
| 451 | return response(data) | 453 | return response(data) |
| 452 | 454 | ||
| 453 | def validate_fields(self, result): | 455 | def validate_fields(self, result): |
| 454 | if 'name' in result == False or 'detail' in result == False: | 456 | if 'name' in result is False or 'detail' in result is False: |
| 455 | raise MissingFieldsException("name and detail are required fields") | 457 | raise self.MissingFieldsException( |
| 458 | "name and detail are required fields") | ||
| 456 | 459 | ||
| 457 | def apply_search(self, search_term, prj): | 460 | def apply_search(self, search_term, prj): |
| 458 | """ Override this function to implement search. Return an array of | 461 | """ Override this function to implement search. Return an array of |
| 459 | dictionaries with a minium of a name and detail field""" | 462 | dictionaries with a minium of a name and detail field""" |
| 460 | pass | 463 | pass |
| 464 | |||
| 465 | |||
| 466 | class MostRecentBuildsView(View): | ||
| 467 | def _was_yesterday_or_earlier(self, completed_on): | ||
| 468 | now = timezone.now() | ||
| 469 | delta = now - completed_on | ||
| 470 | |||
| 471 | if delta.days >= 1: | ||
| 472 | return True | ||
| 473 | |||
| 474 | return False | ||
| 475 | |||
| 476 | def get(self, request, *args, **kwargs): | ||
| 477 | """ | ||
| 478 | Returns a list of builds in JSON format. | ||
| 479 | """ | ||
| 480 | project = None | ||
| 481 | |||
| 482 | project_id = request.GET.get('project_id', None) | ||
| 483 | if project_id: | ||
| 484 | try: | ||
| 485 | project = Project.objects.get(pk=project_id) | ||
| 486 | except: | ||
| 487 | # if project lookup fails, assume no project | ||
| 488 | pass | ||
| 489 | |||
| 490 | recent_build_objs = Build.get_recent(project) | ||
| 491 | recent_builds = [] | ||
| 492 | |||
| 493 | for build_obj in recent_build_objs: | ||
| 494 | dashboard_url = reverse('builddashboard', args=(build_obj.pk,)) | ||
| 495 | buildtime_url = reverse('buildtime', args=(build_obj.pk,)) | ||
| 496 | rebuild_url = \ | ||
| 497 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
| 498 | cancel_url = \ | ||
| 499 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
| 500 | |||
| 501 | build = {} | ||
| 502 | build['id'] = build_obj.pk | ||
| 503 | build['dashboard_url'] = dashboard_url | ||
| 504 | |||
| 505 | buildrequest_id = None | ||
| 506 | if hasattr(build_obj, 'buildrequest'): | ||
| 507 | buildrequest_id = build_obj.buildrequest.pk | ||
| 508 | build['buildrequest_id'] = buildrequest_id | ||
| 509 | |||
| 510 | build['recipes_parsed_percentage'] = \ | ||
| 511 | int((build_obj.recipes_parsed / | ||
| 512 | build_obj.recipes_to_parse) * 100) | ||
| 513 | |||
| 514 | tasks_complete_percentage = 0 | ||
| 515 | if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED): | ||
| 516 | tasks_complete_percentage = 100 | ||
| 517 | elif build_obj.outcome == Build.IN_PROGRESS: | ||
| 518 | tasks_complete_percentage = build_obj.completeper() | ||
| 519 | build['tasks_complete_percentage'] = tasks_complete_percentage | ||
| 520 | |||
| 521 | build['state'] = build_obj.get_state() | ||
| 522 | |||
| 523 | build['errors'] = build_obj.errors.count() | ||
| 524 | build['dashboard_errors_url'] = dashboard_url + '#errors' | ||
| 525 | |||
| 526 | build['warnings'] = build_obj.warnings.count() | ||
| 527 | build['dashboard_warnings_url'] = dashboard_url + '#warnings' | ||
| 528 | |||
| 529 | build['buildtime'] = sectohms(build_obj.timespent_seconds) | ||
| 530 | build['buildtime_url'] = buildtime_url | ||
| 531 | |||
| 532 | build['rebuild_url'] = rebuild_url | ||
| 533 | build['cancel_url'] = cancel_url | ||
| 534 | |||
| 535 | build['is_default_project_build'] = build_obj.project.is_default | ||
| 536 | |||
| 537 | build['build_targets_json'] = \ | ||
| 538 | template_json(get_tasks(build_obj.target_set.all())) | ||
| 539 | |||
| 540 | # convert completed_on time to user's timezone | ||
| 541 | completed_on = timezone.localtime(build_obj.completed_on) | ||
| 542 | |||
| 543 | completed_on_template = '%H:%M' | ||
| 544 | if self._was_yesterday_or_earlier(completed_on): | ||
| 545 | completed_on_template = '%d/%m/%Y ' + completed_on_template | ||
| 546 | build['completed_on'] = completed_on.strftime( | ||
| 547 | completed_on_template) | ||
| 548 | |||
| 549 | targets = [] | ||
| 550 | target_objs = build_obj.get_sorted_target_list() | ||
| 551 | for target_obj in target_objs: | ||
| 552 | if target_obj.task: | ||
| 553 | targets.append(target_obj.target + ':' + target_obj.task) | ||
| 554 | else: | ||
| 555 | targets.append(target_obj.target) | ||
| 556 | build['targets'] = ' '.join(targets) | ||
| 557 | |||
| 558 | # abbreviated form of the full target list | ||
| 559 | abbreviated_targets = '' | ||
| 560 | num_targets = len(targets) | ||
| 561 | if num_targets > 0: | ||
| 562 | abbreviated_targets = targets[0] | ||
| 563 | if num_targets > 1: | ||
| 564 | abbreviated_targets += (' +%s' % (num_targets - 1)) | ||
| 565 | build['targets_abbreviated'] = abbreviated_targets | ||
| 566 | |||
| 567 | recent_builds.append(build) | ||
| 568 | |||
| 569 | return JsonResponse(recent_builds, safe=False) | ||
