summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/toastergui/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/toastergui/views.py')
-rw-r--r--bitbake/lib/toaster/toastergui/views.py258
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
22import operator 22import operator
23 23
24from django.db.models import Q 24from django.db.models import Q, Sum
25from django.shortcuts import render, redirect 25from django.shortcuts import render, redirect
26from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable 26from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
27from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency 27from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency
28from orm.models import Target_Installed_Package 28from orm.models import Target_Installed_Package, Target_File
29from django.views.decorators.cache import cache_control 29from django.views.decorators.cache import cache_control
30from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger 30from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
31from django.http import HttpResponseBadRequest 31from django.http import HttpResponseBadRequest
32from django.utils import timezone 32from django.utils import timezone
33from datetime import timedelta 33from datetime import timedelta
34from django.utils import formats 34from django.utils import formats
35import json
35 36
36def _build_page_range(paginator, index = 1): 37def _build_page_range(paginator, index = 1):
37 try: 38 try:
@@ -163,7 +164,7 @@ def _get_search_results(search_term, queryset, model):
163def _search_tuple(request, model): 164def _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
418def target(request, build_id, target_id): 419def 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
533from django.core.serializers.json import DjangoJSONEncoder
534from django.http import HttpResponse
535def 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
539from django.utils.functional import Promise
540from django.utils.encoding import force_text
541class 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
547from toastergui.templatetags.projecttags import filtered_filesizeformat
548from django import template
549import os
550def _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
622def 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
428def _find_task_dep(task): 652def _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',