diff options
Diffstat (limited to 'bitbake/lib/toaster/toastergui/views.py')
| -rw-r--r-- | bitbake/lib/toaster/toastergui/views.py | 258 |
1 files changed, 241 insertions, 17 deletions
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 9740ef38d1..97514cc0f6 100644 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
| @@ -21,17 +21,18 @@ | |||
| 21 | 21 | ||
| 22 | import operator | 22 | import operator |
| 23 | 23 | ||
| 24 | from django.db.models import Q | 24 | from django.db.models import Q, Sum |
| 25 | from django.shortcuts import render, redirect | 25 | from django.shortcuts import render, redirect |
| 26 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable | 26 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable |
| 27 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency | 27 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency |
| 28 | from orm.models import Target_Installed_Package | 28 | from orm.models import Target_Installed_Package, Target_File |
| 29 | from django.views.decorators.cache import cache_control | 29 | from django.views.decorators.cache import cache_control |
| 30 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | 30 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
| 31 | from django.http import HttpResponseBadRequest | 31 | from django.http import HttpResponseBadRequest |
| 32 | from django.utils import timezone | 32 | from django.utils import timezone |
| 33 | from datetime import timedelta | 33 | from datetime import timedelta |
| 34 | from django.utils import formats | 34 | from django.utils import formats |
| 35 | import json | ||
| 35 | 36 | ||
| 36 | def _build_page_range(paginator, index = 1): | 37 | def _build_page_range(paginator, index = 1): |
| 37 | try: | 38 | try: |
| @@ -163,7 +164,7 @@ def _get_search_results(search_term, queryset, model): | |||
| 163 | def _search_tuple(request, model): | 164 | def _search_tuple(request, model): |
| 164 | ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model) | 165 | ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model) |
| 165 | if invalid: | 166 | if invalid: |
| 166 | raise BaseException("Invalid ordering " + str(invalid)) | 167 | raise BaseException("Invalid ordering model:" + str(model) + str(invalid)) |
| 167 | 168 | ||
| 168 | filter_string, invalid = _validate_input(request.GET.get('filter', ''), model) | 169 | filter_string, invalid = _validate_input(request.GET.get('filter', ''), model) |
| 169 | if invalid: | 170 | if invalid: |
| @@ -284,8 +285,8 @@ def builds(request): | |||
| 284 | 'qhelp': "The date and time the build finished", | 285 | 'qhelp': "The date and time the build finished", |
| 285 | 'orderfield': _get_toggle_order(request, "completed_on", True), | 286 | 'orderfield': _get_toggle_order(request, "completed_on", True), |
| 286 | 'ordericon':_get_toggle_order_icon(request, "completed_on"), | 287 | 'ordericon':_get_toggle_order_icon(request, "completed_on"), |
| 287 | 'filter' : {'class' : 'completed_on', | 288 | 'filter' : {'class' : 'completed_on', |
| 288 | 'label': 'Show:', | 289 | 'label': 'Show:', |
| 289 | 'options' : [ | 290 | 'options' : [ |
| 290 | ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now().strftime("%Y-%m-%d")).count()), | 291 | ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now().strftime("%Y-%m-%d")).count()), |
| 291 | ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d")).count()), | 292 | ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d")).count()), |
| @@ -307,8 +308,8 @@ def builds(request): | |||
| 307 | 'qhelp': "How many errors were encountered during the build (if any)", | 308 | 'qhelp': "How many errors were encountered during the build (if any)", |
| 308 | 'orderfield': _get_toggle_order(request, "errors_no", True), | 309 | 'orderfield': _get_toggle_order(request, "errors_no", True), |
| 309 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), | 310 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), |
| 310 | 'filter' : {'class' : 'errors_no', | 311 | 'filter' : {'class' : 'errors_no', |
| 311 | 'label': 'Show:', | 312 | 'label': 'Show:', |
| 312 | 'options' : [ | 313 | 'options' : [ |
| 313 | ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), | 314 | ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), |
| 314 | ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), | 315 | ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), |
| @@ -319,8 +320,8 @@ def builds(request): | |||
| 319 | 'qhelp': "How many warnigns were encountered during the build (if any)", | 320 | 'qhelp': "How many warnigns were encountered during the build (if any)", |
| 320 | 'orderfield': _get_toggle_order(request, "warnings_no", True), | 321 | 'orderfield': _get_toggle_order(request, "warnings_no", True), |
| 321 | 'ordericon':_get_toggle_order_icon(request, "warnings_no"), | 322 | 'ordericon':_get_toggle_order_icon(request, "warnings_no"), |
| 322 | 'filter' : {'class' : 'warnings_no', | 323 | 'filter' : {'class' : 'warnings_no', |
| 323 | 'label': 'Show:', | 324 | 'label': 'Show:', |
| 324 | 'options' : [ | 325 | 'options' : [ |
| 325 | ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()), | 326 | ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()), |
| 326 | ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()), | 327 | ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()), |
| @@ -417,13 +418,236 @@ def recipe(request, build_id, recipe_id): | |||
| 417 | 418 | ||
| 418 | def target(request, build_id, target_id): | 419 | def target(request, build_id, target_id): |
| 419 | template = "target.html" | 420 | template = "target.html" |
| 420 | if Build.objects.filter(pk=build_id).count() == 0 : | 421 | mandatory_parameters = { 'count': 25, 'page' : 1, 'orderby':'name:+'}; |
| 421 | return redirect(builds) | 422 | retval = _verify_parameters( request.GET, mandatory_parameters ) |
| 422 | context = { | 423 | if retval: |
| 423 | 'build' : Build.objects.filter(pk=build_id)[0], | 424 | return _redirect_parameters( 'target', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id) |
| 424 | } | 425 | (filter_string, search_term, ordering_string) = _search_tuple(request, Package) |
| 426 | |||
| 427 | # FUTURE: get rid of nested sub-queries replacing with ManyToMany field | ||
| 428 | queryset = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')) | ||
| 429 | packages_sum = queryset.aggregate(Sum('installed_size')) | ||
| 430 | queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string) | ||
| 431 | packages = _build_page_range(Paginator(queryset, request.GET.get('count', 25)),request.GET.get('page', 1)) | ||
| 432 | context = { 'build': Build.objects.filter(pk=build_id)[0], | ||
| 433 | 'target': Target.objects.filter(pk=target_id)[0], | ||
| 434 | 'objects': packages, | ||
| 435 | 'packages_sum' : packages_sum['installed_size__sum'], | ||
| 436 | 'object_search_display': "packages included", | ||
| 437 | 'tablecols':[ | ||
| 438 | { | ||
| 439 | 'name':'Package', | ||
| 440 | 'qhelp':'Packaged output resulting from building a recipe and included in this image', | ||
| 441 | 'orderfield': _get_toggle_order(request, "name"), | ||
| 442 | 'ordericon':_get_toggle_order_icon(request, "name"), | ||
| 443 | }, | ||
| 444 | { | ||
| 445 | 'name':'Package version', | ||
| 446 | 'qhelp':'The package version and revision', | ||
| 447 | }, | ||
| 448 | { | ||
| 449 | 'name':'Size', | ||
| 450 | 'qhelp':'The size of the package', | ||
| 451 | 'orderfield': _get_toggle_order(request, "size"), | ||
| 452 | 'ordericon':_get_toggle_order_icon(request, "size"), | ||
| 453 | 'clclass': 'package_size', | ||
| 454 | 'hidden' : 0, | ||
| 455 | }, | ||
| 456 | { | ||
| 457 | 'name':'Size over total (%)', | ||
| 458 | 'qhelp':'Proportion of the overall included package size represented by this package', | ||
| 459 | 'orderfield': _get_toggle_order(request, "size"), | ||
| 460 | 'ordericon':_get_toggle_order_icon(request, "size"), | ||
| 461 | 'clclass': 'size_over_total', | ||
| 462 | 'hidden' : 1, | ||
| 463 | }, | ||
| 464 | { | ||
| 465 | 'name':'License', | ||
| 466 | 'qhelp':'The license under which the package is distributed. Separate license names using | (pipe) means there is a choice between licenses. Separate license names using & (ampersand) means multiple licenses exist that cover different parts of the source', | ||
| 467 | 'orderfield': _get_toggle_order(request, "license"), | ||
| 468 | 'ordericon':_get_toggle_order_icon(request, "license"), | ||
| 469 | 'clclass': 'license', | ||
| 470 | 'hidden' : 1, | ||
| 471 | }, | ||
| 472 | { | ||
| 473 | 'name':'Dependencies', | ||
| 474 | 'qhelp':"Package runtime dependencies (other packages)", | ||
| 475 | 'clclass': 'depends', | ||
| 476 | 'hidden' : 0, | ||
| 477 | }, | ||
| 478 | { | ||
| 479 | 'name':'Reverse dependencies', | ||
| 480 | 'qhelp':'Package run-time reverse dependencies (i.e. which other packages depend on this package', | ||
| 481 | 'clclass': 'brought_in_by', | ||
| 482 | 'hidden' : 0, | ||
| 483 | }, | ||
| 484 | { | ||
| 485 | 'name':'Recipe', | ||
| 486 | 'qhelp':'The name of the recipe building the package', | ||
| 487 | 'orderfield': _get_toggle_order(request, "recipe__name"), | ||
| 488 | 'ordericon':_get_toggle_order_icon(request, "recipe__name"), | ||
| 489 | 'clclass': 'recipe_name', | ||
| 490 | 'hidden' : 0, | ||
| 491 | }, | ||
| 492 | { | ||
| 493 | 'name':'Recipe version', | ||
| 494 | 'qhelp':'Version and revision of the recipe building the package', | ||
| 495 | 'clclass': 'recipe_version', | ||
| 496 | 'hidden' : 1, | ||
| 497 | }, | ||
| 498 | { | ||
| 499 | 'name':'Layer', | ||
| 500 | 'qhelp':'The name of the layer providing the recipe that builds the package', | ||
| 501 | 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__name"), | ||
| 502 | 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__name"), | ||
| 503 | 'clclass': 'layer_name', | ||
| 504 | 'hidden' : 1, | ||
| 505 | }, | ||
| 506 | { | ||
| 507 | 'name':'Layer branch', | ||
| 508 | 'qhelp':'The Git branch of the layer providing the recipe that builds the package', | ||
| 509 | 'orderfield': _get_toggle_order(request, "recipe__layer_version__branch"), | ||
| 510 | 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__branch"), | ||
| 511 | 'clclass': 'layer_branch', | ||
| 512 | 'hidden' : 1, | ||
| 513 | }, | ||
| 514 | { | ||
| 515 | 'name':'Layer commit', | ||
| 516 | 'qhelp':'The Git commit of the layer providing the recipe that builds the package', | ||
| 517 | 'clclass': 'layer_commit', | ||
| 518 | 'hidden' : 1, | ||
| 519 | }, | ||
| 520 | { | ||
| 521 | 'name':'Layer directory', | ||
| 522 | 'qhelp':'Location in disk of the layer providing the recipe that builds the package', | ||
| 523 | 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__local_path"), | ||
| 524 | 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__local_path"), | ||
| 525 | 'clclass': 'layer_directory', | ||
| 526 | 'hidden' : 1, | ||
| 527 | }, | ||
| 528 | ] | ||
| 529 | } | ||
| 530 | |||
| 425 | return render(request, template, context) | 531 | return render(request, template, context) |
| 426 | 532 | ||
| 533 | from django.core.serializers.json import DjangoJSONEncoder | ||
| 534 | from django.http import HttpResponse | ||
| 535 | def dirinfo_ajax(request, build_id, target_id): | ||
| 536 | top = request.GET.get('start', '/') | ||
| 537 | return HttpResponse(_get_dir_entries(build_id, target_id, top)) | ||
| 538 | |||
| 539 | from django.utils.functional import Promise | ||
| 540 | from django.utils.encoding import force_text | ||
| 541 | class LazyEncoder(json.JSONEncoder): | ||
| 542 | def default(self, obj): | ||
| 543 | if isinstance(obj, Promise): | ||
| 544 | return force_text(obj) | ||
| 545 | return super(LazyEncoder, self).default(obj) | ||
| 546 | |||
| 547 | from toastergui.templatetags.projecttags import filtered_filesizeformat | ||
| 548 | from django import template | ||
| 549 | import os | ||
| 550 | def _get_dir_entries(build_id, target_id, start): | ||
| 551 | node_str = { | ||
| 552 | Target_File.ITYPE_REGULAR : '-', | ||
| 553 | Target_File.ITYPE_DIRECTORY : 'd', | ||
| 554 | Target_File.ITYPE_SYMLINK : 'l', | ||
| 555 | Target_File.ITYPE_SOCKET : 's', | ||
| 556 | Target_File.ITYPE_FIFO : 'p', | ||
| 557 | Target_File.ITYPE_CHARACTER : 'c', | ||
| 558 | Target_File.ITYPE_BLOCK : 'b', | ||
| 559 | } | ||
| 560 | response = [] | ||
| 561 | objects = Target_File.objects.filter(target__exact=target_id, directory__path=start) | ||
| 562 | target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True) | ||
| 563 | for o in objects: | ||
| 564 | # exclude root inode '/' | ||
| 565 | if o.path == '/': | ||
| 566 | continue | ||
| 567 | try: | ||
| 568 | entry = {} | ||
| 569 | entry['parent'] = start | ||
| 570 | entry['name'] = os.path.basename(o.path) | ||
| 571 | entry['fullpath'] = o.path | ||
| 572 | |||
| 573 | # set defaults, not all dentries have packages | ||
| 574 | entry['installed_package'] = None | ||
| 575 | entry['package_id'] = None | ||
| 576 | entry['package'] = None | ||
| 577 | entry['link_to'] = None | ||
| 578 | if o.inodetype == Target_File.ITYPE_DIRECTORY: | ||
| 579 | entry['isdir'] = 1 | ||
| 580 | # is there content in directory | ||
| 581 | entry['childcount'] = Target_File.objects.filter(directory__path=o.path).all().count() | ||
| 582 | else: | ||
| 583 | entry['isdir'] = 0 | ||
| 584 | |||
| 585 | # resolve the file to get the package from the resolved file | ||
| 586 | resolved_id = o.sym_target_id | ||
| 587 | resolved_path = o.path | ||
| 588 | if target_packages.count(): | ||
| 589 | while resolved_id != "" and resolved_id != None: | ||
| 590 | tf = Target_File.objects.get(pk=resolved_id) | ||
| 591 | resolved_path = tf.path | ||
| 592 | resolved_id = tf.sym_target_id | ||
| 593 | |||
| 594 | thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages) | ||
| 595 | if thisfile.count(): | ||
| 596 | p = Package.objects.get(pk=thisfile[0].package_id) | ||
| 597 | entry['installed_package'] = p.installed_name | ||
| 598 | entry['package_id'] = str(p.id) | ||
| 599 | entry['package'] = p.name | ||
| 600 | # don't use resolved path from above, show immediate link-to | ||
| 601 | if o.sym_target_id != "" and o.sym_target_id != None: | ||
| 602 | entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path | ||
| 603 | t = template.Template('{% load projecttags %} {{ size|filtered_filesizeformat }}') | ||
| 604 | c = template.Context({'size': o.size}) | ||
| 605 | entry['size'] = str(t.render(c)) | ||
| 606 | if entry['link_to'] != None: | ||
| 607 | entry['permission'] = node_str[o.inodetype] + o.permission | ||
| 608 | else: | ||
| 609 | entry['permission'] = node_str[o.inodetype] + o.permission | ||
| 610 | entry['owner'] = o.owner | ||
| 611 | entry['group'] = o.group | ||
| 612 | response.append(entry) | ||
| 613 | |||
| 614 | except: | ||
| 615 | pass | ||
| 616 | |||
| 617 | # sort by directories first, then by name | ||
| 618 | rsorted = sorted(response, key=lambda entry : entry['name']) | ||
| 619 | rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True) | ||
| 620 | return json.dumps(rsorted, cls=LazyEncoder) | ||
| 621 | |||
| 622 | def dirinfo(request, build_id, target_id, file_path=None): | ||
| 623 | template = "dirinfo.html" | ||
| 624 | objects = _get_dir_entries(build_id, target_id, '/') | ||
| 625 | packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size')) | ||
| 626 | dir_list = None | ||
| 627 | if file_path != None: | ||
| 628 | """ | ||
| 629 | Link from the included package detail file list page and is | ||
| 630 | requesting opening the dir info to a specific file path. | ||
| 631 | Provide the list of directories to expand and the full path to | ||
| 632 | highlight in the page. | ||
| 633 | """ | ||
| 634 | # Aassume target's path separator matches host's, that is, os.sep | ||
| 635 | sep = os.sep | ||
| 636 | dir_list = [] | ||
| 637 | head = file_path | ||
| 638 | while head != sep: | ||
| 639 | (head,tail) = os.path.split(head) | ||
| 640 | if head != sep: | ||
| 641 | dir_list.insert(0, head) | ||
| 642 | |||
| 643 | context = { 'build': Build.objects.filter(pk=build_id)[0], | ||
| 644 | 'target': Target.objects.filter(pk=target_id)[0], | ||
| 645 | 'packages_sum': packages_sum['installed_size__sum'], | ||
| 646 | 'objects': objects, | ||
| 647 | 'dir_list': dir_list, | ||
| 648 | 'file_path': file_path, | ||
| 649 | } | ||
| 650 | return render(request, template, context) | ||
| 427 | 651 | ||
| 428 | def _find_task_dep(task): | 652 | def _find_task_dep(task): |
| 429 | tp = [] | 653 | tp = [] |
| @@ -593,7 +817,7 @@ def tasks_common(request, build_id, variant): | |||
| 593 | } | 817 | } |
| 594 | 818 | ||
| 595 | } | 819 | } |
| 596 | #if 'tasks' == variant: tc_cache['hidden']='0'; | 820 | #if 'tasks' == variant: tc_cache['hidden']='0'; |
| 597 | tc_time={ | 821 | tc_time={ |
| 598 | 'name':'Time (secs)', | 822 | 'name':'Time (secs)', |
| 599 | 'qhelp':'How long it took the task to finish, expressed in seconds', | 823 | 'qhelp':'How long it took the task to finish, expressed in seconds', |
| @@ -796,7 +1020,7 @@ def configvars(request, build_id): | |||
| 796 | # remove duplicate records from multiple search hits in the VariableHistory table | 1020 | # remove duplicate records from multiple search hits in the VariableHistory table |
| 797 | queryset = queryset.distinct() | 1021 | queryset = queryset.distinct() |
| 798 | # remove records where the value is empty AND there are no history files | 1022 | # remove records where the value is empty AND there are no history files |
| 799 | queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True) | 1023 | queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True) |
| 800 | 1024 | ||
| 801 | variables = _build_page_range(Paginator(queryset, request.GET.get('count', 50)), request.GET.get('page', 1)) | 1025 | variables = _build_page_range(Paginator(queryset, request.GET.get('count', 50)), request.GET.get('page', 1)) |
| 802 | 1026 | ||
| @@ -811,7 +1035,7 @@ def configvars(request, build_id): | |||
| 811 | file_filter += 'conf/distro/' | 1035 | file_filter += 'conf/distro/' |
| 812 | if filter_string.find('/bitbake.conf') > 0: | 1036 | if filter_string.find('/bitbake.conf') > 0: |
| 813 | file_filter += '/bitbake.conf' | 1037 | file_filter += '/bitbake.conf' |
| 814 | 1038 | ||
| 815 | context = { | 1039 | context = { |
| 816 | 'objectname': 'configvars', | 1040 | 'objectname': 'configvars', |
| 817 | 'object_search_display':'variables', | 1041 | 'object_search_display':'variables', |
