summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/toastergui/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/toastergui/views.py')
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py2415
1 files changed, 2415 insertions, 0 deletions
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
new file mode 100755
index 0000000000..ea81423d51
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -0,0 +1,2415 @@
1#
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# BitBake Toaster Implementation
6#
7# Copyright (C) 2013 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22import operator,re
23import HTMLParser
24
25from django.db.models import Q, Sum
26from django.db import IntegrityError
27from django.shortcuts import render, redirect
28from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
29from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency
30from orm.models import Target_Installed_Package, Target_File, Target_Image_File
31from django.views.decorators.cache import cache_control
32from django.core.urlresolvers import reverse
33from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
34from django.http import HttpResponseBadRequest, HttpResponseNotFound
35from django.utils import timezone
36from django.utils.html import escape
37from datetime import timedelta
38from django.utils import formats
39import json
40
41def _build_page_range(paginator, index = 1):
42 try:
43 page = paginator.page(index)
44 except PageNotAnInteger:
45 page = paginator.page(1)
46 except EmptyPage:
47 page = paginator.page(paginator.num_pages)
48
49
50 page.page_range = [page.number]
51 crt_range = 0
52 for i in range(1,5):
53 if (page.number + i) <= paginator.num_pages:
54 page.page_range = page.page_range + [ page.number + i]
55 crt_range +=1
56 if (page.number - i) > 0:
57 page.page_range = [page.number -i] + page.page_range
58 crt_range +=1
59 if crt_range == 4:
60 break
61 return page
62
63
64def _verify_parameters(g, mandatory_parameters):
65 miss = []
66 for mp in mandatory_parameters:
67 if not mp in g:
68 miss.append(mp)
69 if len(miss):
70 return miss
71 return None
72
73def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
74 import urllib
75 url = reverse(view, kwargs=kwargs)
76 params = {}
77 for i in g:
78 params[i] = g[i]
79 for i in mandatory_parameters:
80 if not i in params:
81 params[i] = mandatory_parameters[i]
82
83 return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs)
84
85FIELD_SEPARATOR = ":"
86VALUE_SEPARATOR = "!"
87DESCENDING = "-"
88
89def __get_q_for_val(name, value):
90 if "OR" in value:
91 return reduce(operator.or_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("OR") ]))
92 if "AND" in value:
93 return reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ]))
94 if value.startswith("NOT"):
95 kwargs = { name : value.strip("NOT") }
96 return ~Q(**kwargs)
97 else:
98 kwargs = { name : value }
99 return Q(**kwargs)
100
101def _get_filtering_query(filter_string):
102
103 search_terms = filter_string.split(FIELD_SEPARATOR)
104 keys = search_terms[0].split(VALUE_SEPARATOR)
105 values = search_terms[1].split(VALUE_SEPARATOR)
106
107 querydict = dict(zip(keys, values))
108 return reduce(operator.and_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict]))
109
110def _get_toggle_order(request, orderkey, reverse = False):
111 if reverse:
112 return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
113 else:
114 return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
115
116def _get_toggle_order_icon(request, orderkey):
117 if request.GET.get('orderby', "") == "%s:+"%orderkey:
118 return "down"
119 elif request.GET.get('orderby', "") == "%s:-"%orderkey:
120 return "up"
121 else:
122 return None
123
124# we check that the input comes in a valid form that we can recognize
125def _validate_input(input, model):
126
127 invalid = None
128
129 if input:
130 input_list = input.split(FIELD_SEPARATOR)
131
132 # Check we have only one colon
133 if len(input_list) != 2:
134 invalid = "We have an invalid number of separators: " + input + " -> " + str(input_list)
135 return None, invalid
136
137 # Check we have an equal number of terms both sides of the colon
138 if len(input_list[0].split(VALUE_SEPARATOR)) != len(input_list[1].split(VALUE_SEPARATOR)):
139 invalid = "Not all arg names got values"
140 return None, invalid + str(input_list)
141
142 # Check we are looking for a valid field
143 valid_fields = model._meta.get_all_field_names()
144 for field in input_list[0].split(VALUE_SEPARATOR):
145 if not reduce(lambda x, y: x or y, map(lambda x: field.startswith(x), [ x for x in valid_fields ])):
146 return None, (field, [ x for x in valid_fields ])
147
148 return input, invalid
149
150# uses search_allowed_fields in orm/models.py to create a search query
151# for these fields with the supplied input text
152def _get_search_results(search_term, queryset, model):
153 search_objects = []
154 for st in search_term.split(" "):
155 q_map = map(lambda x: Q(**{x+'__icontains': st}),
156 model.search_allowed_fields)
157
158 search_objects.append(reduce(operator.or_, q_map))
159 search_object = reduce(operator.and_, search_objects)
160 queryset = queryset.filter(search_object)
161
162 return queryset
163
164
165# function to extract the search/filter/ordering parameters from the request
166# it uses the request and the model to validate input for the filter and orderby values
167def _search_tuple(request, model):
168 ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
169 if invalid:
170 raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
171
172 filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
173 if invalid:
174 raise BaseException("Invalid filter " + str(invalid))
175
176 search_term = request.GET.get('search', '')
177 return (filter_string, search_term, ordering_string)
178
179
180# returns a lazy-evaluated queryset for a filter/search/order combination
181def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
182 if filter_string:
183 filter_query = _get_filtering_query(filter_string)
184 queryset = queryset.filter(filter_query)
185 else:
186 queryset = queryset.all()
187
188 if search_term:
189 queryset = _get_search_results(search_term, queryset, model)
190
191 if ordering_string and queryset:
192 column, order = ordering_string.split(':')
193 if column == re.sub('-','',ordering_secondary):
194 ordering_secondary=''
195 if order.lower() == DESCENDING:
196 column = '-' + column
197 if ordering_secondary:
198 queryset = queryset.order_by(column, ordering_secondary)
199 else:
200 queryset = queryset.order_by(column)
201
202 # insure only distinct records (e.g. from multiple search hits) are returned
203 return queryset.distinct()
204
205# returns the value of entries per page and the name of the applied sorting field.
206# if the value is given explicitly as a GET parameter it will be the first selected,
207# otherwise the cookie value will be used.
208def _get_parameters_values(request, default_count, default_order):
209 pagesize = request.GET.get('count', request.COOKIES.get('count', default_count))
210 orderby = request.GET.get('orderby', request.COOKIES.get('orderby', default_order))
211 return (pagesize, orderby)
212
213
214# set cookies for parameters. this is usefull in case parameters are set
215# manually from the GET values of the link
216def _save_parameters_cookies(response, pagesize, orderby, request):
217 html_parser = HTMLParser.HTMLParser()
218 response.set_cookie(key='count', value=pagesize, path=request.path)
219 response.set_cookie(key='orderby', value=html_parser.unescape(orderby), path=request.path)
220 return response
221
222# shows the "all builds" page
223def builds(request):
224 template = 'build.html'
225 # define here what parameters the view needs in the GET portion in order to
226 # be able to display something. 'count' and 'page' are mandatory for all views
227 # that use paginators.
228 (pagesize, orderby) = _get_parameters_values(request, 10, 'completed_on:-')
229 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
230 retval = _verify_parameters( request.GET, mandatory_parameters )
231 if retval:
232 return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
233
234 # boilerplate code that takes a request for an object type and returns a queryset
235 # for that object type. copypasta for all needed table searches
236 (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
237 queryset_all = Build.objects.exclude(outcome = Build.IN_PROGRESS)
238 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
239 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
240
241 # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
242 build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
243
244 # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
245 build_mru = Build.objects.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).order_by("-started_on")[:3]
246
247 # set up list of fstypes for each build
248 fstypes_map = {};
249 for build in build_info:
250 targets = Target.objects.filter( build_id = build.id )
251 comma = "";
252 extensions = "";
253 for t in targets:
254 if ( not t.is_image ):
255 continue
256 tif = Target_Image_File.objects.filter( target_id = t.id )
257 for i in tif:
258 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
259 if s == i.file_name:
260 s=re.sub('.*\.', '', i.file_name)
261 if None == re.search(s,extensions):
262 extensions += comma + s
263 comma = ", "
264 fstypes_map[build.id]=extensions
265
266 # send the data to the template
267 context = {
268 # specific info for
269 'mru' : build_mru,
270 # TODO: common objects for all table views, adapt as needed
271 'objects' : build_info,
272 'objectname' : "builds",
273 'default_orderby' : 'completed_on:-',
274 'fstypes' : fstypes_map,
275 'search_term' : search_term,
276 'total_count' : queryset_with_search.count(),
277 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
278 'tablecols' : [
279 {'name': 'Outcome', # column with a single filter
280 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
281 'dclass' : "span2", # indication about column width; comes from the design
282 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
283 'ordericon':_get_toggle_order_icon(request, "outcome"),
284 # filter field will set a filter on that column with the specs in the filter description
285 # the class field in the filter has no relation with clclass; the control different aspects of the UI
286 # still, it is recommended for the values to be identical for easy tracking in the generated HTML
287 'filter' : {'class' : 'outcome',
288 'label': 'Show:',
289 'options' : [
290 ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression
291 ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
292 ]
293 }
294 },
295 {'name': 'Target', # default column, disabled box, with just the name in the list
296 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
297 'orderfield': _get_toggle_order(request, "target__target"),
298 'ordericon':_get_toggle_order_icon(request, "target__target"),
299 },
300 {'name': 'Machine',
301 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
302 'orderfield': _get_toggle_order(request, "machine"),
303 'ordericon':_get_toggle_order_icon(request, "machine"),
304 'dclass': 'span3'
305 }, # a slightly wider column
306 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
307 'qhelp': "The date and time you started the build",
308 'orderfield': _get_toggle_order(request, "started_on", True),
309 'ordericon':_get_toggle_order_icon(request, "started_on"),
310 'filter' : {'class' : 'started_on',
311 'label': 'Show:',
312 'options' : [
313 ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()),
314 ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()),
315 ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()),
316 ]
317 }
318 },
319 {'name': 'Completed on',
320 'qhelp': "The date and time the build finished",
321 'orderfield': _get_toggle_order(request, "completed_on", True),
322 'ordericon':_get_toggle_order_icon(request, "completed_on"),
323 'orderkey' : 'completed_on',
324 'filter' : {'class' : 'completed_on',
325 'label': 'Show:',
326 'options' : [
327 ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()),
328 ("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))).count()),
329 ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()),
330 ]
331 }
332 },
333 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
334 'qhelp': "How many tasks failed during the build",
335 'filter' : {'class' : 'failed_tasks',
336 'label': 'Show:',
337 'options' : [
338 ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
339 ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
340 ]
341 }
342 },
343 {'name': 'Errors', 'clclass': 'errors_no',
344 'qhelp': "How many errors were encountered during the build (if any)",
345 'orderfield': _get_toggle_order(request, "errors_no", True),
346 'ordericon':_get_toggle_order_icon(request, "errors_no"),
347 'orderkey' : 'errors_no',
348 'filter' : {'class' : 'errors_no',
349 'label': 'Show:',
350 'options' : [
351 ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
352 ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
353 ]
354 }
355 },
356 {'name': 'Warnings', 'clclass': 'warnings_no',
357 'qhelp': "How many warnings were encountered during the build (if any)",
358 'orderfield': _get_toggle_order(request, "warnings_no", True),
359 'ordericon':_get_toggle_order_icon(request, "warnings_no"),
360 'orderkey' : 'warnings_no',
361 'filter' : {'class' : 'warnings_no',
362 'label': 'Show:',
363 'options' : [
364 ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
365 ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
366 ]
367 }
368 },
369 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
370 'qhelp': "How long it took the build to finish",
371 'orderfield': _get_toggle_order(request, "timespent", True),
372 'ordericon':_get_toggle_order_icon(request, "timespent"),
373 'orderkey' : 'timespent',
374 },
375 {'name': 'Log',
376 'dclass': "span4",
377 'qhelp': "Path to the build main log file",
378 'clclass': 'log', 'hidden': 1,
379 'orderfield': _get_toggle_order(request, "cooker_log_path"),
380 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
381 'orderkey' : 'cooker_log_path',
382 },
383 {'name': 'Output', 'clclass': 'output',
384 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
385 # TODO: compute image fstypes from Target_Image_File
386 },
387 ]
388 }
389
390 response = render(request, template, context)
391 _save_parameters_cookies(response, pagesize, orderby, request)
392 return response
393
394
395##
396# build dashboard for a single build, coming in as argument
397# Each build may contain multiple targets and each target
398# may generate multiple image files. display them all.
399#
400def builddashboard( request, build_id ):
401 template = "builddashboard.html"
402 if Build.objects.filter( pk=build_id ).count( ) == 0 :
403 return redirect( builds )
404 build = Build.objects.filter( pk = build_id )[ 0 ];
405 layerVersionId = Layer_Version.objects.filter( build = build_id );
406 recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
407 tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
408
409 ##
410 # set up custom target list with computed package and image data
411 #
412
413 targets = [ ]
414 ntargets = 0
415 hasImages = False
416 targetHasNoImages = False
417 for t in tgts:
418 elem = { }
419 elem[ 'target' ] = t
420 if ( t.is_image ):
421 hasImages = True
422 npkg = 0
423 pkgsz = 0
424 pid= 0
425 tp = Target_Installed_Package.objects.filter( target_id = t.id )
426 package = None
427 for p in tp:
428 pid = p.package_id
429 package = Package.objects.get( pk = p.package_id )
430 pkgsz = pkgsz + package.size
431 if ( package.installed_name ):
432 npkg = npkg + 1
433 elem[ 'npkg' ] = npkg
434 elem[ 'pkgsz' ] = pkgsz
435 ti = Target_Image_File.objects.filter( target_id = t.id )
436 imageFiles = [ ]
437 for i in ti:
438 ndx = i.file_name.rfind( '/' )
439 if ( ndx < 0 ):
440 ndx = 0;
441 f = i.file_name[ ndx + 1: ]
442 imageFiles.append({ 'path': f, 'size' : i.file_size })
443 if ( t.is_image and
444 (( len( imageFiles ) <= 0 ) or ( len( t.license_manifest_path ) <= 0 ))):
445 targetHasNoImages = True
446 elem[ 'imageFiles' ] = imageFiles
447 elem[ 'targetHasNoImages' ] = targetHasNoImages
448 targets.append( elem )
449
450 ##
451 # how many packages in this build - ignore anonymous ones
452 #
453
454 packageCount = 0
455 packages = Package.objects.filter( build_id = build_id )
456 for p in packages:
457 if ( p.installed_name ):
458 packageCount = packageCount + 1
459
460 context = {
461 'build' : build,
462 'hasImages' : hasImages,
463 'ntargets' : ntargets,
464 'targets' : targets,
465 'recipecount' : recipeCount,
466 'packagecount' : packageCount,
467 'logmessages' : LogMessage.objects.filter( build = build_id ),
468 }
469 return render( request, template, context )
470
471
472def generateCoveredList( task ):
473 revList = _find_task_revdep( task );
474 list = { };
475 for t in revList:
476 if ( t.outcome == Task.OUTCOME_COVERED ):
477 list.update( generateCoveredList( t ));
478 else:
479 list[ t.task_name ] = t;
480 return( list );
481
482def task( request, build_id, task_id ):
483 template = "task.html"
484 tasks = Task.objects.filter( pk=task_id )
485 if tasks.count( ) == 0:
486 return redirect( builds )
487 task = tasks[ 0 ];
488 dependencies = sorted(
489 _find_task_dep( task ),
490 key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
491 reverse_dependencies = sorted(
492 _find_task_revdep( task ),
493 key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
494 coveredBy = '';
495 if ( task.outcome == Task.OUTCOME_COVERED ):
496 dict = generateCoveredList( task )
497 coveredBy = [ ]
498 for name, t in dict.items( ):
499 coveredBy.append( t )
500 log_head = ''
501 log_body = ''
502 if task.outcome == task.OUTCOME_FAILED:
503 pass
504
505 uri_list= [ ]
506 variables = Variable.objects.filter(build=build_id)
507 v=variables.filter(variable_name='SSTATE_DIR')
508 if v.count > 0:
509 uri_list.append(v[0].variable_value)
510 v=variables.filter(variable_name='SSTATE_MIRRORS')
511 if (v.count > 0):
512 for mirror in v[0].variable_value.split('\\n'):
513 s=re.sub('.* ','',mirror.strip(' \t\n\r'))
514 if len(s): uri_list.append(s)
515
516 context = {
517 'build' : Build.objects.filter( pk = build_id )[ 0 ],
518 'object' : task,
519 'task' : task,
520 'covered_by' : coveredBy,
521 'deps' : dependencies,
522 'rdeps' : reverse_dependencies,
523 'log_head' : log_head,
524 'log_body' : log_body,
525 'showing_matches' : False,
526 'uri_list' : uri_list,
527 }
528 if request.GET.get( 'show_matches', "" ):
529 context[ 'showing_matches' ] = True
530 context[ 'matching_tasks' ] = Task.objects.filter(
531 sstate_checksum=task.sstate_checksum ).filter(
532 build__completed_on__lt=task.build.completed_on).exclude(
533 order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
534
535 return render( request, template, context )
536
537
538def recipe(request, build_id, recipe_id):
539 template = "recipe.html"
540 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
541 return redirect(builds)
542
543 object = Recipe.objects.filter(pk=recipe_id)[0]
544 layer_version = Layer_Version.objects.filter(pk=object.layer_version_id)[0]
545 layer = Layer.objects.filter(pk=layer_version.layer_id)[0]
546 tasks = Task.objects.filter(recipe_id = recipe_id, build_id = build_id).exclude(order__isnull=True).exclude(task_name__endswith='_setscene').exclude(outcome=Task.OUTCOME_NA)
547 packages = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
548
549 context = {
550 'build' : Build.objects.filter(pk=build_id)[0],
551 'object' : object,
552 'layer_version' : layer_version,
553 'layer' : layer,
554 'tasks' : tasks,
555 'packages': packages,
556 }
557 return render(request, template, context)
558
559def target_common( request, build_id, target_id, variant ):
560 template = "target.html"
561 (pagesize, orderby) = _get_parameters_values(request, 25, 'name:+')
562 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
563 retval = _verify_parameters( request.GET, mandatory_parameters )
564 if retval:
565 return _redirect_parameters(
566 variant, request.GET, mandatory_parameters,
567 build_id = build_id, target_id = target_id )
568 ( filter_string, search_term, ordering_string ) = _search_tuple( request, Package )
569
570 # FUTURE: get rid of nested sub-queries replacing with ManyToMany field
571 queryset = Package.objects.filter(
572 size__gte = 0,
573 id__in = Target_Installed_Package.objects.filter(
574 target_id=target_id ).values( 'package_id' ))
575 packages_sum = queryset.aggregate( Sum( 'installed_size' ))
576 queryset = _get_queryset(
577 Package, queryset, filter_string, search_term, ordering_string, 'name' )
578 packages = _build_page_range( Paginator(queryset, pagesize), request.GET.get( 'page', 1 ))
579
580 # bring in package dependencies
581 for p in packages.object_list:
582 p.runtime_dependencies = p.package_dependencies_source.filter(
583 target_id = target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS )
584 p.reverse_runtime_dependencies = p.package_dependencies_target.filter(
585 target_id = target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS )
586 tc_package = {
587 'name' : 'Package',
588 'qhelp' : 'Packaged output resulting from building a recipe included in this image',
589 'orderfield' : _get_toggle_order( request, "name" ),
590 'ordericon' : _get_toggle_order_icon( request, "name" ),
591 }
592 tc_packageVersion = {
593 'name' : 'Package version',
594 'qhelp' : 'The package version and revision',
595 }
596 tc_size = {
597 'name' : 'Size',
598 'qhelp' : 'The size of the package',
599 'orderfield' : _get_toggle_order( request, "size", True ),
600 'ordericon' : _get_toggle_order_icon( request, "size" ),
601 'orderkey' : 'size',
602 'clclass' : 'size',
603 'dclass' : 'span2',
604 }
605 if ( variant == 'target' ):
606 tc_size[ "hidden" ] = 0
607 else:
608 tc_size[ "hidden" ] = 1
609 tc_sizePercentage = {
610 'name' : 'Size over total (%)',
611 'qhelp' : 'Proportion of the overall size represented by this package',
612 'orderfield' : _get_toggle_order( request, "size" ),
613 'ordericon' : _get_toggle_order_icon( request, "size" ),
614 'clclass' : 'size_over_total',
615 'hidden' : 1,
616 }
617 tc_license = {
618 'name' : 'License',
619 'qhelp' : 'The license under which the package is distributed. Separate license names u\
620sing | (pipe) means there is a choice between licenses. Separate license names using & (ampersand) m\
621eans multiple licenses exist that cover different parts of the source',
622 'orderfield' : _get_toggle_order( request, "license" ),
623 'ordericon' : _get_toggle_order_icon( request, "license" ),
624 'orderkey' : 'license',
625 'clclass' : 'license',
626 }
627 if ( variant == 'target' ):
628 tc_license[ "hidden" ] = 1
629 else:
630 tc_license[ "hidden" ] = 0
631 tc_dependencies = {
632 'name' : 'Dependencies',
633 'qhelp' : "Package runtime dependencies (other packages)",
634 'clclass' : 'depends',
635 }
636 if ( variant == 'target' ):
637 tc_dependencies[ "hidden" ] = 0
638 else:
639 tc_dependencies[ "hidden" ] = 1
640 tc_rdependencies = {
641 'name' : 'Reverse dependencies',
642 'qhelp' : 'Package run-time reverse dependencies (i.e. which other packages depend on t\
643his package',
644 'clclass' : 'brought_in_by',
645 }
646 if ( variant == 'target' ):
647 tc_rdependencies[ "hidden" ] = 0
648 else:
649 tc_rdependencies[ "hidden" ] = 1
650 tc_recipe = {
651 'name' : 'Recipe',
652 'qhelp' : 'The name of the recipe building the package',
653 'orderfield' : _get_toggle_order( request, "recipe__name" ),
654 'ordericon' : _get_toggle_order_icon( request, "recipe__name" ),
655 'clclass' : 'recipe_name',
656 'hidden' : 0,
657 }
658 tc_recipeVersion = {
659 'name' : 'Recipe version',
660 'qhelp' : 'Version and revision of the recipe building the package',
661 'clclass' : 'recipe_version',
662 'hidden' : 1,
663 }
664 tc_layer = {
665 'name' : 'Layer',
666 'qhelp' : 'The name of the layer providing the recipe that builds the package',
667 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__name" ),
668 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__name" ),
669 'clclass' : 'layer_name',
670 'hidden' : 1,
671 }
672 tc_layerBranch = {
673 'name' : 'Layer branch',
674 'qhelp' : 'The Git branch of the layer providing the recipe that builds the package',
675 'orderfield' : _get_toggle_order( request, "recipe__layer_version__branch" ),
676 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__branch" ),
677 'clclass' : 'layer_branch',
678 'hidden' : 1,
679 }
680 tc_layerCommit = {
681 'name' : 'Layer commit',
682 'qhelp' : 'The Git commit of the layer providing the recipe that builds the package',
683 'clclass' : 'layer_commit',
684 'hidden' : 1,
685 }
686 tc_layerDir = {
687 'name':'Layer directory',
688 'qhelp':'Location in disk of the layer providing the recipe that builds the package',
689 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__local_path" ),
690 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__local_path" )\
691,
692 'clclass' : 'layer_directory',
693 'hidden' : 1,
694 }
695 context = {
696 'objectname': variant,
697 'build' : Build.objects.filter( pk = build_id )[ 0 ],
698 'target' : Target.objects.filter( pk = target_id )[ 0 ],
699 'objects' : packages,
700 'packages_sum' : packages_sum[ 'installed_size__sum' ],
701 'object_search_display': "packages included",
702 'default_orderby' : orderby,
703 'tablecols' : [
704 tc_package,
705 tc_packageVersion,
706 tc_license,
707 tc_size,
708 tc_sizePercentage,
709 tc_dependencies,
710 tc_rdependencies,
711 tc_recipe,
712 tc_recipeVersion,
713 tc_layer,
714 tc_layerBranch,
715 tc_layerCommit,
716 tc_layerDir,
717 ]
718 }
719
720 response = render(request, template, context)
721 _save_parameters_cookies(response, pagesize, orderby, request)
722 return response
723
724def target( request, build_id, target_id ):
725 return( target_common( request, build_id, target_id, "target" ))
726
727def targetpkg( request, build_id, target_id ):
728 return( target_common( request, build_id, target_id, "targetpkg" ))
729
730from django.core.serializers.json import DjangoJSONEncoder
731from django.http import HttpResponse
732def dirinfo_ajax(request, build_id, target_id):
733 top = request.GET.get('start', '/')
734 return HttpResponse(_get_dir_entries(build_id, target_id, top))
735
736from django.utils.functional import Promise
737from django.utils.encoding import force_text
738class LazyEncoder(json.JSONEncoder):
739 def default(self, obj):
740 if isinstance(obj, Promise):
741 return force_text(obj)
742 return super(LazyEncoder, self).default(obj)
743
744from toastergui.templatetags.projecttags import filtered_filesizeformat
745import os
746def _get_dir_entries(build_id, target_id, start):
747 node_str = {
748 Target_File.ITYPE_REGULAR : '-',
749 Target_File.ITYPE_DIRECTORY : 'd',
750 Target_File.ITYPE_SYMLINK : 'l',
751 Target_File.ITYPE_SOCKET : 's',
752 Target_File.ITYPE_FIFO : 'p',
753 Target_File.ITYPE_CHARACTER : 'c',
754 Target_File.ITYPE_BLOCK : 'b',
755 }
756 response = []
757 objects = Target_File.objects.filter(target__exact=target_id, directory__path=start)
758 target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
759 for o in objects:
760 # exclude root inode '/'
761 if o.path == '/':
762 continue
763 try:
764 entry = {}
765 entry['parent'] = start
766 entry['name'] = os.path.basename(o.path)
767 entry['fullpath'] = o.path
768
769 # set defaults, not all dentries have packages
770 entry['installed_package'] = None
771 entry['package_id'] = None
772 entry['package'] = None
773 entry['link_to'] = None
774 if o.inodetype == Target_File.ITYPE_DIRECTORY:
775 entry['isdir'] = 1
776 # is there content in directory
777 entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
778 else:
779 entry['isdir'] = 0
780
781 # resolve the file to get the package from the resolved file
782 resolved_id = o.sym_target_id
783 resolved_path = o.path
784 if target_packages.count():
785 while resolved_id != "" and resolved_id != None:
786 tf = Target_File.objects.get(pk=resolved_id)
787 resolved_path = tf.path
788 resolved_id = tf.sym_target_id
789
790 thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
791 if thisfile.count():
792 p = Package.objects.get(pk=thisfile[0].package_id)
793 entry['installed_package'] = p.installed_name
794 entry['package_id'] = str(p.id)
795 entry['package'] = p.name
796 # don't use resolved path from above, show immediate link-to
797 if o.sym_target_id != "" and o.sym_target_id != None:
798 entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
799 entry['size'] = filtered_filesizeformat(o.size)
800 if entry['link_to'] != None:
801 entry['permission'] = node_str[o.inodetype] + o.permission
802 else:
803 entry['permission'] = node_str[o.inodetype] + o.permission
804 entry['owner'] = o.owner
805 entry['group'] = o.group
806 response.append(entry)
807
808 except Exception as e:
809 print "Exception ", e
810 import traceback
811 traceback.print_exc(e)
812 pass
813
814 # sort by directories first, then by name
815 rsorted = sorted(response, key=lambda entry : entry['name'])
816 rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
817 return json.dumps(rsorted, cls=LazyEncoder)
818
819def dirinfo(request, build_id, target_id, file_path=None):
820 template = "dirinfo.html"
821 objects = _get_dir_entries(build_id, target_id, '/')
822 packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
823 dir_list = None
824 if file_path != None:
825 """
826 Link from the included package detail file list page and is
827 requesting opening the dir info to a specific file path.
828 Provide the list of directories to expand and the full path to
829 highlight in the page.
830 """
831 # Aassume target's path separator matches host's, that is, os.sep
832 sep = os.sep
833 dir_list = []
834 head = file_path
835 while head != sep:
836 (head,tail) = os.path.split(head)
837 if head != sep:
838 dir_list.insert(0, head)
839
840 context = { 'build': Build.objects.filter(pk=build_id)[0],
841 'target': Target.objects.filter(pk=target_id)[0],
842 'packages_sum': packages_sum['installed_size__sum'],
843 'objects': objects,
844 'dir_list': dir_list,
845 'file_path': file_path,
846 }
847 return render(request, template, context)
848
849def _find_task_dep(task):
850 tp = []
851 for p in Task_Dependency.objects.filter(task=task):
852 if (p.depends_on.order > 0) and (p.depends_on.outcome != Task.OUTCOME_NA):
853 tp.append(p.depends_on);
854 return tp
855
856
857def _find_task_revdep(task):
858 tp = []
859 for p in Task_Dependency.objects.filter(depends_on=task):
860 if (p.task.order > 0) and (p.task.outcome != Task.OUTCOME_NA):
861 tp.append(p.task);
862 return tp
863
864def _find_task_provider(task):
865 task_revdeps = _find_task_revdep(task)
866 for tr in task_revdeps:
867 if tr.outcome != Task.OUTCOME_COVERED:
868 return tr
869 for tr in task_revdeps:
870 trc = _find_task_provider(tr)
871 if trc is not None:
872 return trc
873 return None
874
875def tasks_common(request, build_id, variant, task_anchor):
876# This class is shared between these pages
877#
878# Column tasks buildtime diskio cpuusage
879# --------- ------ ---------- ------- ---------
880# Cache def
881# CPU min -
882# Disk min -
883# Executed def def def def
884# Log
885# Order def +
886# Outcome def def def def
887# Recipe min min min min
888# Version
889# Task min min min min
890# Time min -
891#
892# 'min':on always, 'def':on by default, else hidden
893# '+' default column sort up, '-' default column sort down
894
895 anchor = request.GET.get('anchor', '')
896 if not anchor:
897 anchor=task_anchor
898
899 # default ordering depends on variant
900 if 'buildtime' == variant:
901 title_variant='Time'
902 object_search_display="time data"
903 filter_search_display="tasks"
904 (pagesize, orderby) = _get_parameters_values(request, 25, 'elapsed_time:-')
905 elif 'diskio' == variant:
906 title_variant='Disk I/O'
907 object_search_display="disk I/O data"
908 filter_search_display="tasks"
909 (pagesize, orderby) = _get_parameters_values(request, 25, 'disk_io:-')
910 elif 'cpuusage' == variant:
911 title_variant='CPU usage'
912 object_search_display="CPU usage data"
913 filter_search_display="tasks"
914 (pagesize, orderby) = _get_parameters_values(request, 25, 'cpu_usage:-')
915 else :
916 title_variant='Tasks'
917 object_search_display="tasks"
918 filter_search_display="tasks"
919 (pagesize, orderby) = _get_parameters_values(request, 25, 'order:+')
920
921
922 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
923
924 template = 'tasks.html'
925 retval = _verify_parameters( request.GET, mandatory_parameters )
926 if retval:
927 if task_anchor:
928 mandatory_parameters['anchor']=task_anchor
929 return _redirect_parameters( variant, request.GET, mandatory_parameters, build_id = build_id)
930 (filter_string, search_term, ordering_string) = _search_tuple(request, Task)
931 queryset_all = Task.objects.filter(build=build_id).exclude(order__isnull=True).exclude(outcome=Task.OUTCOME_NA)
932 queryset_with_search = _get_queryset(Task, queryset_all, None , search_term, ordering_string, 'order')
933 if ordering_string.startswith('outcome'):
934 queryset = _get_queryset(Task, queryset_all, filter_string, search_term, 'order:+', 'order')
935 queryset = sorted(queryset, key=lambda ur: (ur.outcome_text), reverse=ordering_string.endswith('-'))
936 elif ordering_string.startswith('sstate_result'):
937 queryset = _get_queryset(Task, queryset_all, filter_string, search_term, 'order:+', 'order')
938 queryset = sorted(queryset, key=lambda ur: (ur.sstate_text), reverse=ordering_string.endswith('-'))
939 else:
940 queryset = _get_queryset(Task, queryset_all, filter_string, search_term, ordering_string, 'order')
941
942 # compute the anchor's page
943 if anchor:
944 request.GET = request.GET.copy()
945 del request.GET['anchor']
946 i=0
947 a=int(anchor)
948 count_per_page=int(pagesize)
949 for task in queryset.iterator():
950 if a == task.order:
951 new_page= (i / count_per_page ) + 1
952 request.GET.__setitem__('page', new_page)
953 mandatory_parameters['page']=new_page
954 return _redirect_parameters( variant, request.GET, mandatory_parameters, build_id = build_id)
955 i += 1
956
957 tasks = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
958
959 # define (and modify by variants) the 'tablecols' members
960 tc_order={
961 'name':'Order',
962 'qhelp':'The running sequence of each task in the build',
963 'clclass': 'order', 'hidden' : 1,
964 'orderkey' : 'order',
965 'orderfield':_get_toggle_order(request, "order"),
966 'ordericon':_get_toggle_order_icon(request, "order")}
967 if 'tasks' == variant: tc_order['hidden']='0'; del tc_order['clclass']
968 tc_recipe={
969 'name':'Recipe',
970 'qhelp':'The name of the recipe to which each task applies',
971 'orderkey' : 'recipe__name',
972 'orderfield': _get_toggle_order(request, "recipe__name"),
973 'ordericon':_get_toggle_order_icon(request, "recipe__name"),
974 }
975 tc_recipe_version={
976 'name':'Recipe version',
977 'qhelp':'The version of the recipe to which each task applies',
978 'clclass': 'recipe_version', 'hidden' : 1,
979 }
980 tc_task={
981 'name':'Task',
982 'qhelp':'The name of the task',
983 'orderfield': _get_toggle_order(request, "task_name"),
984 'ordericon':_get_toggle_order_icon(request, "task_name"),
985 'orderkey' : 'task_name',
986 }
987 tc_executed={
988 'name':'Executed',
989 'qhelp':"This value tells you if a task had to run (executed) in order to generate the task output, or if the output was provided by another task and therefore the task didn't need to run (not executed)",
990 'clclass': 'executed', 'hidden' : 0,
991 'orderfield': _get_toggle_order(request, "task_executed"),
992 'ordericon':_get_toggle_order_icon(request, "task_executed"),
993 'orderkey' : 'task_executed',
994 'filter' : {
995 'class' : 'executed',
996 'label': 'Show:',
997 'options' : [
998 ('Executed Tasks', 'task_executed:1', queryset_with_search.filter(task_executed=1).count()),
999 ('Not Executed Tasks', 'task_executed:0', queryset_with_search.filter(task_executed=0).count()),
1000 ]
1001 }
1002
1003 }
1004 tc_outcome={
1005 'name':'Outcome',
1006 'qhelp':"This column tells you if 'executed' tasks succeeded or failed. The column also tells you why 'not executed' tasks did not need to run",
1007 'clclass': 'outcome', 'hidden' : 0,
1008 'orderfield': _get_toggle_order(request, "outcome"),
1009 'ordericon':_get_toggle_order_icon(request, "outcome"),
1010 'orderkey' : 'outcome',
1011 'filter' : {
1012 'class' : 'outcome',
1013 'label': 'Show:',
1014 'options' : [
1015 ('Succeeded Tasks', 'outcome:%d'%Task.OUTCOME_SUCCESS, queryset_with_search.filter(outcome=Task.OUTCOME_SUCCESS).count(), "'Succeeded' tasks are those that ran and completed during the build" ),
1016 ('Failed Tasks', 'outcome:%d'%Task.OUTCOME_FAILED, queryset_with_search.filter(outcome=Task.OUTCOME_FAILED).count(), "'Failed' tasks are those that ran but did not complete during the build"),
1017 ('Cached Tasks', 'outcome:%d'%Task.OUTCOME_CACHED, queryset_with_search.filter(outcome=Task.OUTCOME_CACHED).count(), 'Cached tasks restore output from the <code>sstate-cache</code> directory or mirrors'),
1018 ('Prebuilt Tasks', 'outcome:%d'%Task.OUTCOME_PREBUILT, queryset_with_search.filter(outcome=Task.OUTCOME_PREBUILT).count(),'Prebuilt tasks didn\'t need to run because their output was reused from a previous build'),
1019 ('Covered Tasks', 'outcome:%d'%Task.OUTCOME_COVERED, queryset_with_search.filter(outcome=Task.OUTCOME_COVERED).count(), 'Covered tasks didn\'t need to run because their output is provided by another task in this build'),
1020 ('Empty Tasks', 'outcome:%d'%Task.OUTCOME_EMPTY, queryset_with_search.filter(outcome=Task.OUTCOME_EMPTY).count(), 'Empty tasks have no executable content'),
1021 ]
1022 }
1023
1024 }
1025 tc_log={
1026 'name':'Log',
1027 'qhelp':'Path to the task log file',
1028 'orderfield': _get_toggle_order(request, "logfile"),
1029 'ordericon':_get_toggle_order_icon(request, "logfile"),
1030 'orderkey' : 'logfile',
1031 'clclass': 'task_log', 'hidden' : 1,
1032 }
1033 tc_cache={
1034 'name':'Cache attempt',
1035 'qhelp':'This column tells you if a task tried to restore output from the <code>sstate-cache</code> directory or mirrors, and reports the result: Succeeded, Failed or File not in cache',
1036 'clclass': 'cache_attempt', 'hidden' : 0,
1037 'orderfield': _get_toggle_order(request, "sstate_result"),
1038 'ordericon':_get_toggle_order_icon(request, "sstate_result"),
1039 'orderkey' : 'sstate_result',
1040 'filter' : {
1041 'class' : 'cache_attempt',
1042 'label': 'Show:',
1043 'options' : [
1044 ('Tasks with cache attempts', 'sstate_result__gt:%d'%Task.SSTATE_NA, queryset_with_search.filter(sstate_result__gt=Task.SSTATE_NA).count(), 'Show all tasks that tried to restore ouput from the <code>sstate-cache</code> directory or mirrors'),
1045 ("Tasks with 'File not in cache' attempts", 'sstate_result:%d'%Task.SSTATE_MISS, queryset_with_search.filter(sstate_result=Task.SSTATE_MISS).count(), 'Show tasks that tried to restore output, but did not find it in the <code>sstate-cache</code> directory or mirrors'),
1046 ("Tasks with 'Failed' cache attempts", 'sstate_result:%d'%Task.SSTATE_FAILED, queryset_with_search.filter(sstate_result=Task.SSTATE_FAILED).count(), 'Show tasks that found the required output in the <code>sstate-cache</code> directory or mirrors, but could not restore it'),
1047 ("Tasks with 'Succeeded' cache attempts", 'sstate_result:%d'%Task.SSTATE_RESTORED, queryset_with_search.filter(sstate_result=Task.SSTATE_RESTORED).count(), 'Show tasks that successfully restored the required output from the <code>sstate-cache</code> directory or mirrors'),
1048 ]
1049 }
1050
1051 }
1052 #if 'tasks' == variant: tc_cache['hidden']='0';
1053 tc_time={
1054 'name':'Time (secs)',
1055 'qhelp':'How long it took the task to finish in seconds',
1056 'orderfield': _get_toggle_order(request, "elapsed_time", True),
1057 'ordericon':_get_toggle_order_icon(request, "elapsed_time"),
1058 'orderkey' : 'elapsed_time',
1059 'clclass': 'time_taken', 'hidden' : 1,
1060 }
1061 if 'buildtime' == variant: tc_time['hidden']='0'; del tc_time['clclass']; tc_cache['hidden']='1';
1062 tc_cpu={
1063 'name':'CPU usage',
1064 'qhelp':'The percentage of task CPU utilization',
1065 'orderfield': _get_toggle_order(request, "cpu_usage", True),
1066 'ordericon':_get_toggle_order_icon(request, "cpu_usage"),
1067 'orderkey' : 'cpu_usage',
1068 'clclass': 'cpu_used', 'hidden' : 1,
1069 }
1070 if 'cpuusage' == variant: tc_cpu['hidden']='0'; del tc_cpu['clclass']; tc_cache['hidden']='1';
1071 tc_diskio={
1072 'name':'Disk I/O (ms)',
1073 'qhelp':'Number of miliseconds the task spent doing disk input and output',
1074 'orderfield': _get_toggle_order(request, "disk_io", True),
1075 'ordericon':_get_toggle_order_icon(request, "disk_io"),
1076 'orderkey' : 'disk_io',
1077 'clclass': 'disk_io', 'hidden' : 1,
1078 }
1079 if 'diskio' == variant: tc_diskio['hidden']='0'; del tc_diskio['clclass']; tc_cache['hidden']='1';
1080
1081
1082 context = { 'objectname': variant,
1083 'object_search_display': object_search_display,
1084 'filter_search_display': filter_search_display,
1085 'title': title_variant,
1086 'build': Build.objects.filter(pk=build_id)[0],
1087 'objects': tasks,
1088 'default_orderby' : orderby,
1089 'search_term': search_term,
1090 'total_count': queryset_with_search.count(),
1091 'tablecols':[
1092 tc_order,
1093 tc_recipe,
1094 tc_recipe_version,
1095 tc_task,
1096 tc_executed,
1097 tc_outcome,
1098 tc_cache,
1099 tc_time,
1100 tc_cpu,
1101 tc_diskio,
1102 tc_log,
1103 ]}
1104
1105 response = render(request, template, context)
1106 _save_parameters_cookies(response, pagesize, orderby, request)
1107 return response
1108
1109def tasks(request, build_id):
1110 return tasks_common(request, build_id, 'tasks', '')
1111
1112def tasks_task(request, build_id, task_id):
1113 return tasks_common(request, build_id, 'tasks', task_id)
1114
1115def buildtime(request, build_id):
1116 return tasks_common(request, build_id, 'buildtime', '')
1117
1118def diskio(request, build_id):
1119 return tasks_common(request, build_id, 'diskio', '')
1120
1121def cpuusage(request, build_id):
1122 return tasks_common(request, build_id, 'cpuusage', '')
1123
1124
1125def recipes(request, build_id):
1126 template = 'recipes.html'
1127 (pagesize, orderby) = _get_parameters_values(request, 100, 'name:+')
1128 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1129 retval = _verify_parameters( request.GET, mandatory_parameters )
1130 if retval:
1131 return _redirect_parameters( 'recipes', request.GET, mandatory_parameters, build_id = build_id)
1132 (filter_string, search_term, ordering_string) = _search_tuple(request, Recipe)
1133 queryset = Recipe.objects.filter(layer_version__id__in=Layer_Version.objects.filter(build=build_id))
1134 queryset = _get_queryset(Recipe, queryset, filter_string, search_term, ordering_string, 'name')
1135
1136 recipes = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
1137
1138 # prefetch the forward and reverse recipe dependencies
1139 deps = { }; revs = { }
1140 queryset_dependency=Recipe_Dependency.objects.filter(recipe__layer_version__build_id = build_id)
1141 for recipe in recipes:
1142 deplist = [ ]
1143 for recipe_dep in [x for x in queryset_dependency if x.recipe_id == recipe.id]:
1144 deplist.append(recipe_dep)
1145 deps[recipe.id] = deplist
1146 revlist = [ ]
1147 for recipe_dep in [x for x in queryset_dependency if x.depends_on_id == recipe.id]:
1148 revlist.append(recipe_dep)
1149 revs[recipe.id] = revlist
1150
1151 context = {
1152 'objectname': 'recipes',
1153 'build': Build.objects.filter(pk=build_id)[0],
1154 'objects': recipes,
1155 'default_orderby' : 'name:+',
1156 'recipe_deps' : deps,
1157 'recipe_revs' : revs,
1158 'tablecols':[
1159 {
1160 'name':'Recipe',
1161 'qhelp':'Information about a single piece of software, including where to download the source, configuration options, how to compile the source files and how to package the compiled output',
1162 'orderfield': _get_toggle_order(request, "name"),
1163 'ordericon':_get_toggle_order_icon(request, "name"),
1164 },
1165 {
1166 'name':'Recipe version',
1167 'qhelp':'The recipe version and revision',
1168 },
1169 {
1170 'name':'Dependencies',
1171 'qhelp':'Recipe build-time dependencies (i.e. other recipes)',
1172 'clclass': 'depends_on', 'hidden': 1,
1173 },
1174 {
1175 'name':'Reverse dependencies',
1176 'qhelp':'Recipe build-time reverse dependencies (i.e. the recipes that depend on this recipe)',
1177 'clclass': 'depends_by', 'hidden': 1,
1178 },
1179 {
1180 'name':'Recipe file',
1181 'qhelp':'Path to the recipe .bb file',
1182 'orderfield': _get_toggle_order(request, "file_path"),
1183 'ordericon':_get_toggle_order_icon(request, "file_path"),
1184 'orderkey' : 'file_path',
1185 'clclass': 'recipe_file', 'hidden': 0,
1186 },
1187 {
1188 'name':'Section',
1189 'qhelp':'The section in which recipes should be categorized',
1190 'orderfield': _get_toggle_order(request, "section"),
1191 'ordericon':_get_toggle_order_icon(request, "section"),
1192 'orderkey' : 'section',
1193 'clclass': 'recipe_section', 'hidden': 0,
1194 },
1195 {
1196 'name':'License',
1197 'qhelp':'The list of source licenses for the recipe. Multiple license names separated by the pipe character indicates a choice between licenses. Multiple license names separated by the ampersand character indicates multiple licenses exist that cover different parts of the source',
1198 'orderfield': _get_toggle_order(request, "license"),
1199 'ordericon':_get_toggle_order_icon(request, "license"),
1200 'orderkey' : 'license',
1201 'clclass': 'recipe_license', 'hidden': 0,
1202 },
1203 {
1204 'name':'Layer',
1205 'qhelp':'The name of the layer providing the recipe',
1206 'orderfield': _get_toggle_order(request, "layer_version__layer__name"),
1207 'ordericon':_get_toggle_order_icon(request, "layer_version__layer__name"),
1208 'orderkey' : 'layer_version__layer__name',
1209 'clclass': 'layer_version__layer__name', 'hidden': 0,
1210 },
1211 {
1212 'name':'Layer branch',
1213 'qhelp':'The Git branch of the layer providing the recipe',
1214 'orderfield': _get_toggle_order(request, "layer_version__branch"),
1215 'ordericon':_get_toggle_order_icon(request, "layer_version__branch"),
1216 'orderkey' : 'layer_version__branch',
1217 'clclass': 'layer_version__branch', 'hidden': 1,
1218 },
1219 {
1220 'name':'Layer commit',
1221 'qhelp':'The Git commit of the layer providing the recipe',
1222 'clclass': 'layer_version__layer__commit', 'hidden': 1,
1223 },
1224 {
1225 'name':'Layer directory',
1226 'qhelp':'Path to the layer prodiving the recipe',
1227 'orderfield': _get_toggle_order(request, "layer_version__layer__local_path"),
1228 'ordericon':_get_toggle_order_icon(request, "layer_version__layer__local_path"),
1229 'orderkey' : 'layer_version__layer__local_path',
1230 'clclass': 'layer_version__layer__local_path', 'hidden': 1,
1231 },
1232 ]
1233 }
1234
1235 response = render(request, template, context)
1236 _save_parameters_cookies(response, pagesize, orderby, request)
1237 return response
1238
1239def configuration(request, build_id):
1240 template = 'configuration.html'
1241
1242 variables = Variable.objects.filter(build=build_id)
1243 BB_VERSION=variables.filter(variable_name='BB_VERSION')[0].variable_value
1244 BUILD_SYS=variables.filter(variable_name='BUILD_SYS')[0].variable_value
1245 NATIVELSBSTRING=variables.filter(variable_name='NATIVELSBSTRING')[0].variable_value
1246 TARGET_SYS=variables.filter(variable_name='TARGET_SYS')[0].variable_value
1247 MACHINE=variables.filter(variable_name='MACHINE')[0].variable_value
1248 DISTRO=variables.filter(variable_name='DISTRO')[0].variable_value
1249 DISTRO_VERSION=variables.filter(variable_name='DISTRO_VERSION')[0].variable_value
1250 TUNE_FEATURES=variables.filter(variable_name='TUNE_FEATURES')[0].variable_value
1251 TARGET_FPU=variables.filter(variable_name='TARGET_FPU')[0].variable_value
1252
1253 targets = Target.objects.filter(build=build_id)
1254
1255 context = {
1256 'objectname': 'configuration',
1257 'object_search_display':'variables',
1258 'filter_search_display':'variables',
1259 'build': Build.objects.filter(pk=build_id)[0],
1260 'BB_VERSION':BB_VERSION,
1261 'BUILD_SYS':BUILD_SYS,
1262 'NATIVELSBSTRING':NATIVELSBSTRING,
1263 'TARGET_SYS':TARGET_SYS,
1264 'MACHINE':MACHINE,
1265 'DISTRO':DISTRO,
1266 'DISTRO_VERSION':DISTRO_VERSION,
1267 'TUNE_FEATURES':TUNE_FEATURES,
1268 'TARGET_FPU':TARGET_FPU,
1269 'targets':targets,
1270 }
1271 return render(request, template, context)
1272
1273
1274def configvars(request, build_id):
1275 template = 'configvars.html'
1276 (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
1277 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
1278 retval = _verify_parameters( request.GET, mandatory_parameters )
1279 (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
1280 if retval:
1281 # if new search, clear the default filter
1282 if search_term and len(search_term):
1283 mandatory_parameters['filter']=''
1284 return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
1285
1286 queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
1287 queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
1288 queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
1289 # remove records where the value is empty AND there are no history files
1290 queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
1291
1292 variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
1293
1294 # show all matching files (not just the last one)
1295 file_filter= search_term + ":"
1296 if filter_string.find('/conf/') > 0:
1297 file_filter += 'conf/(local|bblayers).conf'
1298 if filter_string.find('conf/machine/') > 0:
1299 file_filter += 'conf/machine/'
1300 if filter_string.find('conf/distro/') > 0:
1301 file_filter += 'conf/distro/'
1302 if filter_string.find('/bitbake.conf') > 0:
1303 file_filter += '/bitbake.conf'
1304 build_dir=re.sub("/tmp/log/.*","",Build.objects.filter(pk=build_id)[0].cooker_log_path)
1305
1306 context = {
1307 'objectname': 'configvars',
1308 'object_search_display':'BitBake variables',
1309 'filter_search_display':'variables',
1310 'file_filter': file_filter,
1311 'build': Build.objects.filter(pk=build_id)[0],
1312 'objects' : variables,
1313 'total_count':queryset_with_search.count(),
1314 'default_orderby' : 'variable_name:+',
1315 'search_term':search_term,
1316 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
1317 'tablecols' : [
1318 {'name': 'Variable',
1319 'qhelp': "BitBake is a generic task executor that considers a list of tasks with dependencies and handles metadata that consists of variables in a certain format that get passed to the tasks",
1320 'orderfield': _get_toggle_order(request, "variable_name"),
1321 'ordericon':_get_toggle_order_icon(request, "variable_name"),
1322 },
1323 {'name': 'Value',
1324 'qhelp': "The value assigned to the variable",
1325 'dclass': "span4",
1326 },
1327 {'name': 'Set in file',
1328 'qhelp': "The last configuration file that touched the variable value",
1329 'clclass': 'file', 'hidden' : 0,
1330 'orderkey' : 'vhistory__file_name',
1331 'filter' : {
1332 'class' : 'vhistory__file_name',
1333 'label': 'Show:',
1334 'options' : [
1335 ('Local configuration variables', 'vhistory__file_name__contains:'+build_dir+'/conf/',queryset_with_search.filter(vhistory__file_name__contains=build_dir+'/conf/').count(), 'Select this filter to see variables set by the <code>local.conf</code> and <code>bblayers.conf</code> configuration files inside the <code>/build/conf/</code> directory'),
1336 ('Machine configuration variables', 'vhistory__file_name__contains:conf/machine/',queryset_with_search.filter(vhistory__file_name__contains='conf/machine').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/machine/</code> directory'),
1337 ('Distro configuration variables', 'vhistory__file_name__contains:conf/distro/',queryset_with_search.filter(vhistory__file_name__contains='conf/distro').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/distro/</code> directory'),
1338 ('Layer configuration variables', 'vhistory__file_name__contains:conf/layer.conf',queryset_with_search.filter(vhistory__file_name__contains='conf/layer.conf').count(), 'Select this filter to see variables set by the <code>layer.conf</code> configuration file inside your layers'),
1339 ('bitbake.conf variables', 'vhistory__file_name__contains:/bitbake.conf',queryset_with_search.filter(vhistory__file_name__contains='/bitbake.conf').count(), 'Select this filter to see variables set by the <code>bitbake.conf</code> configuration file'),
1340 ]
1341 },
1342 },
1343 {'name': 'Description',
1344 'qhelp': "A brief explanation of the variable",
1345 'clclass': 'description', 'hidden' : 0,
1346 'dclass': "span4",
1347 'filter' : {
1348 'class' : 'description',
1349 'label': 'Show:',
1350 'options' : [
1351 ('Variables with description', 'description__regex:.+', queryset_with_search.filter(description__regex='.+').count(), 'We provide descriptions for the most common BitBake variables. The list of descriptions lives in <code>meta/conf/documentation.conf</code>'),
1352 ]
1353 },
1354 },
1355 ],
1356 }
1357
1358 response = render(request, template, context)
1359 _save_parameters_cookies(response, pagesize, orderby, request)
1360 return response
1361
1362def bpackage(request, build_id):
1363 template = 'bpackage.html'
1364 (pagesize, orderby) = _get_parameters_values(request, 100, 'name:+')
1365 mandatory_parameters = { 'count' : pagesize, 'page' : 1, 'orderby' : orderby }
1366 retval = _verify_parameters( request.GET, mandatory_parameters )
1367 if retval:
1368 return _redirect_parameters( 'packages', request.GET, mandatory_parameters, build_id = build_id)
1369 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
1370 queryset = Package.objects.filter(build = build_id).filter(size__gte=0)
1371 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
1372
1373 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
1374
1375 context = {
1376 'objectname': 'packages built',
1377 'build': Build.objects.filter(pk=build_id)[0],
1378 'objects' : packages,
1379 'default_orderby' : 'name:+',
1380 'tablecols':[
1381 {
1382 'name':'Package',
1383 'qhelp':'Packaged output resulting from building a recipe',
1384 'orderfield': _get_toggle_order(request, "name"),
1385 'ordericon':_get_toggle_order_icon(request, "name"),
1386 },
1387 {
1388 'name':'Package version',
1389 'qhelp':'The package version and revision',
1390 },
1391 {
1392 'name':'Size',
1393 'qhelp':'The size of the package',
1394 'orderfield': _get_toggle_order(request, "size", True),
1395 'ordericon':_get_toggle_order_icon(request, "size"),
1396 'orderkey' : 'size',
1397 'clclass': 'size', 'hidden': 0,
1398 'dclass' : 'span2',
1399 },
1400 {
1401 'name':'License',
1402 'qhelp':'The license under which the package is distributed. Multiple license names separated by the pipe character indicates a choice between licenses. Multiple license names separated by the ampersand character indicates multiple licenses exist that cover different parts of the source',
1403 'orderfield': _get_toggle_order(request, "license"),
1404 'ordericon':_get_toggle_order_icon(request, "license"),
1405 'orderkey' : 'license',
1406 'clclass': 'license', 'hidden': 1,
1407 },
1408 {
1409 'name':'Recipe',
1410 'qhelp':'The name of the recipe building the package',
1411 'orderfield': _get_toggle_order(request, "recipe__name"),
1412 'ordericon':_get_toggle_order_icon(request, "recipe__name"),
1413 'orderkey' : 'recipe__name',
1414 'clclass': 'recipe__name', 'hidden': 0,
1415 },
1416 {
1417 'name':'Recipe version',
1418 'qhelp':'Version and revision of the recipe building the package',
1419 'clclass': 'recipe__version', 'hidden': 1,
1420 },
1421 {
1422 'name':'Layer',
1423 'qhelp':'The name of the layer providing the recipe that builds the package',
1424 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__name"),
1425 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__name"),
1426 'orderkey' : 'recipe__layer_version__layer__name',
1427 'clclass': 'recipe__layer_version__layer__name', 'hidden': 1,
1428 },
1429 {
1430 'name':'Layer branch',
1431 'qhelp':'The Git branch of the layer providing the recipe that builds the package',
1432 'orderfield': _get_toggle_order(request, "recipe__layer_version__branch"),
1433 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__branch"),
1434 'orderkey' : 'recipe__layer_version__layer__branch',
1435 'clclass': 'recipe__layer_version__branch', 'hidden': 1,
1436 },
1437 {
1438 'name':'Layer commit',
1439 'qhelp':'The Git commit of the layer providing the recipe that builds the package',
1440 'clclass': 'recipe__layer_version__layer__commit', 'hidden': 1,
1441 },
1442 {
1443 'name':'Layer directory',
1444 'qhelp':'Path to the layer providing the recipe that builds the package',
1445 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__local_path"),
1446 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__local_path"),
1447 'orderkey' : 'recipe__layer_version__layer__local_path',
1448 'clclass': 'recipe__layer_version__layer__local_path', 'hidden': 1,
1449 },
1450 ]
1451 }
1452
1453 response = render(request, template, context)
1454 _save_parameters_cookies(response, pagesize, orderby, request)
1455 return response
1456
1457def bfile(request, build_id, package_id):
1458 template = 'bfile.html'
1459 files = Package_File.objects.filter(package = package_id)
1460 context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : files}
1461 return render(request, template, context)
1462
1463def tpackage(request, build_id, target_id):
1464 template = 'package.html'
1465 packages = map(lambda x: x.package, list(Target_Installed_Package.objects.filter(target=target_id)))
1466 context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : packages}
1467 return render(request, template, context)
1468
1469def layer(request):
1470 template = 'layer.html'
1471 layer_info = Layer.objects.all()
1472
1473 for li in layer_info:
1474 li.versions = Layer_Version.objects.filter(layer = li)
1475 for liv in li.versions:
1476 liv.count = Recipe.objects.filter(layer_version__id = liv.id).count()
1477
1478 context = {'objects': layer_info}
1479
1480 return render(request, template, context)
1481
1482
1483def layer_versions_recipes(request, layerversion_id):
1484 template = 'recipe.html'
1485 recipes = Recipe.objects.filter(layer_version__id = layerversion_id)
1486
1487 context = {'objects': recipes,
1488 'layer_version' : Layer_Version.objects.filter( id = layerversion_id )[0]
1489 }
1490
1491 return render(request, template, context)
1492
1493# A set of dependency types valid for both included and built package views
1494OTHER_DEPENDS_BASE = [
1495 Package_Dependency.TYPE_RSUGGESTS,
1496 Package_Dependency.TYPE_RPROVIDES,
1497 Package_Dependency.TYPE_RREPLACES,
1498 Package_Dependency.TYPE_RCONFLICTS,
1499 ]
1500
1501# value for invalid row id
1502INVALID_KEY = -1
1503
1504"""
1505Given a package id, target_id retrieves two sets of this image and package's
1506dependencies. The return value is a dictionary consisting of two other
1507lists: a list of 'runtime' dependencies, that is, having RDEPENDS
1508values in source package's recipe, and a list of other dependencies, that is
1509the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
1510the RRECOMENDS or TRECOMENDS value.
1511The lists are built in the sort order specified for the package runtime
1512dependency views.
1513"""
1514def _get_package_dependencies(package_id, target_id = INVALID_KEY):
1515 runtime_deps = []
1516 other_deps = []
1517 other_depends_types = OTHER_DEPENDS_BASE
1518
1519 if target_id != INVALID_KEY :
1520 rdepends_type = Package_Dependency.TYPE_TRDEPENDS
1521 other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS]
1522 else :
1523 rdepends_type = Package_Dependency.TYPE_RDEPENDS
1524 other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
1525
1526 package = Package.objects.get(pk=package_id)
1527 if target_id != INVALID_KEY :
1528 alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
1529 else :
1530 alldeps = package.package_dependencies_source.all()
1531 for idep in alldeps:
1532 dep_package = Package.objects.get(pk=idep.depends_on_id)
1533 dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
1534 if dep_package.version == '' :
1535 version = ''
1536 else :
1537 version = dep_package.version + "-" + dep_package.revision
1538 installed = False
1539 if target_id != INVALID_KEY :
1540 if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
1541 installed = True
1542 dep = {
1543 'name' : dep_package.name,
1544 'version' : version,
1545 'size' : dep_package.size,
1546 'dep_type' : idep.dep_type,
1547 'dep_type_display' : dep_entry[0].capitalize(),
1548 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
1549 'depends_on_id' : dep_package.id,
1550 'installed' : installed,
1551 }
1552
1553 if target_id != INVALID_KEY:
1554 dep['alias'] = _get_package_alias(dep_package)
1555
1556 if idep.dep_type == rdepends_type :
1557 runtime_deps.append(dep)
1558 elif idep.dep_type in other_depends_types :
1559 other_deps.append(dep)
1560
1561 rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1562 odep_sorted = sorted(
1563 sorted(other_deps, key=lambda k: k['name']),
1564 key=lambda k: k['dep_type'])
1565 retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1566 return retvalues
1567
1568# Return the count of packages dependent on package for this target_id image
1569def _get_package_reverse_dep_count(package, target_id):
1570 return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1571
1572# Return the count of the packages that this package_id is dependent on.
1573# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1574# installed, or else RDEPENDS if only built.
1575def _get_package_dependency_count(package, target_id, is_installed):
1576 if is_installed :
1577 return package.package_dependencies_source.filter(target_id__exact = target_id,
1578 dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1579 else :
1580 return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1581
1582def _get_package_alias(package):
1583 alias = package.installed_name
1584 if alias != None and alias != '' and alias != package.name:
1585 return alias
1586 else:
1587 return ''
1588
1589def _get_fullpackagespec(package):
1590 r = package.name
1591 version_good = package.version != None and package.version != ''
1592 revision_good = package.revision != None and package.revision != ''
1593 if version_good or revision_good:
1594 r += '_'
1595 if version_good:
1596 r += package.version
1597 if revision_good:
1598 r += '-'
1599 if revision_good:
1600 r += package.revision
1601 return r
1602
1603def package_built_detail(request, build_id, package_id):
1604 template = "package_built_detail.html"
1605 if Build.objects.filter(pk=build_id).count() == 0 :
1606 return redirect(builds)
1607
1608 # follow convention for pagination w/ search although not used for this view
1609 queryset = Package_File.objects.filter(package_id__exact=package_id)
1610 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1611 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1612 retval = _verify_parameters( request.GET, mandatory_parameters )
1613 if retval:
1614 return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1615
1616 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1617 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1618
1619 package = Package.objects.filter(pk=package_id)[0]
1620 package.fullpackagespec = _get_fullpackagespec(package)
1621 context = {
1622 'build' : Build.objects.filter(pk=build_id)[0],
1623 'package' : package,
1624 'dependency_count' : _get_package_dependency_count(package, -1, False),
1625 'objects' : paths,
1626 'tablecols':[
1627 {
1628 'name':'File',
1629 'orderfield': _get_toggle_order(request, "path"),
1630 'ordericon':_get_toggle_order_icon(request, "path"),
1631 },
1632 {
1633 'name':'Size',
1634 'orderfield': _get_toggle_order(request, "size", True),
1635 'ordericon':_get_toggle_order_icon(request, "size"),
1636 'dclass': 'sizecol span2',
1637 },
1638 ]
1639 }
1640 if paths.all().count() < 2:
1641 context['disable_sort'] = True;
1642
1643 response = render(request, template, context)
1644 _save_parameters_cookies(response, pagesize, orderby, request)
1645 return response
1646
1647def package_built_dependencies(request, build_id, package_id):
1648 template = "package_built_dependencies.html"
1649 if Build.objects.filter(pk=build_id).count() == 0 :
1650 return redirect(builds)
1651
1652 package = Package.objects.filter(pk=package_id)[0]
1653 package.fullpackagespec = _get_fullpackagespec(package)
1654 dependencies = _get_package_dependencies(package_id)
1655 context = {
1656 'build' : Build.objects.filter(pk=build_id)[0],
1657 'package' : package,
1658 'runtime_deps' : dependencies['runtime_deps'],
1659 'other_deps' : dependencies['other_deps'],
1660 'dependency_count' : _get_package_dependency_count(package, -1, False)
1661 }
1662 return render(request, template, context)
1663
1664
1665def package_included_detail(request, build_id, target_id, package_id):
1666 template = "package_included_detail.html"
1667 if Build.objects.filter(pk=build_id).count() == 0 :
1668 return redirect(builds)
1669
1670 # follow convention for pagination w/ search although not used for this view
1671 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1672 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1673 retval = _verify_parameters( request.GET, mandatory_parameters )
1674 if retval:
1675 return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1676 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1677
1678 queryset = Package_File.objects.filter(package_id__exact=package_id)
1679 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1680
1681 package = Package.objects.filter(pk=package_id)[0]
1682 package.fullpackagespec = _get_fullpackagespec(package)
1683 package.alias = _get_package_alias(package)
1684 target = Target.objects.filter(pk=target_id)[0]
1685 context = {
1686 'build' : Build.objects.filter(pk=build_id)[0],
1687 'target' : target,
1688 'package' : package,
1689 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1690 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1691 'objects': paths,
1692 'tablecols':[
1693 {
1694 'name':'File',
1695 'orderfield': _get_toggle_order(request, "path"),
1696 'ordericon':_get_toggle_order_icon(request, "path"),
1697 },
1698 {
1699 'name':'Size',
1700 'orderfield': _get_toggle_order(request, "size", True),
1701 'ordericon':_get_toggle_order_icon(request, "size"),
1702 'dclass': 'sizecol span2',
1703 },
1704 ]
1705 }
1706 if paths.all().count() < 2:
1707 context['disable_sort'] = True
1708 response = render(request, template, context)
1709 _save_parameters_cookies(response, pagesize, orderby, request)
1710 return response
1711
1712def package_included_dependencies(request, build_id, target_id, package_id):
1713 template = "package_included_dependencies.html"
1714 if Build.objects.filter(pk=build_id).count() == 0 :
1715 return redirect(builds)
1716
1717 package = Package.objects.filter(pk=package_id)[0]
1718 package.fullpackagespec = _get_fullpackagespec(package)
1719 package.alias = _get_package_alias(package)
1720 target = Target.objects.filter(pk=target_id)[0]
1721
1722 dependencies = _get_package_dependencies(package_id, target_id)
1723 context = {
1724 'build' : Build.objects.filter(pk=build_id)[0],
1725 'package' : package,
1726 'target' : target,
1727 'runtime_deps' : dependencies['runtime_deps'],
1728 'other_deps' : dependencies['other_deps'],
1729 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1730 'dependency_count' : _get_package_dependency_count(package, target_id, True)
1731 }
1732 return render(request, template, context)
1733
1734def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1735 template = "package_included_reverse_dependencies.html"
1736 if Build.objects.filter(pk=build_id).count() == 0 :
1737 return redirect(builds)
1738
1739 (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1740 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1741 retval = _verify_parameters( request.GET, mandatory_parameters )
1742 if retval:
1743 return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1744 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1745
1746 queryset = Package_Dependency.objects.select_related('depends_on__name', 'depends_on__size').filter(depends_on=package_id, target_id=target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS)
1747 objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1748
1749 package = Package.objects.filter(pk=package_id)[0]
1750 package.fullpackagespec = _get_fullpackagespec(package)
1751 package.alias = _get_package_alias(package)
1752 target = Target.objects.filter(pk=target_id)[0]
1753 for o in objects:
1754 if o.package.version != '':
1755 o.package.version += '-' + o.package.revision
1756 o.alias = _get_package_alias(o.package)
1757 context = {
1758 'build' : Build.objects.filter(pk=build_id)[0],
1759 'package' : package,
1760 'target' : target,
1761 'objects' : objects,
1762 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1763 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1764 'tablecols':[
1765 {
1766 'name':'Package',
1767 'orderfield': _get_toggle_order(request, "package__name"),
1768 'ordericon': _get_toggle_order_icon(request, "package__name"),
1769 },
1770 {
1771 'name':'Version',
1772 },
1773 {
1774 'name':'Size',
1775 'orderfield': _get_toggle_order(request, "package__size", True),
1776 'ordericon': _get_toggle_order_icon(request, "package__size"),
1777 'dclass': 'sizecol span2',
1778 },
1779 ]
1780 }
1781 if objects.all().count() < 2:
1782 context['disable_sort'] = True
1783 response = render(request, template, context)
1784 _save_parameters_cookies(response, pagesize, orderby, request)
1785 return response
1786
1787def image_information_dir(request, build_id, target_id, packagefile_id):
1788 # stubbed for now
1789 return redirect(builds)
1790
1791
1792import toastermain.settings
1793
1794
1795# we have a set of functions if we're in managed mode, or
1796# a default "page not available" simple functions for interactive mode
1797if toastermain.settings.MANAGED:
1798
1799 from django.contrib.auth.models import User
1800 from django.contrib.auth import authenticate, login
1801 from django.contrib.auth.decorators import login_required
1802
1803 from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
1804 from orm.models import Branch, LayerSource, ToasterSetting, Release, Machine
1805 from bldcontrol.models import BuildRequest
1806
1807 import traceback
1808
1809 class BadParameterException(Exception): pass # error thrown on invalid POST requests
1810
1811 # the context processor that supplies data used across all the pages
1812 def managedcontextprocessor(request):
1813 ret = {
1814 "projects": Project.objects.all(),
1815 "MANAGED" : toastermain.settings.MANAGED
1816 }
1817 if 'project_id' in request.session:
1818 ret['project'] = Project.objects.get(pk = request.session['project_id'])
1819 return ret
1820
1821 # new project
1822 def newproject(request):
1823 template = "newproject.html"
1824 context = {
1825 'email': request.user.email if request.user.is_authenticated() else '',
1826 'username': request.user.username if request.user.is_authenticated() else '',
1827 'releases': Release.objects.order_by("id"),
1828 'defaultbranch': ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value,
1829 }
1830
1831
1832 if request.method == "GET":
1833 # render new project page
1834 return render(request, template, context)
1835 elif request.method == "POST":
1836 mandatory_fields = ['projectname', 'email', 'username', 'projectversion']
1837 try:
1838 # make sure we have values for all mandatory_fields
1839 if reduce( lambda x, y: x or y, map(lambda x: len(request.POST.get(x, '')) == 0, mandatory_fields)):
1840 # set alert for missing fields
1841 raise BadParameterException("Fields missing: " +
1842 ", ".join([x for x in mandatory_fields if len(request.POST.get(x, '')) == 0 ]))
1843
1844 if not request.user.is_authenticated():
1845 user = authenticate(username = request.POST['username'], password = 'nopass')
1846 if user is None:
1847 user = User.objects.create_user(username = request.POST['username'], email = request.POST['email'], password = "nopass")
1848
1849 user = authenticate(username = user.username, password = 'nopass')
1850 login(request, user)
1851
1852 # save the project
1853 prj = Project.objects.create_project(name = request.POST['projectname'],
1854 release = Release.objects.get(pk = request.POST['projectversion']))
1855 prj.user_id = request.user.pk
1856 prj.save()
1857 return redirect(reverse(project, args = (prj.pk,)))
1858
1859 except (IntegrityError, BadParameterException) as e:
1860 # fill in page with previously submitted values
1861 map(lambda x: context.__setitem__(x, request.POST.get(x, "-- missing")), mandatory_fields)
1862 if isinstance(e, IntegrityError) and "username" in str(e):
1863 context['alert'] = "Your chosen username is already used"
1864 else:
1865 context['alert'] = str(e)
1866 return render(request, template, context)
1867
1868 raise Exception("Invalid HTTP method for this page")
1869
1870 # Shows the edit project page
1871 def project(request, pid):
1872 template = "project.html"
1873 try:
1874 prj = Project.objects.get(id = pid)
1875 except Project.DoesNotExist:
1876 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1877
1878 try:
1879 puser = User.objects.get(id = prj.user_id)
1880 except User.DoesNotExist:
1881 puser = None
1882
1883 # we use implicit knowledge of the current user's project to filter layer information, e.g.
1884 request.session['project_id'] = prj.id
1885
1886 context = {
1887 "project" : prj,
1888 #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED),
1889 "buildrequests" : map(lambda x: (x, {"machine" : x.brvariable_set.filter(name="MACHINE")[0]}), prj.buildrequest_set.filter(state__lt = BuildRequest.REQ_INPROGRESS).order_by("-pk")),
1890 "builds" : prj.build_set.all(),
1891 "puser": puser,
1892 }
1893 try:
1894 context["machine"] = prj.projectvariable_set.get(name="MACHINE").value
1895 except ProjectVariable.DoesNotExist:
1896 context["machine"] = "-- not set yet"
1897
1898 try:
1899 context["distro"] = prj.projectvariable_set.get(name="DISTRO").value
1900 except ProjectVariable.DoesNotExist:
1901 context["distro"] = "-- not set yet"
1902
1903
1904 return render(request, template, context)
1905
1906 import json
1907
1908 def xhr_projectbuild(request, pid):
1909 try:
1910 if request.method != "POST":
1911 raise BadParameterException("invalid method")
1912 prj = Project.objects.get(id = pid)
1913
1914 if prj.projecttarget_set.count() == 0:
1915 raise BadParameterException("no targets selected")
1916
1917 br = prj.schedule_build()
1918 return HttpResponse(json.dumps({"error":"ok",
1919 "brtarget" : map(lambda x: x.target, br.brtarget_set.all()),
1920 "machine" : br.brvariable_set.get(name="MACHINE").value,
1921
1922 }), content_type = "application/json")
1923 except Exception as e:
1924 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1925
1926 def xhr_projectedit(request, pid):
1927 try:
1928 prj = Project.objects.get(id = pid)
1929 # add targets
1930 if 'targetAdd' in request.POST:
1931 for t in request.POST['targetAdd'].strip().split(" "):
1932 if ":" in t:
1933 target, task = t.split(":")
1934 else:
1935 target = t
1936 task = ""
1937
1938 pt, created = ProjectTarget.objects.get_or_create(project = prj, target = target, task = task)
1939 # remove targets
1940 if 'targetDel' in request.POST:
1941 for t in request.POST['targetDel'].strip().split(" "):
1942 pt = ProjectTarget.objects.get(pk = int(t)).delete()
1943
1944 # add layers
1945
1946 # remove layers
1947
1948 # return all project settings
1949 return HttpResponse(json.dumps( {
1950 "error": "ok",
1951 "layers": map(lambda x: (x.layercommit.layer.name, x.layercommit.layer.layer_index_url), prj.projectlayer_set.all()),
1952 "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
1953 "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()),
1954 }), content_type = "application/json")
1955
1956 except Exception as e:
1957 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1958
1959 def importlayer(request):
1960 template = "importlayer.html"
1961 context = {
1962 }
1963 return render(request, template, context)
1964
1965 def layers(request):
1966 template = "layers.html"
1967 # define here what parameters the view needs in the GET portion in order to
1968 # be able to display something. 'count' and 'page' are mandatory for all views
1969 # that use paginators.
1970 mandatory_parameters = { 'count': 10, 'page' : 1, 'orderby' : 'layer__name:+' };
1971 retval = _verify_parameters( request.GET, mandatory_parameters )
1972 if retval:
1973 return _redirect_parameters( 'layers', request.GET, mandatory_parameters)
1974
1975 # boilerplate code that takes a request for an object type and returns a queryset
1976 # for that object type. copypasta for all needed table searches
1977 (filter_string, search_term, ordering_string) = _search_tuple(request, Layer_Version)
1978
1979 queryset_all = Layer_Version.objects.all()
1980 if 'project_id' in request.session:
1981 queryset_all = queryset_all.filter(up_branch__in = Branch.objects.filter(name = Project.objects.get(pk = request.session['project_id']).release.name))
1982
1983 queryset_with_search = _get_queryset(Layer_Version, queryset_all, None, search_term, ordering_string, '-layer__name')
1984 queryset = _get_queryset(Layer_Version, queryset_all, filter_string, search_term, ordering_string, '-layer__name')
1985
1986 # retrieve the objects that will be displayed in the table; layers a paginator and gets a page range to display
1987 layer_info = _build_page_range(Paginator(queryset, request.GET.get('count', 10)),request.GET.get('page', 1))
1988
1989
1990 context = {
1991 'objects' : layer_info,
1992 'objectname' : "layers",
1993 'default_orderby' : 'layer__name:+',
1994 'total_count': queryset_with_search.count(),
1995
1996 'tablecols' : [
1997 { 'name': 'Layer',
1998 'orderfield': _get_toggle_order(request, "layer__name"),
1999 'ordericon' : _get_toggle_order_icon(request, "layer__name"),
2000 },
2001 { 'name': 'Description',
2002 'dclass': 'span4',
2003 'clclass': 'description',
2004 },
2005 { 'name': 'Layer source',
2006 'clclass': 'source',
2007 'qhelp': "Where the layer is coming from, for example, if it's part of the OpenEmbedded collection of layers or if it's a layer you have imported",
2008 'orderfield': _get_toggle_order(request, "layer_source__name"),
2009 'ordericon': _get_toggle_order_icon(request, "layer_source__name"),
2010 'filter': {
2011 'class': 'layer',
2012 'label': 'Show:',
2013 'options': map(lambda x: (x.name, 'layer_source__pk:' + str(x.id), queryset_with_search.filter(layer_source__pk = x.id).count() ), LayerSource.objects.all()),
2014 }
2015 },
2016 { 'name': 'Git repository URL',
2017 'dclass': 'span6',
2018 'clclass': 'git-repo', 'hidden': 1,
2019 'qhelp': "The Git repository for the layer source code",
2020 },
2021 { 'name': 'Subdirectory',
2022 'clclass': 'git-subdir',
2023 'hidden': 1,
2024 'qhelp': "The layer directory within the Git repository",
2025 },
2026 { 'name': 'Branch, tag o commit',
2027 'clclass': 'branch',
2028 'qhelp': "The Git branch of the layer. For the layers from the OpenEmbedded source, the branch matches the Yocto Project version you selected for this project",
2029 },
2030 { 'name': 'Dependencies',
2031 'clclass': 'dependencies',
2032 'qhelp': "Other layers a layer depends upon",
2033 },
2034 { 'name': 'Add | Delete',
2035 'dclass': 'span2',
2036 'qhelp': "Add or delete layers to / from your project ",
2037 },
2038
2039 ]
2040 }
2041
2042 return render(request, template, context)
2043
2044 def layerdetails(request, layerid):
2045 template = "layerdetails.html"
2046 context = {
2047 'layerversion': Layer_Version.objects.get(pk = layerid),
2048 }
2049 return render(request, template, context)
2050
2051 def targets(request):
2052 template = "targets.html"
2053 # define here what parameters the view needs in the GET portion in order to
2054 # be able to display something. 'count' and 'page' are mandatory for all views
2055 # that use paginators.
2056 mandatory_parameters = { 'count': 10, 'page' : 1, 'orderby' : 'name:+' };
2057 retval = _verify_parameters( request.GET, mandatory_parameters )
2058 if retval:
2059 return _redirect_parameters( 'targets', request.GET, mandatory_parameters)
2060
2061 # boilerplate code that takes a request for an object type and returns a queryset
2062 # for that object type. copypasta for all needed table searches
2063 (filter_string, search_term, ordering_string) = _search_tuple(request, Recipe)
2064
2065 queryset_all = Recipe.objects.all()
2066 if 'project_id' in request.session:
2067 queryset_all = queryset_all.filter(Q(layer_version__up_branch__in = Branch.objects.filter(name = Project.objects.get(pk=request.session['project_id']).release.name)) | Q(layer_version__build__in = Project.objects.get(pk = request.session['project_id']).build_set.all()))
2068
2069 queryset_with_search = _get_queryset(Recipe, queryset_all, None, search_term, ordering_string, '-name')
2070 queryset = _get_queryset(Recipe, queryset_all, filter_string, search_term, ordering_string, '-name')
2071
2072 # retrieve the objects that will be displayed in the table; targets a paginator and gets a page range to display
2073 target_info = _build_page_range(Paginator(queryset, request.GET.get('count', 10)),request.GET.get('page', 1))
2074
2075
2076 context = {
2077 'objects' : target_info,
2078 'objectname' : "targets",
2079 'default_orderby' : 'name:+',
2080 'total_count': queryset_with_search.count(),
2081
2082 'tablecols' : [
2083 { 'name': 'Target',
2084 'orderfield': _get_toggle_order(request, "name"),
2085 'ordericon' : _get_toggle_order_icon(request, "name"),
2086 },
2087 { 'name': 'Target version',
2088 'dclass': 'span2',
2089 },
2090 { 'name': 'Description',
2091 'dclass': 'span5',
2092 'clclass': 'description',
2093 },
2094 { 'name': 'Recipe file',
2095 'clclass': 'recipe-file',
2096 'hidden': 1,
2097 'dclass': 'span5',
2098 },
2099 { 'name': 'Section',
2100 'clclass': 'target-section',
2101 'hidden': 1,
2102 },
2103 { 'name': 'License',
2104 'clclass': 'license',
2105 'hidden': 1,
2106 },
2107 { 'name': 'Layer',
2108 'clclass': 'layer',
2109 },
2110 { 'name': 'Layer source',
2111 'clclass': 'source',
2112 'qhelp': "Where the target is coming from, for example, if it's part of the OpenEmbedded collection of targets or if it's a target you have imported",
2113 'orderfield': _get_toggle_order(request, "layer_source__name"),
2114 'ordericon': _get_toggle_order_icon(request, "layer_source__name"),
2115 'filter': {
2116 'class': 'target',
2117 'label': 'Show:',
2118 'options': map(lambda x: (x.name, 'layer_source__pk:' + str(x.id), queryset_with_search.filter(layer_source__pk = x.id).count() ), LayerSource.objects.all()),
2119 }
2120 },
2121 { 'name': 'Branch, tag or commit',
2122 'clclass': 'branch',
2123 'hidden': 1,
2124 },
2125 { 'name': 'Build',
2126 'dclass': 'span2',
2127 'qhelp': "Add or delete targets to / from your project ",
2128 },
2129
2130 ]
2131 }
2132
2133 return render(request, template, context)
2134
2135 def machines(request):
2136 template = "machines.html"
2137 # define here what parameters the view needs in the GET portion in order to
2138 # be able to display something. 'count' and 'page' are mandatory for all views
2139 # that use paginators.
2140 mandatory_parameters = { 'count': 10, 'page' : 1, 'orderby' : 'name:+' };
2141 retval = _verify_parameters( request.GET, mandatory_parameters )
2142 if retval:
2143 return _redirect_parameters( 'machines', request.GET, mandatory_parameters)
2144
2145 # boilerplate code that takes a request for an object type and returns a queryset
2146 # for that object type. copypasta for all needed table searches
2147 (filter_string, search_term, ordering_string) = _search_tuple(request, Machine)
2148
2149 queryset_all = Machine.objects.all()
2150# if 'project_id' in request.session:
2151# queryset_all = queryset_all.filter(Q(layer_version__up_branch__in = Branch.objects.filter(name = Project.objects.get(request.session['project_id']).release.name)) | Q(layer_version__build__in = Project.objects.get(request.session['project_id']).build_set.all()))
2152
2153 queryset_with_search = _get_queryset(Machine, queryset_all, None, search_term, ordering_string, '-name')
2154 queryset = _get_queryset(Machine, queryset_all, filter_string, search_term, ordering_string, '-name')
2155
2156 # retrieve the objects that will be displayed in the table; machines a paginator and gets a page range to display
2157 machine_info = _build_page_range(Paginator(queryset, request.GET.get('count', 10)),request.GET.get('page', 1))
2158
2159
2160 context = {
2161 'objects' : machine_info,
2162 'objectname' : "machines",
2163 'default_orderby' : 'name:+',
2164 'total_count': queryset_with_search.count(),
2165
2166 'tablecols' : [
2167 { 'name': 'Machine',
2168 'orderfield': _get_toggle_order(request, "name"),
2169 'ordericon' : _get_toggle_order_icon(request, "name"),
2170 },
2171 { 'name': 'Description',
2172 'dclass': 'span5',
2173 'clclass': 'description',
2174 },
2175 { 'name': 'Machine file',
2176 'clclass': 'machine-file',
2177 'hidden': 1,
2178 },
2179 { 'name': 'Layer',
2180 'clclass': 'layer',
2181 },
2182 { 'name': 'Layer source',
2183 'clclass': 'source',
2184 'qhelp': "Where the machine is coming from, for example, if it's part of the OpenEmbedded collection of machines or if it's a machine you have imported",
2185 'orderfield': _get_toggle_order(request, "layer_source__name"),
2186 'ordericon': _get_toggle_order_icon(request, "layer_source__name"),
2187 'filter': {
2188 'class': 'machine',
2189 'label': 'Show:',
2190 'options': map(lambda x: (x.name, 'layer_source__pk:' + str(x.id), queryset_with_search.filter(layer_source__pk = x.id).count() ), LayerSource.objects.all()),
2191 }
2192 },
2193 { 'name': 'Branch, tag or commit',
2194 'clclass': 'branch',
2195 'hidden': 1,
2196 },
2197 { 'name': 'Select',
2198 'dclass': 'span2',
2199 'qhelp': "Add or delete machines to / from your project ",
2200 },
2201
2202 ]
2203 }
2204
2205 return render(request, template, context)
2206
2207 def projectconf(request, pid):
2208 template = "projectconf.html"
2209 context = {
2210 'configvars': ProjectVariable.objects.filter(project_id = pid),
2211 }
2212 return render(request, template, context)
2213
2214 def projectbuilds(request, pid):
2215 template = 'projectbuilds.html'
2216 # define here what parameters the view needs in the GET portion in order to
2217 # be able to display something. 'count' and 'page' are mandatory for all views
2218 # that use paginators.
2219 mandatory_parameters = { 'count': 10, 'page' : 1, 'orderby' : 'completed_on:-' };
2220 retval = _verify_parameters( request.GET, mandatory_parameters )
2221
2222 # boilerplate code that takes a request for an object type and returns a queryset
2223 # for that object type. copypasta for all needed table searches
2224 (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
2225 queryset_all = Build.objects.all.exclude(outcome = Build.IN_PROGRESS)
2226 queryset_with_search = _get_queryset(Build, queryset_all, None, search_term, ordering_string, '-completed_on')
2227 queryset = _get_queryset(Build, queryset_all, filter_string, search_term, ordering_string, '-completed_on')
2228
2229 # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display
2230 build_info = _build_page_range(Paginator(queryset, request.GET.get('count', 10)),request.GET.get('page', 1))
2231
2232
2233 # set up list of fstypes for each build
2234 fstypes_map = {};
2235 for build in build_info:
2236 targets = Target.objects.filter( build_id = build.id )
2237 comma = "";
2238 extensions = "";
2239 for t in targets:
2240 if ( not t.is_image ):
2241 continue
2242 tif = Target_Image_File.objects.filter( target_id = t.id )
2243 for i in tif:
2244 s=re.sub('.*tar.bz2', 'tar.bz2', i.file_name)
2245 if s == i.file_name:
2246 s=re.sub('.*\.', '', i.file_name)
2247 if None == re.search(s,extensions):
2248 extensions += comma + s
2249 comma = ", "
2250 fstypes_map[build.id]=extensions
2251
2252 # send the data to the template
2253 context = {
2254 'objects' : build_info,
2255 'objectname' : "builds",
2256 'default_orderby' : 'completed_on:-',
2257 'fstypes' : fstypes_map,
2258 'search_term' : search_term,
2259 'total_count' : queryset_with_search.count(),
2260 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
2261 'tablecols' : [
2262 {'name': 'Outcome', # column with a single filter
2263 'qhelp' : "The outcome tells you if a build successfully completed or failed", # the help button content
2264 'dclass' : "span2", # indication about column width; comes from the design
2265 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
2266 'ordericon':_get_toggle_order_icon(request, "outcome"),
2267 # filter field will set a filter on that column with the specs in the filter description
2268 # the class field in the filter has no relation with clclass; the control different aspects of the UI
2269 # still, it is recommended for the values to be identical for easy tracking in the generated HTML
2270 'filter' : {'class' : 'outcome',
2271 'label': 'Show:',
2272 'options' : [
2273 ('Successful builds', 'outcome:' + str(Build.SUCCEEDED), queryset_with_search.filter(outcome=str(Build.SUCCEEDED)).count()), # this is the field search expression
2274 ('Failed builds', 'outcome:'+ str(Build.FAILED), queryset_with_search.filter(outcome=str(Build.FAILED)).count()),
2275 ]
2276 }
2277 },
2278 {'name': 'Target', # default column, disabled box, with just the name in the list
2279 'qhelp': "This is the build target or build targets (i.e. one or more recipes or image recipes)",
2280 'orderfield': _get_toggle_order(request, "target__target"),
2281 'ordericon':_get_toggle_order_icon(request, "target__target"),
2282 },
2283 {'name': 'Machine',
2284 'qhelp': "The machine is the hardware for which you are building a recipe or image recipe",
2285 'orderfield': _get_toggle_order(request, "machine"),
2286 'ordericon':_get_toggle_order_icon(request, "machine"),
2287 'dclass': 'span3'
2288 }, # a slightly wider column
2289 {'name': 'Started on', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
2290 'qhelp': "The date and time you started the build",
2291 'orderfield': _get_toggle_order(request, "started_on", True),
2292 'ordericon':_get_toggle_order_icon(request, "started_on"),
2293 'filter' : {'class' : 'started_on',
2294 'label': 'Show:',
2295 'options' : [
2296 ("Today's builds" , 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=timezone.now()).count()),
2297 ("Yesterday's builds", 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(hours=24))).count()),
2298 ("This week's builds", 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(started_on__gte=(timezone.now()-timedelta(days=7))).count()),
2299 ]
2300 }
2301 },
2302 {'name': 'Completed on',
2303 'qhelp': "The date and time the build finished",
2304 'orderfield': _get_toggle_order(request, "completed_on", True),
2305 'ordericon':_get_toggle_order_icon(request, "completed_on"),
2306 'orderkey' : 'completed_on',
2307 'filter' : {'class' : 'completed_on',
2308 'label': 'Show:',
2309 'options' : [
2310 ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now()).count()),
2311 ("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))).count()),
2312 ("This week's builds", 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(days=7))).count()),
2313 ]
2314 }
2315 },
2316 {'name': 'Failed tasks', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
2317 'qhelp': "How many tasks failed during the build",
2318 'filter' : {'class' : 'failed_tasks',
2319 'label': 'Show:',
2320 'options' : [
2321 ('Builds with failed tasks', 'task_build__outcome:4', queryset_with_search.filter(task_build__outcome=4).count()),
2322 ('Builds without failed tasks', 'task_build__outcome:NOT4', queryset_with_search.filter(~Q(task_build__outcome=4)).count()),
2323 ]
2324 }
2325 },
2326 {'name': 'Errors', 'clclass': 'errors_no',
2327 'qhelp': "How many errors were encountered during the build (if any)",
2328 'orderfield': _get_toggle_order(request, "errors_no", True),
2329 'ordericon':_get_toggle_order_icon(request, "errors_no"),
2330 'orderkey' : 'errors_no',
2331 'filter' : {'class' : 'errors_no',
2332 'label': 'Show:',
2333 'options' : [
2334 ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()),
2335 ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()),
2336 ]
2337 }
2338 },
2339 {'name': 'Warnings', 'clclass': 'warnings_no',
2340 'qhelp': "How many warnings were encountered during the build (if any)",
2341 'orderfield': _get_toggle_order(request, "warnings_no", True),
2342 'ordericon':_get_toggle_order_icon(request, "warnings_no"),
2343 'orderkey' : 'warnings_no',
2344 'filter' : {'class' : 'warnings_no',
2345 'label': 'Show:',
2346 'options' : [
2347 ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()),
2348 ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()),
2349 ]
2350 }
2351 },
2352 {'name': 'Time', 'clclass': 'time', 'hidden' : 1,
2353 'qhelp': "How long it took the build to finish",
2354 'orderfield': _get_toggle_order(request, "timespent", True),
2355 'ordericon':_get_toggle_order_icon(request, "timespent"),
2356 'orderkey' : 'timespent',
2357 },
2358 {'name': 'Log',
2359 'dclass': "span4",
2360 'qhelp': "Path to the build main log file",
2361 'clclass': 'log', 'hidden': 1,
2362 'orderfield': _get_toggle_order(request, "cooker_log_path"),
2363 'ordericon':_get_toggle_order_icon(request, "cooker_log_path"),
2364 'orderkey' : 'cooker_log_path',
2365 },
2366 {'name': 'Output', 'clclass': 'output',
2367 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory",
2368 },
2369 ]
2370 }
2371
2372 return render(request, template, context)
2373else:
2374 # these are pages that are NOT available in interactive mode
2375 def managedcontextprocessor(request):
2376 return {
2377 "projects": [],
2378 "MANAGED" : toastermain.settings.MANAGED
2379 }
2380
2381 def newproject(request):
2382 raise Exception("page not available in interactive mode")
2383
2384 def project(request, pid):
2385 raise Exception("page not available in interactive mode")
2386
2387 def xhr_projectbuild(request, pid):
2388 raise Exception("page not available in interactive mode")
2389
2390 def xhr_projectedit(request, pid):
2391 raise Exception("page not available in interactive mode")
2392
2393 def importlayer(request):
2394 raise Exception("page not available in interactive mode")
2395
2396 def layers(request):
2397 raise Exception("page not available in interactive mode")
2398
2399 def layerdetails(request):
2400 raise Exception("page not available in interactive mode")
2401
2402 def targets(request):
2403 raise Exception("page not available in interactive mode")
2404
2405 def targetdetails(request):
2406 raise Exception("page not available in interactive mode")
2407
2408 def machines(request):
2409 raise Exception("page not available in interactive mode")
2410
2411 def projectconf(request):
2412 raise Exception("page not available in interactive mode")
2413
2414 def projectbuilds(request):
2415 raise Exception("page not available in interactive mode")