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', |