summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/toastergui
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/toastergui')
-rw-r--r--bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js15
-rw-r--r--bitbake/lib/toaster/toastergui/templates/base.html1
-rw-r--r--bitbake/lib/toaster/toastergui/templates/basebuildpage.html62
-rw-r--r--bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html68
-rw-r--r--bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py35
-rw-r--r--bitbake/lib/toaster/toastergui/templatetags/queryset_to_list_filter.py26
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py26
7 files changed, 160 insertions, 73 deletions
diff --git a/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js b/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
index 1ae0d34e90..cb9ed4da05 100644
--- a/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
+++ b/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
@@ -12,6 +12,7 @@ for the new custom image. This will manage the addition of radio buttons
12to select the base image (or remove the radio buttons, if there is only a 12to select the base image (or remove the radio buttons, if there is only a
13single base image available). 13single base image available).
14*/ 14*/
15
15function newCustomImageModalInit(){ 16function newCustomImageModalInit(){
16 17
17 var newCustomImgBtn = $("#create-new-custom-image-btn"); 18 var newCustomImgBtn = $("#create-new-custom-image-btn");
@@ -21,7 +22,8 @@ function newCustomImageModalInit(){
21 var nameInput = imgCustomModal.find('input'); 22 var nameInput = imgCustomModal.find('input');
22 23
23 var invalidNameMsg = "Image names cannot contain spaces or capital letters. The only allowed special character is dash (-)."; 24 var invalidNameMsg = "Image names cannot contain spaces or capital letters. The only allowed special character is dash (-).";
24 var duplicateNameMsg = "An image with this name already exists. Image names must be unique."; 25 var duplicateNameMsg = "A recipe with this name already exists. Image names must be unique.";
26 var duplicateImageInProjectMsg = "An image with this name already exists in this project."
25 var invalidBaseRecipeIdMsg = "Please select an image to customise."; 27 var invalidBaseRecipeIdMsg = "Please select an image to customise.";
26 28
27 // capture clicks on radio buttons inside the modal; when one is selected, 29 // capture clicks on radio buttons inside the modal; when one is selected,
@@ -51,9 +53,12 @@ function newCustomImageModalInit(){
51 if (ret.error === "invalid-name") { 53 if (ret.error === "invalid-name") {
52 showNameError(invalidNameMsg); 54 showNameError(invalidNameMsg);
53 return; 55 return;
54 } else if (ret.error === "already-exists") { 56 } else if (ret.error === "recipe-already-exists") {
55 showNameError(duplicateNameMsg); 57 showNameError(duplicateNameMsg);
56 return; 58 return;
59 } else if (ret.error === "image-already-exists") {
60 showNameError(duplicateImageInProjectMsg);
61 return;
57 } 62 }
58 } else { 63 } else {
59 imgCustomModal.modal('hide'); 64 imgCustomModal.modal('hide');
@@ -112,13 +117,13 @@ function newCustomImageModalSetRecipes(baseRecipes) {
112 var imageSelector = $('#new-custom-image-modal [data-role="image-selector"]'); 117 var imageSelector = $('#new-custom-image-modal [data-role="image-selector"]');
113 var imageSelectRadiosContainer = $('#new-custom-image-modal [data-role="image-selector-radios"]'); 118 var imageSelectRadiosContainer = $('#new-custom-image-modal [data-role="image-selector-radios"]');
114 119
120 // remove any existing radio buttons + labels
121 imageSelector.remove('[data-role="image-radio"]');
122
115 if (baseRecipes.length === 1) { 123 if (baseRecipes.length === 1) {
116 // hide the radio button container 124 // hide the radio button container
117 imageSelector.hide(); 125 imageSelector.hide();
118 126
119 // remove any radio buttons + labels
120 imageSelector.remove('[data-role="image-radio"]');
121
122 // set the single recipe ID on the modal as it's the only one 127 // set the single recipe ID on the modal as it's the only one
123 // we can build from 128 // we can build from
124 imgCustomModal.data('recipe', baseRecipes[0].id); 129 imgCustomModal.data('recipe', baseRecipes[0].id);
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html
index 192f9fb556..210cf3360c 100644
--- a/bitbake/lib/toaster/toastergui/templates/base.html
+++ b/bitbake/lib/toaster/toastergui/templates/base.html
@@ -43,7 +43,6 @@
43 recipesTypeAheadUrl: {% url 'xhr_recipestypeahead' project.id as paturl%}{{paturl|json}}, 43 recipesTypeAheadUrl: {% url 'xhr_recipestypeahead' project.id as paturl%}{{paturl|json}},
44 layersTypeAheadUrl: {% url 'xhr_layerstypeahead' project.id as paturl%}{{paturl|json}}, 44 layersTypeAheadUrl: {% url 'xhr_layerstypeahead' project.id as paturl%}{{paturl|json}},
45 machinesTypeAheadUrl: {% url 'xhr_machinestypeahead' project.id as paturl%}{{paturl|json}}, 45 machinesTypeAheadUrl: {% url 'xhr_machinestypeahead' project.id as paturl%}{{paturl|json}},
46
47 projectBuildsUrl: {% url 'projectbuilds' project.id as pburl %}{{pburl|json}}, 46 projectBuildsUrl: {% url 'projectbuilds' project.id as pburl %}{{pburl|json}},
48 xhrCustomRecipeUrl : "{% url 'xhr_customrecipe' %}", 47 xhrCustomRecipeUrl : "{% url 'xhr_customrecipe' %}",
49 projectId : {{project.id}}, 48 projectId : {{project.id}},
diff --git a/bitbake/lib/toaster/toastergui/templates/basebuildpage.html b/bitbake/lib/toaster/toastergui/templates/basebuildpage.html
index 4a8e2a7abd..0d8c8820da 100644
--- a/bitbake/lib/toaster/toastergui/templates/basebuildpage.html
+++ b/bitbake/lib/toaster/toastergui/templates/basebuildpage.html
@@ -1,7 +1,7 @@
1{% extends "base.html" %} 1{% extends "base.html" %}
2{% load projecttags %} 2{% load projecttags %}
3{% load project_url_tag %} 3{% load project_url_tag %}
4{% load queryset_to_list_filter %} 4{% load objects_to_dictionaries_filter %}
5{% load humanize %} 5{% load humanize %}
6{% block pagecontent %} 6{% block pagecontent %}
7 <!-- breadcrumbs --> 7 <!-- breadcrumbs -->
@@ -81,33 +81,40 @@
81 </p> 81 </p>
82 </li> 82 </li>
83 83
84 <li> 84 {% with build.get_custom_image_recipes as custom_image_recipes %}
85 <!-- edit custom image built during this build --> 85 {% if custom_image_recipes.count > 0 %}
86 <p class="navbar-btn" data-role="edit-custom-image-trigger"> 86 <!-- edit custom image built during this build -->
87 <button class="btn btn-block">Edit custom image</button> 87 <li>
88 </p> 88 <p class="navbar-btn" data-role="edit-custom-image-trigger">
89 {% include 'editcustomimage_modal.html' %} 89 <button class="btn btn-block">Edit custom image</button>
90 <script> 90 {% include 'editcustomimage_modal.html' %}
91 $(document).ready(function () { 91 <script>
92 var editableCustomImageRecipes = {{ build.get_custom_image_recipes | queryset_to_list:"id,name" | json }}; 92 var editableCustomImageRecipes = {{ custom_image_recipes | objects_to_dictionaries:"id,name" | json }};
93
94 // edit custom image which was built during this build
95 var editCustomImageModal = $('#edit-custom-image-modal');
96 var editCustomImageTrigger = $('[data-role="edit-custom-image-trigger"]');
97 93
98 editCustomImageTrigger.click(function () { 94 $(document).ready(function () {
99 // if there is a single editable custom image, go direct to the edit 95 var editCustomImageTrigger = $('[data-role="edit-custom-image-trigger"]');
100 // page for it; if there are multiple editable custom images, show 96 var editCustomImageModal = $('#edit-custom-image-modal');
101 // dialog to select one of them for editing
102 97
103 // single editable custom image 98 // edit custom image which was built during this build
104 99 editCustomImageTrigger.click(function () {
105 // multiple editable custom images 100 // single editable custom image: redirect to the edit page
106 editCustomImageModal.modal('show'); 101 // for that image
107 }); 102 if (editableCustomImageRecipes.length === 1) {
108 }); 103 var url = '{% url "customrecipe" build.project.id custom_image_recipes.first.id %}';
109 </script> 104 document.location.href = url;
110 </li> 105 }
106 // multiple editable custom images: show modal to select
107 // one of them for editing
108 else {
109 editCustomImageModal.modal('show');
110 }
111 });
112 });
113 </script>
114 </p>
115 </li>
116 {% endif %}
117 {% endwith %}
111 118
112 <li> 119 <li>
113 <!-- new custom image from image recipe in this build --> 120 <!-- new custom image from image recipe in this build -->
@@ -119,7 +126,7 @@
119 // imageRecipes includes both custom image recipes and built-in 126 // imageRecipes includes both custom image recipes and built-in
120 // image recipes, any of which can be used as the basis for a 127 // image recipes, any of which can be used as the basis for a
121 // new custom image 128 // new custom image
122 var imageRecipes = {{ build.get_image_recipes | queryset_to_list:"id,name" | json }}; 129 var imageRecipes = {{ build.get_image_recipes | objects_to_dictionaries:"id,name" | json }};
123 130
124 $(document).ready(function () { 131 $(document).ready(function () {
125 var newCustomImageModal = $('#new-custom-image-modal'); 132 var newCustomImageModal = $('#new-custom-image-modal');
@@ -131,6 +138,7 @@
131 if (!imageRecipes.length) { 138 if (!imageRecipes.length) {
132 return; 139 return;
133 } 140 }
141
134 newCustomImageModalSetRecipes(imageRecipes); 142 newCustomImageModalSetRecipes(imageRecipes);
135 newCustomImageModal.modal('show'); 143 newCustomImageModal.modal('show');
136 }); 144 });
diff --git a/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html b/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html
index fd998f63eb..8046c08fb5 100644
--- a/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html
+++ b/bitbake/lib/toaster/toastergui/templates/editcustomimage_modal.html
@@ -1,23 +1,71 @@
1<!-- 1<!--
2modal dialog shown on the build dashboard, for editing an existing custom image 2modal dialog shown on the build dashboard, for editing an existing custom image;
3only shown if more than one custom image was built, so the user needs to
4choose which one to edit
5
6required context:
7 build - a Build object
3--> 8-->
4<div class="modal hide fade in" aria-hidden="false" id="edit-custom-image-modal"> 9<div class="modal hide fade in" aria-hidden="false" id="edit-custom-image-modal">
5 <div class="modal-header"> 10 <div class="modal-header">
6 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> 11 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
7 <h3>Select custom image to edit</h3> 12 <h3>Which image do you want to edit?</h3>
8 </div> 13 </div>
14
9 <div class="modal-body"> 15 <div class="modal-body">
10 <div class="row-fluid"> 16 <div class="row-fluid">
11 <span class="help-block"> 17 {% for recipe in build.get_custom_image_recipes %}
12 Explanation of what this modal is for 18 <label class="radio">
13 </span> 19 {{recipe.name}}
14 </div> 20 <input type="radio" class="form-control" name="select-custom-image"
15 <div class="control-group controls"> 21 data-url="{% url 'customrecipe' build.project.id recipe.id %}">
16 <input type="text" class="huge" placeholder="input box" required> 22 </label>
17 <span class="help-block error" style="display:none">Error text</span> 23 {% endfor %}
18 </div> 24 </div>
25 <span class="help-block error" id="invalid-custom-image-help" style="display:none">
26 Please select a custom image to edit.
27 </span>
19 </div> 28 </div>
29
20 <div class="modal-footer"> 30 <div class="modal-footer">
21 <button class="btn btn-primary btn-large" disabled>Action</button> 31 <button class="btn btn-primary btn-large" data-url="#"
32 data-action="edit-custom-image" disabled>
33 Edit custom image
34 </button>
22 </div> 35 </div>
23</div> 36</div>
37
38<script>
39$(document).ready(function () {
40 var editCustomImageButton = $('[data-action="edit-custom-image"]');
41 var error = $('#invalid-custom-image-help');
42 var radios = $('[name="select-custom-image"]');
43
44 // return custom image radio buttons which are selected
45 var getSelectedRadios = function () {
46 return $('[name="select-custom-image"]:checked');
47 };
48
49 radios.change(function () {
50 if (getSelectedRadios().length === 1) {
51 editCustomImageButton.removeAttr('disabled');
52 error.hide();
53 }
54 else {
55 editCustomImageButton.attr('disabled', 'disabled');
56 error.show();
57 }
58 });
59
60 editCustomImageButton.click(function () {
61 var selectedRadios = getSelectedRadios();
62
63 if (selectedRadios.length === 1) {
64 document.location.href = selectedRadios.first().attr('data-url');
65 }
66 else {
67 error.show();
68 }
69 });
70});
71</script>
diff --git a/bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py b/bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py
new file mode 100644
index 0000000000..0dcc7d2714
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templatetags/objects_to_dictionaries_filter.py
@@ -0,0 +1,35 @@
1from django import template
2import json
3
4register = template.Library()
5
6def objects_to_dictionaries(iterable, fields):
7 """
8 Convert an iterable into a list of dictionaries; fields should be set
9 to a comma-separated string of properties for each item included in the
10 resulting list; e.g. for a queryset:
11
12 {{ queryset | objects_to_dictionaries:"id,name" }}
13
14 will return a list like
15
16 [{'id': 1, 'name': 'foo'}, ...]
17
18 providing queryset has id and name fields
19
20 This is mostly to support serialising querysets or lists of model objects
21 to JSON
22 """
23 objects = []
24
25 if fields:
26 fields_list = [field.strip() for field in fields.split(',')]
27 for item in iterable:
28 out = {}
29 for field in fields_list:
30 out[field] = getattr(item, field)
31 objects.append(out)
32
33 return objects
34
35register.filter('objects_to_dictionaries', objects_to_dictionaries)
diff --git a/bitbake/lib/toaster/toastergui/templatetags/queryset_to_list_filter.py b/bitbake/lib/toaster/toastergui/templatetags/queryset_to_list_filter.py
deleted file mode 100644
index dfc094b591..0000000000
--- a/bitbake/lib/toaster/toastergui/templatetags/queryset_to_list_filter.py
+++ /dev/null
@@ -1,26 +0,0 @@
1from django import template
2import json
3
4register = template.Library()
5
6def queryset_to_list(queryset, fields):
7 """
8 Convert a queryset to a list; fields can be set to a comma-separated
9 string of fields for each record included in the resulting list; if
10 omitted, all fields are included for each record, e.g.
11
12 {{ queryset | queryset_to_list:"id,name" }}
13
14 will return a list like
15
16 [{'id': 1, 'name': 'foo'}, ...]
17
18 (providing queryset has id and name fields)
19 """
20 if fields:
21 fields_list = [field.strip() for field in fields.split(',')]
22 return list(queryset.values(*fields_list))
23 else:
24 return list(queryset.values())
25
26register.filter('queryset_to_list', queryset_to_list)
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 942dc31ae9..bd5bf63341 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -507,6 +507,7 @@ def builddashboard( request, build_id ):
507 507
508 context = { 508 context = {
509 'build' : build, 509 'build' : build,
510 'project' : build.project,
510 'hasImages' : hasImages, 511 'hasImages' : hasImages,
511 'ntargets' : ntargets, 512 'ntargets' : ntargets,
512 'targets' : targets, 513 'targets' : targets,
@@ -797,6 +798,7 @@ eans multiple licenses exist that cover different parts of the source',
797 context = { 798 context = {
798 'objectname': variant, 799 'objectname': variant,
799 'build' : build, 800 'build' : build,
801 'project' : build.project,
800 'target' : Target.objects.filter( pk = target_id )[ 0 ], 802 'target' : Target.objects.filter( pk = target_id )[ 0 ],
801 'objects' : packages, 803 'objects' : packages,
802 'packages_sum' : packages_sum[ 'installed_size__sum' ], 804 'packages_sum' : packages_sum[ 'installed_size__sum' ],
@@ -937,7 +939,10 @@ def dirinfo(request, build_id, target_id, file_path=None):
937 if head != sep: 939 if head != sep:
938 dir_list.insert(0, head) 940 dir_list.insert(0, head)
939 941
940 context = { 'build': Build.objects.get(pk=build_id), 942 build = Build.objects.get(pk=build_id)
943
944 context = { 'build': build,
945 'project': build.project,
941 'target': Target.objects.get(pk=target_id), 946 'target': Target.objects.get(pk=target_id),
942 'packages_sum': packages_sum['installed_size__sum'], 947 'packages_sum': packages_sum['installed_size__sum'],
943 'objects': objects, 948 'objects': objects,
@@ -1211,6 +1216,7 @@ def tasks_common(request, build_id, variant, task_anchor):
1211 'filter_search_display': filter_search_display, 1216 'filter_search_display': filter_search_display,
1212 'mainheading': title_variant, 1217 'mainheading': title_variant,
1213 'build': build, 1218 'build': build,
1219 'project': build.project,
1214 'objects': task_objects, 1220 'objects': task_objects,
1215 'default_orderby' : default_orderby, 1221 'default_orderby' : default_orderby,
1216 'search_term': search_term, 1222 'search_term': search_term,
@@ -1282,6 +1288,7 @@ def recipes(request, build_id):
1282 context = { 1288 context = {
1283 'objectname': 'recipes', 1289 'objectname': 'recipes',
1284 'build': build, 1290 'build': build,
1291 'project': build.project,
1285 'objects': recipes, 1292 'objects': recipes,
1286 'default_orderby' : 'name:+', 1293 'default_orderby' : 'name:+',
1287 'recipe_deps' : deps, 1294 'recipe_deps' : deps,
@@ -1366,10 +1373,12 @@ def configuration(request, build_id):
1366 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU') 1373 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
1367 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\ 1374 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
1368 .values_list('variable_name', 'variable_value')) 1375 .values_list('variable_name', 'variable_value'))
1376 build = Build.objects.get(pk=build_id)
1369 context.update({'objectname': 'configuration', 1377 context.update({'objectname': 'configuration',
1370 'object_search_display':'variables', 1378 'object_search_display':'variables',
1371 'filter_search_display':'variables', 1379 'filter_search_display':'variables',
1372 'build': Build.objects.get(pk=build_id), 1380 'build': build,
1381 'project': build.project,
1373 'targets': Target.objects.filter(build=build_id)}) 1382 'targets': Target.objects.filter(build=build_id)})
1374 return render(request, template, context) 1383 return render(request, template, context)
1375 1384
@@ -1406,12 +1415,15 @@ def configvars(request, build_id):
1406 file_filter += '/bitbake.conf' 1415 file_filter += '/bitbake.conf'
1407 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path) 1416 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
1408 1417
1418 build = Build.objects.get(pk=build_id)
1419
1409 context = { 1420 context = {
1410 'objectname': 'configvars', 1421 'objectname': 'configvars',
1411 'object_search_display':'BitBake variables', 1422 'object_search_display':'BitBake variables',
1412 'filter_search_display':'variables', 1423 'filter_search_display':'variables',
1413 'file_filter': file_filter, 1424 'file_filter': file_filter,
1414 'build': Build.objects.get(pk=build_id), 1425 'build': build,
1426 'project': build.project,
1415 'objects' : variables, 1427 'objects' : variables,
1416 'total_count':queryset_with_search.count(), 1428 'total_count':queryset_with_search.count(),
1417 'default_orderby' : 'variable_name:+', 1429 'default_orderby' : 'variable_name:+',
@@ -1480,6 +1492,7 @@ def bpackage(request, build_id):
1480 context = { 1492 context = {
1481 'objectname': 'packages built', 1493 'objectname': 'packages built',
1482 'build': build, 1494 'build': build,
1495 'project': build.project,
1483 'objects' : packages, 1496 'objects' : packages,
1484 'default_orderby' : 'name:+', 1497 'default_orderby' : 'name:+',
1485 'tablecols':[ 1498 'tablecols':[
@@ -1554,7 +1567,12 @@ def bpackage(request, build_id):
1554def bfile(request, build_id, package_id): 1567def bfile(request, build_id, package_id):
1555 template = 'bfile.html' 1568 template = 'bfile.html'
1556 files = Package_File.objects.filter(package = package_id) 1569 files = Package_File.objects.filter(package = package_id)
1557 context = {'build': Build.objects.get(pk=build_id), 'objects' : files} 1570 build = Build.objects.get(pk=build_id)
1571 context = {
1572 'build': build,
1573 'project': build.project,
1574 'objects' : files
1575 }
1558 return render(request, template, context) 1576 return render(request, template, context)
1559 1577
1560 1578