blob: 528dd32b0ce9169c50f493f0df801eedeb5c6e20 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002# BitBake Toaster Implementation
3#
4# Copyright (C) 2015 Intel Corporation
5#
Brad Bishopc342db32019-05-15 21:57:59 -04006# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05008
9from toastergui.widgets import ToasterTable
Andrew Geissler82c905d2020-04-13 13:39:40 -050010from orm.models import Recipe, ProjectLayer, Layer_Version, Project
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050011from orm.models import CustomImageRecipe, Package, Target, Build, LogMessage, Task
Patrick Williamsc0f7c042017-02-23 20:41:17 -060012from orm.models import CustomImagePackage, Package_DependencyManager
Andrew Geissler82c905d2020-04-13 13:39:40 -050013from django.db.models import Q, Sum, Count, When, Case, Value, IntegerField
Patrick Williamsc124f4f2015-09-15 14:41:29 -050014
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050015from toastergui.tablefilter import TableFilter
16from toastergui.tablefilter import TableFilterActionToggle
17from toastergui.tablefilter import TableFilterActionDateRange
18from toastergui.tablefilter import TableFilterActionDay
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080020import os
21
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050022class ProjectFilters(object):
23 @staticmethod
24 def in_project(project_layers):
25 return Q(layer_version__in=project_layers)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050027 @staticmethod
28 def not_in_project(project_layers):
29 return ~(ProjectFilters.in_project(project_layers))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030
31class LayersTable(ToasterTable):
32 """Table of layers in Toaster"""
33
34 def __init__(self, *args, **kwargs):
35 super(LayersTable, self).__init__(*args, **kwargs)
36 self.default_orderby = "layer__name"
Patrick Williamsf1e5d692016-03-30 15:21:19 -050037 self.title = "Compatible layers"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038
39 def get_context_data(self, **kwargs):
40 context = super(LayersTable, self).get_context_data(**kwargs)
41
42 project = Project.objects.get(pk=kwargs['pid'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -050043 context['project'] = project
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044
45 return context
46
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047 def setup_filters(self, *args, **kwargs):
48 project = Project.objects.get(pk=kwargs['pid'])
49 self.project_layers = ProjectLayer.objects.filter(project=project)
50
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050051 in_current_project_filter = TableFilter(
52 "in_current_project",
53 "Filter by project layers"
54 )
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050056 criteria = Q(projectlayer__in=self.project_layers)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050058 in_project_action = TableFilterActionToggle(
59 "in_project",
60 "Layers added to this project",
61 criteria
62 )
Patrick Williamsc124f4f2015-09-15 14:41:29 -050063
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050064 not_in_project_action = TableFilterActionToggle(
65 "not_in_project",
66 "Layers not added to this project",
67 ~criteria
68 )
Patrick Williamsc124f4f2015-09-15 14:41:29 -050069
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050070 in_current_project_filter.add_action(in_project_action)
71 in_current_project_filter.add_action(not_in_project_action)
72 self.add_filter(in_current_project_filter)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050073
74 def setup_queryset(self, *args, **kwargs):
75 prj = Project.objects.get(pk = kwargs['pid'])
Patrick Williamsf1e5d692016-03-30 15:21:19 -050076 compatible_layers = prj.get_all_compatible_layer_versions()
77
78 self.static_context_extra['current_layers'] = \
79 prj.get_project_layer_versions(pk=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050080
81 self.queryset = compatible_layers.order_by(self.default_orderby)
82
83 def setup_columns(self, *args, **kwargs):
84
85 layer_link_template = '''
86 <a href="{% url 'layerdetails' extra.pid data.id %}">
87 {{data.layer.name}}
88 </a>
89 '''
90
91 self.add_column(title="Layer",
92 hideable=False,
93 orderable=True,
94 static_data_name="layer__name",
95 static_data_template=layer_link_template)
96
97 self.add_column(title="Summary",
98 field_name="layer__summary")
99
100 git_url_template = '''
101 <a href="{% url 'layerdetails' extra.pid data.id %}">
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600102 {% if data.layer.local_source_dir %}
103 <code>{{data.layer.local_source_dir}}</code>
104 {% else %}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500105 <code>{{data.layer.vcs_url}}</code>
106 </a>
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600107 {% endif %}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500108 {% if data.get_vcs_link_url %}
109 <a target="_blank" href="{{ data.get_vcs_link_url }}">
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600110 <span class="glyphicon glyphicon-new-window"></span>
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500111 </a>
112 {% endif %}
113 '''
114
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600115 self.add_column(title="Layer source code location",
116 help_text="A Git repository or an absolute path to a directory",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500117 hidden=True,
118 static_data_name="layer__vcs_url",
119 static_data_template=git_url_template)
120
121 git_dir_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600122 {% if data.layer.local_source_dir %}
123 <span class="text-muted">Not applicable</span>
124 <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer.name}} is not in a Git repository, so there is no subdirectory associated with it"> </span>
125 {% else %}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500126 <a href="{% url 'layerdetails' extra.pid data.id %}">
127 <code>{{data.dirpath}}</code>
128 </a>
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600129 {% endif %}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500130 {% if data.dirpath and data.get_vcs_dirpath_link_url %}
131 <a target="_blank" href="{{ data.get_vcs_dirpath_link_url }}">
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600132 <span class="glyphicon glyphicon-new-window"></span>
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500133 </a>
134 {% endif %}'''
135
136 self.add_column(title="Subdirectory",
137 help_text="The layer directory within the Git repository",
138 hidden=True,
139 static_data_name="git_subdir",
140 static_data_template=git_dir_template)
141
142 revision_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600143 {% if data.layer.local_source_dir %}
144 <span class="text-muted">Not applicable</span>
145 <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer.name}} is not in a Git repository, so there is no revision associated with it"> </span>
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500146 {% else %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600147 {% with vcs_ref=data.get_vcs_reference %}
148 {% include 'snippets/gitrev_popover.html' %}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 {% endwith %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600150 {% endif %}
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500151 '''
152
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500153 self.add_column(title="Git revision",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500154 help_text="The Git branch, tag or commit. For the layers from the OpenEmbedded layer source, the revision is always the branch compatible with the Yocto Project version you selected for this project",
155 static_data_name="revision",
156 static_data_template=revision_template)
157
158 deps_template = '''
159 {% with ods=data.dependencies.all%}
160 {% if ods.count %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600161 <a class="btn btn-default" title="<a href='{% url "layerdetails" extra.pid data.id %}'>{{data.layer.name}}</a> dependencies"
162 data-content="<ul class='list-unstyled'>
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500163 {% for i in ods%}
164 <li><a href='{% url "layerdetails" extra.pid i.depends_on.pk %}'>{{i.depends_on.layer.name}}</a></li>
165 {% endfor %}
166 </ul>">
167 {{ods.count}}
168 </a>
169 {% endif %}
170 {% endwith %}
171 '''
172
173 self.add_column(title="Dependencies",
174 help_text="Other layers a layer depends upon",
175 static_data_name="dependencies",
176 static_data_template=deps_template)
177
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500178 self.add_column(title="Add | Remove",
179 help_text="Add or remove layers to / from your project",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180 hideable=False,
181 filter_name="in_current_project",
182 static_data_name="add-del-layers",
183 static_data_template='{% include "layer_btn.html" %}')
184
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500185
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500186class MachinesTable(ToasterTable):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500187 """Table of Machines in Toaster"""
188
189 def __init__(self, *args, **kwargs):
190 super(MachinesTable, self).__init__(*args, **kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600191 self.empty_state = "Toaster has no machine information for this project. Sadly, machine information cannot be obtained from builds, so this page will remain empty."
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500192 self.title = "Compatible machines"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193 self.default_orderby = "name"
194
195 def get_context_data(self, **kwargs):
196 context = super(MachinesTable, self).get_context_data(**kwargs)
197 context['project'] = Project.objects.get(pk=kwargs['pid'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500198 return context
199
200 def setup_filters(self, *args, **kwargs):
201 project = Project.objects.get(pk=kwargs['pid'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500202
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500203 in_current_project_filter = TableFilter(
204 "in_current_project",
205 "Filter by project machines"
206 )
207
208 in_project_action = TableFilterActionToggle(
209 "in_project",
210 "Machines provided by layers added to this project",
211 ProjectFilters.in_project(self.project_layers)
212 )
213
214 not_in_project_action = TableFilterActionToggle(
215 "not_in_project",
216 "Machines provided by layers not added to this project",
217 ProjectFilters.not_in_project(self.project_layers)
218 )
219
220 in_current_project_filter.add_action(in_project_action)
221 in_current_project_filter.add_action(not_in_project_action)
222 self.add_filter(in_current_project_filter)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500223
224 def setup_queryset(self, *args, **kwargs):
225 prj = Project.objects.get(pk = kwargs['pid'])
226 self.queryset = prj.get_all_compatible_machines()
227 self.queryset = self.queryset.order_by(self.default_orderby)
228
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500229 self.static_context_extra['current_layers'] = \
230 self.project_layers = \
231 prj.get_project_layer_versions(pk=True)
232
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500233 def setup_columns(self, *args, **kwargs):
234
235 self.add_column(title="Machine",
236 hideable=False,
237 orderable=True,
238 field_name="name")
239
240 self.add_column(title="Description",
241 field_name="description")
242
243 layer_link_template = '''
244 <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}">
245 {{data.layer_version.layer.name}}</a>
246 '''
247
248 self.add_column(title="Layer",
249 static_data_name="layer_version__layer__name",
250 static_data_template=layer_link_template,
251 orderable=True)
252
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500253 self.add_column(title="Git revision",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 help_text="The Git branch, tag or commit. For the layers from the OpenEmbedded layer source, the revision is always the branch compatible with the Yocto Project version you selected for this project",
255 hidden=True,
256 field_name="layer_version__get_vcs_reference")
257
258 machine_file_template = '''<code>conf/machine/{{data.name}}.conf</code>
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600259 <a href="{{data.get_vcs_machine_file_link_url}}" target="_blank"><span class="glyphicon glyphicon-new-window"></i></a>'''
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500260
261 self.add_column(title="Machine file",
262 hidden=True,
263 static_data_name="machinefile",
264 static_data_template=machine_file_template)
265
266 self.add_column(title="Select",
267 help_text="Sets the selected machine as the project machine. You can only have one machine per project",
268 hideable=False,
269 filter_name="in_current_project",
270 static_data_name="add-del-layers",
271 static_data_template='{% include "machine_btn.html" %}')
272
273
274class LayerMachinesTable(MachinesTable):
275 """ Smaller version of the Machines table for use in layer details """
276
277 def __init__(self, *args, **kwargs):
278 super(LayerMachinesTable, self).__init__(*args, **kwargs)
279
280 def get_context_data(self, **kwargs):
281 context = super(LayerMachinesTable, self).get_context_data(**kwargs)
282 context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
283 return context
284
285
286 def setup_queryset(self, *args, **kwargs):
287 MachinesTable.setup_queryset(self, *args, **kwargs)
288
289 self.queryset = self.queryset.filter(layer_version__pk=int(kwargs['layerid']))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500290 self.queryset = self.queryset.order_by(self.default_orderby)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500291 self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count()
292
293 def setup_columns(self, *args, **kwargs):
294 self.add_column(title="Machine",
295 hideable=False,
296 orderable=True,
297 field_name="name")
298
299 self.add_column(title="Description",
300 field_name="description")
301
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600302 select_btn_template = '''
303 <a href="{% url "project" extra.pid %}?setMachine={{data.name}}"
304 class="btn btn-default btn-block select-machine-btn
305 {% if extra.in_prj == 0%}disabled{%endif%}">Select machine</a>
306 '''
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500307
308 self.add_column(title="Select machine",
309 static_data_name="add-del-layers",
310 static_data_template=select_btn_template)
311
312
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500313class RecipesTable(ToasterTable):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500314 """Table of All Recipes in Toaster"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315
316 def __init__(self, *args, **kwargs):
317 super(RecipesTable, self).__init__(*args, **kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600318 self.empty_state = "Toaster has no recipe information. To generate recipe information you need to run a build."
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500320 build_col = { 'title' : "Build",
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600321 'help_text' : "Before building a recipe, you might need to add the corresponding layer to your project",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500322 'hideable' : False,
323 'filter_name' : "in_current_project",
324 'static_data_name' : "add-del-layers",
325 'static_data_template' : '{% include "recipe_btn.html" %}'}
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800326 if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'):
327 build_col['static_data_template'] = '{% include "recipe_add_btn.html" %}'
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500328
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329 def get_context_data(self, **kwargs):
330 project = Project.objects.get(pk=kwargs['pid'])
331 context = super(RecipesTable, self).get_context_data(**kwargs)
332
333 context['project'] = project
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600334 context['projectlayers'] = [player.layercommit.id for player in ProjectLayer.objects.filter(project=context['project'])]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500335
336 return context
337
338 def setup_filters(self, *args, **kwargs):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500339 table_filter = TableFilter(
340 'in_current_project',
341 'Filter by project recipes'
342 )
343
344 in_project_action = TableFilterActionToggle(
345 'in_project',
346 'Recipes provided by layers added to this project',
347 ProjectFilters.in_project(self.project_layers)
348 )
349
350 not_in_project_action = TableFilterActionToggle(
351 'not_in_project',
352 'Recipes provided by layers not added to this project',
353 ProjectFilters.not_in_project(self.project_layers)
354 )
355
356 table_filter.add_action(in_project_action)
357 table_filter.add_action(not_in_project_action)
358 self.add_filter(table_filter)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500359
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500360 def setup_queryset(self, *args, **kwargs):
361 prj = Project.objects.get(pk = kwargs['pid'])
362
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500363 # Project layers used by the filters
364 self.project_layers = prj.get_project_layer_versions(pk=True)
365
366 # Project layers used to switch the button states
367 self.static_context_extra['current_layers'] = self.project_layers
368
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500369 self.queryset = prj.get_all_compatible_recipes()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500370
371
372 def setup_columns(self, *args, **kwargs):
373
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500374 self.add_column(title="Version",
375 hidden=False,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500376 field_name="version")
377
378 self.add_column(title="Description",
379 field_name="get_description_or_summary")
380
381 recipe_file_template = '''
382 <code>{{data.file_path}}</code>
383 <a href="{{data.get_vcs_recipe_file_link_url}}" target="_blank">
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600384 <span class="glyphicon glyphicon-new-window"></i>
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500385 </a>
386 '''
387
388 self.add_column(title="Recipe file",
389 help_text="Path to the recipe .bb file",
390 hidden=True,
391 static_data_name="recipe-file",
392 static_data_template=recipe_file_template)
393
394 self.add_column(title="Section",
395 help_text="The section in which recipes should be categorized",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500396 hidden=True,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500397 orderable=True,
398 field_name="section")
399
400 layer_link_template = '''
401 <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}">
402 {{data.layer_version.layer.name}}</a>
403 '''
404
405 self.add_column(title="Layer",
406 help_text="The name of the layer providing the recipe",
407 orderable=True,
408 static_data_name="layer_version__layer__name",
409 static_data_template=layer_link_template)
410
411 self.add_column(title="License",
412 help_text="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",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500413 hidden=True,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500414 orderable=True,
415 field_name="license")
416
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600417 revision_link_template = '''
418 {% if data.layer_version.layer.local_source_dir %}
419 <span class="text-muted">Not applicable</span>
420 <span class="glyphicon glyphicon-question-sign get-help" data-original-title="" title="The source code of {{data.layer_version.layer.name}} is not in a Git repository, so there is no revision associated with it"> </span>
421 {% else %}
422 {{data.layer_version.get_vcs_reference}}
423 {% endif %}
424 '''
425
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500426 self.add_column(title="Git revision",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500427 hidden=True,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600428 static_data_name="layer_version__get_vcs_reference",
429 static_data_template=revision_link_template)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500430
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431
432class LayerRecipesTable(RecipesTable):
433 """ Smaller version of the Recipes table for use in layer details """
434
435 def __init__(self, *args, **kwargs):
436 super(LayerRecipesTable, self).__init__(*args, **kwargs)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500437 self.default_orderby = "name"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438
439 def get_context_data(self, **kwargs):
440 context = super(LayerRecipesTable, self).get_context_data(**kwargs)
441 context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
442 return context
443
444
445 def setup_queryset(self, *args, **kwargs):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500446 self.queryset = \
447 Recipe.objects.filter(layer_version__pk=int(kwargs['layerid']))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500448
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500449 self.queryset = self.queryset.order_by(self.default_orderby)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450 self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count()
451
452 def setup_columns(self, *args, **kwargs):
453 self.add_column(title="Recipe",
454 help_text="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",
455 hideable=False,
456 orderable=True,
457 field_name="name")
458
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500459 self.add_column(title="Version",
460 field_name="version")
461
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500462 self.add_column(title="Description",
463 field_name="get_description_or_summary")
464
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600465 build_recipe_template = '''
466 <a class="btn btn-default btn-block build-recipe-btn
467 {% if extra.in_prj == 0 %}disabled{% endif %}"
468 data-recipe-name="{{data.name}}">Build recipe</a>
469 '''
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500470
471 self.add_column(title="Build recipe",
472 static_data_name="add-del-layers",
473 static_data_template=build_recipe_template)
474
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500475class CustomImagesTable(ToasterTable):
476 """ Table to display your custom images """
477 def __init__(self, *args, **kwargs):
478 super(CustomImagesTable, self).__init__(*args, **kwargs)
479 self.title = "Custom images"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500480 self.default_orderby = "name"
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500481
482 def get_context_data(self, **kwargs):
483 context = super(CustomImagesTable, self).get_context_data(**kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600484
485 empty_state_template = '''
486 You have not created any custom images yet.
487 <a href="{% url 'newcustomimage' data.pid %}">
488 Create your first custom image</a>
489 '''
490 context['empty_state'] = self.render_static_data(empty_state_template,
491 kwargs)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500492 project = Project.objects.get(pk=kwargs['pid'])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600493
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500494 # TODO put project into the ToasterTable base class
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500495 context['project'] = project
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500496 return context
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500497
498 def setup_queryset(self, *args, **kwargs):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500499 prj = Project.objects.get(pk = kwargs['pid'])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500500 self.queryset = CustomImageRecipe.objects.filter(project=prj)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500501 self.queryset = self.queryset.order_by(self.default_orderby)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500502
503 def setup_columns(self, *args, **kwargs):
504
505 name_link_template = '''
506 <a href="{% url 'customrecipe' extra.pid data.id %}">
507 {{data.name}}
508 </a>
509 '''
510
511 self.add_column(title="Custom image",
512 hideable=False,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500513 orderable=True,
514 field_name="name",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500515 static_data_name="name",
516 static_data_template=name_link_template)
517
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500518 recipe_file_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600519 {% if data.get_base_recipe_file %}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500520 <code>{{data.name}}_{{data.version}}.bb</code>
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600521 <a href="{% url 'customrecipedownload' extra.pid data.pk %}"
522 class="glyphicon glyphicon-download-alt get-help" title="Download recipe file"></a>
523 {% endif %}'''
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500524
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500525 self.add_column(title="Recipe file",
526 static_data_name='recipe_file_download',
527 static_data_template=recipe_file_template)
528
529 approx_packages_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600530 {% if data.get_all_packages.count > 0 %}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500531 <a href="{% url 'customrecipe' extra.pid data.id %}">
532 {{data.get_all_packages.count}}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600533 </a>
534 {% endif %}'''
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500535
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600536 self.add_column(title="Packages",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500537 static_data_name='approx_packages',
538 static_data_template=approx_packages_template)
539
540
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500541 build_btn_template = '''
542 <button data-recipe-name="{{data.name}}"
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600543 class="btn btn-default btn-block build-recipe-btn">
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500544 Build
545 </button>'''
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500546
547 self.add_column(title="Build",
548 hideable=False,
549 static_data_name='build_custom_img',
550 static_data_template=build_btn_template)
551
552class ImageRecipesTable(RecipesTable):
553 """ A subset of the recipes table which displayed just image recipes """
554
555 def __init__(self, *args, **kwargs):
556 super(ImageRecipesTable, self).__init__(*args, **kwargs)
557 self.title = "Compatible image recipes"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500558 self.default_orderby = "name"
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500559
560 def setup_queryset(self, *args, **kwargs):
561 super(ImageRecipesTable, self).setup_queryset(*args, **kwargs)
562
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500563 custom_image_recipes = CustomImageRecipe.objects.filter(
564 project=kwargs['pid'])
565 self.queryset = self.queryset.filter(
566 Q(is_image=True) & ~Q(pk__in=custom_image_recipes))
567 self.queryset = self.queryset.order_by(self.default_orderby)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500568
569
570 def setup_columns(self, *args, **kwargs):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500571
572 name_link_template = '''
573 <a href="{% url 'recipedetails' extra.pid data.pk %}">{{data.name}}</a>
574 '''
575
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500576 self.add_column(title="Image recipe",
577 help_text="When you build an image recipe, you get an "
578 "image: a root file system you can"
579 "deploy to a machine",
580 hideable=False,
581 orderable=True,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500582 static_data_name="name",
583 static_data_template=name_link_template,
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500584 field_name="name")
585
586 super(ImageRecipesTable, self).setup_columns(*args, **kwargs)
587
588 self.add_column(**RecipesTable.build_col)
589
590
591class NewCustomImagesTable(ImageRecipesTable):
592 """ Table which displays Images recipes which can be customised """
593 def __init__(self, *args, **kwargs):
594 super(NewCustomImagesTable, self).__init__(*args, **kwargs)
595 self.title = "Select the image recipe you want to customise"
596
597 def setup_queryset(self, *args, **kwargs):
598 super(ImageRecipesTable, self).setup_queryset(*args, **kwargs)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500599 prj = Project.objects.get(pk = kwargs['pid'])
600 self.static_context_extra['current_layers'] = \
601 prj.get_project_layer_versions(pk=True)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500602
603 self.queryset = self.queryset.filter(is_image=True)
604
605 def setup_columns(self, *args, **kwargs):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500606
607 name_link_template = '''
608 <a href="{% url 'recipedetails' extra.pid data.pk %}">{{data.name}}</a>
609 '''
610
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500611 self.add_column(title="Image recipe",
612 help_text="When you build an image recipe, you get an "
613 "image: a root file system you can"
614 "deploy to a machine",
615 hideable=False,
616 orderable=True,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500617 static_data_name="name",
618 static_data_template=name_link_template,
619 field_name="name")
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500620
621 super(ImageRecipesTable, self).setup_columns(*args, **kwargs)
622
623 self.add_column(title="Customise",
624 hideable=False,
625 filter_name="in_current_project",
626 static_data_name="customise-or-add-recipe",
627 static_data_template='{% include "customise_btn.html" %}')
628
629
630class SoftwareRecipesTable(RecipesTable):
631 """ Displays just the software recipes """
632 def __init__(self, *args, **kwargs):
633 super(SoftwareRecipesTable, self).__init__(*args, **kwargs)
634 self.title = "Compatible software recipes"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500635 self.default_orderby = "name"
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500636
637 def setup_queryset(self, *args, **kwargs):
638 super(SoftwareRecipesTable, self).setup_queryset(*args, **kwargs)
639
640 self.queryset = self.queryset.filter(is_image=False)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500641 self.queryset = self.queryset.order_by(self.default_orderby)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500642
643
644 def setup_columns(self, *args, **kwargs):
645 self.add_column(title="Software recipe",
646 help_text="Information about a single piece of "
647 "software, including where to download the source, "
648 "configuration options, how to compile the source "
649 "files and how to package the compiled output",
650 hideable=False,
651 orderable=True,
652 field_name="name")
653
654 super(SoftwareRecipesTable, self).setup_columns(*args, **kwargs)
655
656 self.add_column(**RecipesTable.build_col)
657
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500658class PackagesTable(ToasterTable):
659 """ Table to display the packages in a recipe from it's last successful
660 build"""
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500661
662 def __init__(self, *args, **kwargs):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500663 super(PackagesTable, self).__init__(*args, **kwargs)
664 self.title = "Packages included"
665 self.packages = None
666 self.default_orderby = "name"
667
668 def create_package_list(self, recipe, project_id):
669 """Creates a list of packages for the specified recipe by looking for
670 the last SUCCEEDED build of ther recipe"""
671
672 target = Target.objects.filter(Q(target=recipe.name) &
673 Q(build__project_id=project_id) &
674 Q(build__outcome=Build.SUCCEEDED)
675 ).last()
676
677 if target:
678 pkgs = target.target_installed_package_set.values_list('package',
679 flat=True)
680 return Package.objects.filter(pk__in=pkgs)
681
682 # Target/recipe never successfully built so empty queryset
683 return Package.objects.none()
684
685 def get_context_data(self, **kwargs):
686 """Context for rendering the sidebar and other items on the recipe
687 details page """
688 context = super(PackagesTable, self).get_context_data(**kwargs)
689
690 recipe = Recipe.objects.get(pk=kwargs['recipe_id'])
691 project = Project.objects.get(pk=kwargs['pid'])
692
693 in_project = (recipe.layer_version.pk in
694 project.get_project_layer_versions(pk=True))
695
696 packages = self.create_package_list(recipe, project.pk)
697
698 context.update({'project': project,
699 'recipe' : recipe,
700 'packages': packages,
701 'approx_pkg_size' : packages.aggregate(Sum('size')),
702 'in_project' : in_project,
703 })
704
705 return context
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500706
707 def setup_queryset(self, *args, **kwargs):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500708 recipe = Recipe.objects.get(pk=kwargs['recipe_id'])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600709 self.static_context_extra['target_name'] = recipe.name
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500710
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500711 self.queryset = self.create_package_list(recipe, kwargs['pid'])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500712 self.queryset = self.queryset.order_by('name')
713
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500714 def setup_columns(self, *args, **kwargs):
715 self.add_column(title="Package",
716 hideable=False,
717 orderable=True,
718 field_name="name")
719
720 self.add_column(title="Package Version",
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500721 field_name="version",
722 hideable=False)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500723
724 self.add_column(title="Approx Size",
725 orderable=True,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600726 field_name="size",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500727 static_data_name="size",
728 static_data_template="{% load projecttags %} \
729 {{data.size|filtered_filesizeformat}}")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500730
731 self.add_column(title="License",
732 field_name="license",
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600733 orderable=True,
734 hidden=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500735
736
737 self.add_column(title="Dependencies",
738 static_data_name="dependencies",
739 static_data_template='\
740 {% include "snippets/pkg_dependencies_popover.html" %}')
741
742 self.add_column(title="Reverse dependencies",
743 static_data_name="reverse_dependencies",
744 static_data_template='\
745 {% include "snippets/pkg_revdependencies_popover.html" %}',
746 hidden=True)
747
748 self.add_column(title="Recipe",
749 field_name="recipe__name",
750 orderable=True,
751 hidden=True)
752
753 self.add_column(title="Recipe version",
754 field_name="recipe__version",
755 hidden=True)
756
757
758class SelectPackagesTable(PackagesTable):
759 """ Table to display the packages to add and remove from an image """
760
761 def __init__(self, *args, **kwargs):
762 super(SelectPackagesTable, self).__init__(*args, **kwargs)
763 self.title = "Add | Remove packages"
764
765 def setup_queryset(self, *args, **kwargs):
766 self.cust_recipe =\
767 CustomImageRecipe.objects.get(pk=kwargs['custrecipeid'])
768 prj = Project.objects.get(pk = kwargs['pid'])
769
770 current_packages = self.cust_recipe.get_all_packages()
771
772 current_recipes = prj.get_available_recipes()
773
774 # only show packages where recipes->layers are in the project
775 self.queryset = CustomImagePackage.objects.filter(
776 ~Q(recipe=None) &
777 Q(recipe__in=current_recipes))
778
779 self.queryset = self.queryset.order_by('name')
780
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600781 # This target is the target used to work out which group of dependences
782 # to display, if we've built the custom image we use it otherwise we
783 # can use the based recipe instead
784 if prj.build_set.filter(target__target=self.cust_recipe.name).count()\
785 > 0:
786 self.static_context_extra['target_name'] = self.cust_recipe.name
787 else:
788 self.static_context_extra['target_name'] =\
789 Package_DependencyManager.TARGET_LATEST
790
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500791 self.static_context_extra['recipe_id'] = kwargs['custrecipeid']
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600792
793
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500794 self.static_context_extra['current_packages'] = \
795 current_packages.values_list('pk', flat=True)
796
797 def get_context_data(self, **kwargs):
798 # to reuse the Super class map the custrecipeid to the recipe_id
799 kwargs['recipe_id'] = kwargs['custrecipeid']
800 context = super(SelectPackagesTable, self).get_context_data(**kwargs)
801 custom_recipe = \
802 CustomImageRecipe.objects.get(pk=kwargs['custrecipeid'])
803
804 context['recipe'] = custom_recipe
805 context['approx_pkg_size'] = \
806 custom_recipe.get_all_packages().aggregate(Sum('size'))
807 return context
808
809
810 def setup_columns(self, *args, **kwargs):
811 super(SelectPackagesTable, self).setup_columns(*args, **kwargs)
812
813 add_remove_template = '{% include "pkg_add_rm_btn.html" %}'
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500814
815 self.add_column(title="Add | Remove",
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500816 hideable=False,
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500817 help_text="Use the add and remove buttons to modify "
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500818 "the package content of your custom image",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500819 static_data_name="add_rm_pkg_btn",
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500820 static_data_template=add_remove_template,
821 filter_name='in_current_image_filter')
822
823 def setup_filters(self, *args, **kwargs):
824 in_current_image_filter = TableFilter(
825 'in_current_image_filter',
826 'Filter by added packages'
827 )
828
829 in_image_action = TableFilterActionToggle(
830 'in_image',
831 'Packages in %s' % self.cust_recipe.name,
832 Q(pk__in=self.static_context_extra['current_packages'])
833 )
834
835 not_in_image_action = TableFilterActionToggle(
836 'not_in_image',
837 'Packages not added to %s' % self.cust_recipe.name,
838 ~Q(pk__in=self.static_context_extra['current_packages'])
839 )
840
841 in_current_image_filter.add_action(in_image_action)
842 in_current_image_filter.add_action(not_in_image_action)
843 self.add_filter(in_current_image_filter)
844
845class ProjectsTable(ToasterTable):
846 """Table of projects in Toaster"""
847
848 def __init__(self, *args, **kwargs):
849 super(ProjectsTable, self).__init__(*args, **kwargs)
850 self.default_orderby = '-updated'
851 self.title = 'All projects'
852 self.static_context_extra['Build'] = Build
853
854 def get_context_data(self, **kwargs):
855 return super(ProjectsTable, self).get_context_data(**kwargs)
856
857 def setup_queryset(self, *args, **kwargs):
858 queryset = Project.objects.all()
859
860 # annotate each project with its number of builds
861 queryset = queryset.annotate(num_builds=Count('build'))
862
863 # exclude the command line builds project if it has no builds
864 q_default_with_builds = Q(is_default=True) & Q(num_builds__gt=0)
865 queryset = queryset.filter(Q(is_default=False) |
866 q_default_with_builds)
867
868 # order rows
869 queryset = queryset.order_by(self.default_orderby)
870
871 self.queryset = queryset
872
873 # columns: last activity on (updated) - DEFAULT, project (name), release,
874 # machine, number of builds, last build outcome, recipe (name), errors,
875 # warnings, image files
876 def setup_columns(self, *args, **kwargs):
877 name_template = '''
878 {% load project_url_tag %}
879 <span data-project-field="name">
880 <a href="{% project_url data %}">
881 {{data.name}}
882 </a>
883 </span>
884 '''
885
886 last_activity_on_template = '''
887 {% load project_url_tag %}
888 <span data-project-field="updated">
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500889 {{data.updated | date:"d/m/y H:i"}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500890 </span>
891 '''
892
893 release_template = '''
894 <span data-project-field="release">
895 {% if data.release %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600896 {{data.release.name}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500897 {% elif data.is_default %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600898 <span class="text-muted">Not applicable</span>
899 <span class="glyphicon glyphicon-question-sign get-help hover-help"
900 title="This project does not have a release set.
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500901 It simply collects information about the builds you start from
902 the command line while Toaster is running"
903 style="visibility: hidden;">
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600904 </span>
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500905 {% else %}
906 No release available
907 {% endif %}
908 </span>
909 '''
910
911 machine_template = '''
912 <span data-project-field="machine">
913 {% if data.is_default %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600914 <span class="text-muted">Not applicable</span>
915 <span class="glyphicon glyphicon-question-sign get-help hover-help"
916 title="This project does not have a machine
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500917 set. It simply collects information about the builds you
918 start from the command line while Toaster is running"
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600919 style="visibility: hidden;"></span>
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500920 {% else %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600921 {{data.get_current_machine_name}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500922 {% endif %}
923 </span>
924 '''
925
926 number_of_builds_template = '''
927 {% if data.get_number_of_builds > 0 %}
928 <a href="{% url 'projectbuilds' data.id %}">
929 {{data.get_number_of_builds}}
930 </a>
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500931 {% endif %}
932 '''
933
934 last_build_outcome_template = '''
935 {% if data.get_number_of_builds > 0 %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600936 {% if data.get_last_outcome == extra.Build.SUCCEEDED %}
937 <span class="glyphicon glyphicon-ok-circle"></span>
938 {% elif data.get_last_outcome == extra.Build.FAILED %}
939 <span class="glyphicon glyphicon-minus-sign"></span>
940 {% endif %}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500941 {% endif %}
942 '''
943
944 recipe_template = '''
945 {% if data.get_number_of_builds > 0 %}
946 <a href="{% url "builddashboard" data.get_last_build_id %}">
947 {{data.get_last_target}}
948 </a>
949 {% endif %}
950 '''
951
952 errors_template = '''
953 {% if data.get_number_of_builds > 0 and data.get_last_errors > 0 %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600954 <a class="errors.count text-danger"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500955 href="{% url "builddashboard" data.get_last_build_id %}#errors">
956 {{data.get_last_errors}} error{{data.get_last_errors | pluralize}}
957 </a>
958 {% endif %}
959 '''
960
961 warnings_template = '''
962 {% if data.get_number_of_builds > 0 and data.get_last_warnings > 0 %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600963 <a class="warnings.count text-warning"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500964 href="{% url "builddashboard" data.get_last_build_id %}#warnings">
965 {{data.get_last_warnings}} warning{{data.get_last_warnings | pluralize}}
966 </a>
967 {% endif %}
968 '''
969
970 image_files_template = '''
971 {% if data.get_number_of_builds > 0 and data.get_last_outcome == extra.Build.SUCCEEDED %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600972 {{data.get_last_build_extensions}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500973 {% endif %}
974 '''
975
976 self.add_column(title='Project',
977 hideable=False,
978 orderable=True,
979 static_data_name='name',
980 static_data_template=name_template)
981
982 self.add_column(title='Last activity on',
983 help_text='Starting date and time of the \
984 last project build. If the project has no \
985 builds, this shows the date the project was \
986 created.',
987 hideable=False,
988 orderable=True,
989 static_data_name='updated',
990 static_data_template=last_activity_on_template)
991
992 self.add_column(title='Release',
993 help_text='The version of the build system used by \
994 the project',
995 hideable=False,
996 orderable=True,
997 static_data_name='release',
998 static_data_template=release_template)
999
1000 self.add_column(title='Machine',
1001 help_text='The hardware currently selected for the \
1002 project',
1003 hideable=False,
1004 orderable=False,
1005 static_data_name='machine',
1006 static_data_template=machine_template)
1007
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001008 self.add_column(title='Builds',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001009 help_text='The number of builds which have been run \
1010 for the project',
1011 hideable=False,
1012 orderable=False,
1013 static_data_name='number_of_builds',
1014 static_data_template=number_of_builds_template)
1015
1016 self.add_column(title='Last build outcome',
1017 help_text='Indicates whether the last project build \
1018 completed successfully or failed',
1019 hideable=True,
1020 orderable=False,
1021 static_data_name='last_build_outcome',
1022 static_data_template=last_build_outcome_template)
1023
1024 self.add_column(title='Recipe',
1025 help_text='The last recipe which was built in this \
1026 project',
1027 hideable=True,
1028 orderable=False,
1029 static_data_name='recipe_name',
1030 static_data_template=recipe_template)
1031
1032 self.add_column(title='Errors',
1033 help_text='The number of errors encountered during \
1034 the last project build (if any)',
1035 hideable=True,
1036 orderable=False,
1037 static_data_name='errors',
1038 static_data_template=errors_template)
1039
1040 self.add_column(title='Warnings',
1041 help_text='The number of warnings encountered during \
1042 the last project build (if any)',
1043 hideable=True,
1044 hidden=True,
1045 orderable=False,
1046 static_data_name='warnings',
1047 static_data_template=warnings_template)
1048
1049 self.add_column(title='Image files',
1050 help_text='The root file system types produced by \
1051 the last project build',
1052 hideable=True,
1053 hidden=True,
1054 orderable=False,
1055 static_data_name='image_files',
1056 static_data_template=image_files_template)
1057
1058class BuildsTable(ToasterTable):
1059 """Table of builds in Toaster"""
1060
1061 def __init__(self, *args, **kwargs):
1062 super(BuildsTable, self).__init__(*args, **kwargs)
1063 self.default_orderby = '-completed_on'
1064 self.static_context_extra['Build'] = Build
1065 self.static_context_extra['Task'] = Task
1066
1067 # attributes that are overridden in subclasses
1068
1069 # title for the page
1070 self.title = ''
1071
1072 # 'project' or 'all'; determines how the mrb (most recent builds)
1073 # section is displayed
1074 self.mrb_type = ''
1075
1076 def get_builds(self):
1077 """
1078 overridden in ProjectBuildsTable to return builds for a
1079 single project
1080 """
1081 return Build.objects.all()
1082
1083 def get_context_data(self, **kwargs):
1084 context = super(BuildsTable, self).get_context_data(**kwargs)
1085
1086 # should be set in subclasses
1087 context['mru'] = []
1088
1089 context['mrb_type'] = self.mrb_type
1090
1091 return context
1092
1093 def setup_queryset(self, *args, **kwargs):
1094 """
1095 The queryset is annotated so that it can be sorted by number of
1096 errors and number of warnings; but note that the criteria for
1097 finding the log messages to populate these fields should match those
1098 used in the Build model (orm/models.py) to populate the errors and
1099 warnings properties
1100 """
1101 queryset = self.get_builds()
1102
1103 # Don't include in progress builds pr cancelled builds
1104 queryset = queryset.exclude(Q(outcome=Build.IN_PROGRESS) |
1105 Q(outcome=Build.CANCELLED))
1106
1107 # sort
1108 queryset = queryset.order_by(self.default_orderby)
1109
1110 # annotate with number of ERROR, EXCEPTION and CRITICAL log messages
1111 criteria = (Q(logmessage__level=LogMessage.ERROR) |
1112 Q(logmessage__level=LogMessage.EXCEPTION) |
1113 Q(logmessage__level=LogMessage.CRITICAL))
1114
1115 queryset = queryset.annotate(
1116 errors_no=Count(
1117 Case(
1118 When(criteria, then=Value(1)),
1119 output_field=IntegerField()
1120 )
1121 )
1122 )
1123
1124 # annotate with number of WARNING log messages
1125 queryset = queryset.annotate(
1126 warnings_no=Count(
1127 Case(
1128 When(logmessage__level=LogMessage.WARNING, then=Value(1)),
1129 output_field=IntegerField()
1130 )
1131 )
1132 )
1133
1134 self.queryset = queryset
1135
1136 def setup_columns(self, *args, **kwargs):
1137 outcome_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001138 {% if data.outcome == data.SUCCEEDED %}
1139 <span class="glyphicon glyphicon-ok-circle"></span>
1140 {% elif data.outcome == data.FAILED %}
1141 <span class="glyphicon glyphicon-minus-sign"></span>
1142 {% endif %}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001143
1144 {% if data.cooker_log_path %}
1145 &nbsp;
1146 <a href="{% url "build_artifact" data.id "cookerlog" data.id %}">
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001147 <span class="glyphicon glyphicon-download-alt get-help"
1148 data-original-title="Download build log"></span>
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001149 </a>
1150 {% endif %}
1151 '''
1152
1153 recipe_template = '''
1154 {% for target_label in data.target_labels %}
1155 <a href="{% url "builddashboard" data.id %}">
1156 {{target_label}}
1157 </a>
1158 <br />
1159 {% endfor %}
1160 '''
1161
1162 machine_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001163 {{data.machine}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001164 '''
1165
1166 started_on_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001167 {{data.started_on | date:"d/m/y H:i"}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001168 '''
1169
1170 completed_on_template = '''
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001171 {{data.completed_on | date:"d/m/y H:i"}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001172 '''
1173
1174 failed_tasks_template = '''
1175 {% if data.failed_tasks.count == 1 %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001176 <a class="text-danger" href="{% url "task" data.id data.failed_tasks.0.id %}">
1177 <span>
1178 {{data.failed_tasks.0.recipe.name}} {{data.failed_tasks.0.task_name}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001179 </span>
1180 </a>
1181 <a href="{% url "build_artifact" data.id "tasklogfile" data.failed_tasks.0.id %}">
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001182 <span class="glyphicon glyphicon-download-alt get-help"
1183 title="Download task log file">
1184 </span>
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001185 </a>
1186 {% elif data.failed_tasks.count > 1 %}
1187 <a href="{% url "tasks" data.id %}?filter=outcome%3A{{extra.Task.OUTCOME_FAILED}}">
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001188 <span class="text-danger">{{data.failed_tasks.count}} tasks</span>
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001189 </a>
1190 {% endif %}
1191 '''
1192
1193 errors_template = '''
1194 {% if data.errors_no %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001195 <a class="errors.count text-danger" href="{% url "builddashboard" data.id %}#errors">
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001196 {{data.errors_no}} error{{data.errors_no|pluralize}}
1197 </a>
1198 {% endif %}
1199 '''
1200
1201 warnings_template = '''
1202 {% if data.warnings_no %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001203 <a class="warnings.count text-warning" href="{% url "builddashboard" data.id %}#warnings">
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001204 {{data.warnings_no}} warning{{data.warnings_no|pluralize}}
1205 </a>
1206 {% endif %}
1207 '''
1208
1209 time_template = '''
1210 {% load projecttags %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001211 {% if data.outcome == extra.Build.SUCCEEDED %}
1212 <a href="{% url "buildtime" data.id %}">
1213 {{data.timespent_seconds | sectohms}}
1214 </a>
1215 {% else %}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001216 {{data.timespent_seconds | sectohms}}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001217 {% endif %}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001218 '''
1219
1220 image_files_template = '''
1221 {% if data.outcome == extra.Build.SUCCEEDED %}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001222 {{data.get_image_file_extensions}}
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001223 {% endif %}
1224 '''
1225
1226 self.add_column(title='Outcome',
1227 help_text='Final state of the build (successful \
1228 or failed)',
1229 hideable=False,
1230 orderable=True,
1231 filter_name='outcome_filter',
1232 static_data_name='outcome',
1233 static_data_template=outcome_template)
1234
1235 self.add_column(title='Recipe',
1236 help_text='What was built (i.e. one or more recipes \
1237 or image recipes)',
1238 hideable=False,
1239 orderable=False,
1240 static_data_name='target',
1241 static_data_template=recipe_template)
1242
1243 self.add_column(title='Machine',
1244 help_text='Hardware for which you are building a \
1245 recipe or image recipe',
1246 hideable=False,
1247 orderable=True,
1248 static_data_name='machine',
1249 static_data_template=machine_template)
1250
1251 self.add_column(title='Started on',
1252 help_text='The date and time when the build started',
1253 hideable=True,
1254 hidden=True,
1255 orderable=True,
1256 filter_name='started_on_filter',
1257 static_data_name='started_on',
1258 static_data_template=started_on_template)
1259
1260 self.add_column(title='Completed on',
1261 help_text='The date and time when the build finished',
1262 hideable=False,
1263 orderable=True,
1264 filter_name='completed_on_filter',
1265 static_data_name='completed_on',
1266 static_data_template=completed_on_template)
1267
1268 self.add_column(title='Failed tasks',
1269 help_text='The number of tasks which failed during \
1270 the build',
1271 hideable=True,
1272 orderable=False,
1273 filter_name='failed_tasks_filter',
1274 static_data_name='failed_tasks',
1275 static_data_template=failed_tasks_template)
1276
1277 self.add_column(title='Errors',
1278 help_text='The number of errors encountered during \
1279 the build (if any)',
1280 hideable=True,
1281 orderable=True,
1282 static_data_name='errors_no',
1283 static_data_template=errors_template)
1284
1285 self.add_column(title='Warnings',
1286 help_text='The number of warnings encountered during \
1287 the build (if any)',
1288 hideable=True,
1289 orderable=True,
1290 static_data_name='warnings_no',
1291 static_data_template=warnings_template)
1292
1293 self.add_column(title='Time',
1294 help_text='How long the build took to finish',
1295 hideable=True,
1296 hidden=True,
1297 orderable=False,
1298 static_data_name='time',
1299 static_data_template=time_template)
1300
1301 self.add_column(title='Image files',
1302 help_text='The root file system types produced by \
1303 the build',
1304 hideable=True,
1305 orderable=False,
1306 static_data_name='image_files',
1307 static_data_template=image_files_template)
1308
1309 def setup_filters(self, *args, **kwargs):
1310 # outcomes
1311 outcome_filter = TableFilter(
1312 'outcome_filter',
1313 'Filter builds by outcome'
1314 )
1315
1316 successful_builds_action = TableFilterActionToggle(
1317 'successful_builds',
1318 'Successful builds',
1319 Q(outcome=Build.SUCCEEDED)
1320 )
1321
1322 failed_builds_action = TableFilterActionToggle(
1323 'failed_builds',
1324 'Failed builds',
1325 Q(outcome=Build.FAILED)
1326 )
1327
1328 outcome_filter.add_action(successful_builds_action)
1329 outcome_filter.add_action(failed_builds_action)
1330 self.add_filter(outcome_filter)
1331
1332 # started on
1333 started_on_filter = TableFilter(
1334 'started_on_filter',
1335 'Filter by date when build was started'
1336 )
1337
1338 started_today_action = TableFilterActionDay(
1339 'today',
1340 'Today\'s builds',
1341 'started_on',
1342 'today'
1343 )
1344
1345 started_yesterday_action = TableFilterActionDay(
1346 'yesterday',
1347 'Yesterday\'s builds',
1348 'started_on',
1349 'yesterday'
1350 )
1351
1352 by_started_date_range_action = TableFilterActionDateRange(
1353 'date_range',
1354 'Build date range',
1355 'started_on'
1356 )
1357
1358 started_on_filter.add_action(started_today_action)
1359 started_on_filter.add_action(started_yesterday_action)
1360 started_on_filter.add_action(by_started_date_range_action)
1361 self.add_filter(started_on_filter)
1362
1363 # completed on
1364 completed_on_filter = TableFilter(
1365 'completed_on_filter',
1366 'Filter by date when build was completed'
1367 )
1368
1369 completed_today_action = TableFilterActionDay(
1370 'today',
1371 'Today\'s builds',
1372 'completed_on',
1373 'today'
1374 )
1375
1376 completed_yesterday_action = TableFilterActionDay(
1377 'yesterday',
1378 'Yesterday\'s builds',
1379 'completed_on',
1380 'yesterday'
1381 )
1382
1383 by_completed_date_range_action = TableFilterActionDateRange(
1384 'date_range',
1385 'Build date range',
1386 'completed_on'
1387 )
1388
1389 completed_on_filter.add_action(completed_today_action)
1390 completed_on_filter.add_action(completed_yesterday_action)
1391 completed_on_filter.add_action(by_completed_date_range_action)
1392 self.add_filter(completed_on_filter)
1393
1394 # failed tasks
1395 failed_tasks_filter = TableFilter(
1396 'failed_tasks_filter',
1397 'Filter builds by failed tasks'
1398 )
1399
1400 criteria = Q(task_build__outcome=Task.OUTCOME_FAILED)
1401
1402 with_failed_tasks_action = TableFilterActionToggle(
1403 'with_failed_tasks',
1404 'Builds with failed tasks',
1405 criteria
1406 )
1407
1408 without_failed_tasks_action = TableFilterActionToggle(
1409 'without_failed_tasks',
1410 'Builds without failed tasks',
1411 ~criteria
1412 )
1413
1414 failed_tasks_filter.add_action(with_failed_tasks_action)
1415 failed_tasks_filter.add_action(without_failed_tasks_action)
1416 self.add_filter(failed_tasks_filter)
1417
1418
1419class AllBuildsTable(BuildsTable):
1420 """ Builds page for all builds """
1421
1422 def __init__(self, *args, **kwargs):
1423 super(AllBuildsTable, self).__init__(*args, **kwargs)
1424 self.title = 'All builds'
1425 self.mrb_type = 'all'
1426
1427 def setup_columns(self, *args, **kwargs):
1428 """
1429 All builds page shows a column for the project
1430 """
1431
1432 super(AllBuildsTable, self).setup_columns(*args, **kwargs)
1433
1434 project_template = '''
1435 {% load project_url_tag %}
1436 <a href="{% project_url data.project %}">
1437 {{data.project.name}}
1438 </a>
1439 {% if data.project.is_default %}
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001440 <span class="glyphicon glyphicon-question-sign get-help hover-help" title=""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001441 data-original-title="This project shows information about
1442 the builds you start from the command line while Toaster is
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001443 running" style="visibility: hidden;"></span>
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001444 {% endif %}
1445 '''
1446
1447 self.add_column(title='Project',
1448 hideable=True,
1449 orderable=True,
1450 static_data_name='project',
1451 static_data_template=project_template)
1452
1453 def get_context_data(self, **kwargs):
1454 """ Get all builds for the recent builds area """
1455 context = super(AllBuildsTable, self).get_context_data(**kwargs)
1456 context['mru'] = Build.get_recent()
1457 return context
1458
1459class ProjectBuildsTable(BuildsTable):
1460 """
1461 Builds page for a single project; a BuildsTable, with the queryset
1462 filtered by project
1463 """
1464
1465 def __init__(self, *args, **kwargs):
1466 super(ProjectBuildsTable, self).__init__(*args, **kwargs)
1467 self.title = 'All project builds'
1468 self.mrb_type = 'project'
1469
1470 # set from the querystring
1471 self.project_id = None
1472
1473 def setup_columns(self, *args, **kwargs):
1474 """
1475 Project builds table doesn't show the machines column by default
1476 """
1477
1478 super(ProjectBuildsTable, self).setup_columns(*args, **kwargs)
1479
1480 # hide the machine column
1481 self.set_column_hidden('Machine', True)
1482
1483 # allow the machine column to be hidden by the user
1484 self.set_column_hideable('Machine', True)
1485
1486 def setup_queryset(self, *args, **kwargs):
1487 """
1488 NOTE: self.project_id must be set before calling super(),
1489 as it's used in setup_queryset()
1490 """
1491 self.project_id = kwargs['pid']
1492 super(ProjectBuildsTable, self).setup_queryset(*args, **kwargs)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001493 project = Project.objects.get(pk=self.project_id)
1494 self.queryset = self.queryset.filter(project=project)
1495
1496 def get_context_data(self, **kwargs):
1497 """
1498 Get recent builds for this project, and the project itself
1499
1500 NOTE: self.project_id must be set before calling super(),
1501 as it's used in get_context_data()
1502 """
1503 self.project_id = kwargs['pid']
1504 context = super(ProjectBuildsTable, self).get_context_data(**kwargs)
1505
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001506 empty_state_template = '''
1507 This project has no builds.
1508 <a href="{% url 'projectimagerecipes' data.pid %}">
1509 Choose a recipe to build</a>
1510 '''
1511 context['empty_state'] = self.render_static_data(empty_state_template,
1512 kwargs)
1513
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001514 project = Project.objects.get(pk=self.project_id)
1515 context['mru'] = Build.get_recent(project)
1516 context['project'] = project
1517
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001518 self.setup_queryset(**kwargs)
1519 if self.queryset.count() == 0 and \
1520 project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0:
1521 context['build_in_progress_none_completed'] = True
1522 else:
1523 context['build_in_progress_none_completed'] = False
1524
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001525 return context
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001526
1527
1528class DistrosTable(ToasterTable):
1529 """Table of Distros in Toaster"""
1530
1531 def __init__(self, *args, **kwargs):
1532 super(DistrosTable, self).__init__(*args, **kwargs)
1533 self.empty_state = "Toaster has no distro information for this project. Sadly, distro information cannot be obtained from builds, so this page will remain empty."
1534 self.title = "Compatible Distros"
1535 self.default_orderby = "name"
1536
1537 def get_context_data(self, **kwargs):
1538 context = super(DistrosTable, self).get_context_data(**kwargs)
1539 context['project'] = Project.objects.get(pk=kwargs['pid'])
1540 return context
1541
1542 def setup_filters(self, *args, **kwargs):
1543 project = Project.objects.get(pk=kwargs['pid'])
1544
1545 in_current_project_filter = TableFilter(
1546 "in_current_project",
1547 "Filter by project Distros"
1548 )
1549
1550 in_project_action = TableFilterActionToggle(
1551 "in_project",
1552 "Distro provided by layers added to this project",
1553 ProjectFilters.in_project(self.project_layers)
1554 )
1555
1556 not_in_project_action = TableFilterActionToggle(
1557 "not_in_project",
1558 "Distros provided by layers not added to this project",
1559 ProjectFilters.not_in_project(self.project_layers)
1560 )
1561
1562 in_current_project_filter.add_action(in_project_action)
1563 in_current_project_filter.add_action(not_in_project_action)
1564 self.add_filter(in_current_project_filter)
1565
1566 def setup_queryset(self, *args, **kwargs):
1567 prj = Project.objects.get(pk = kwargs['pid'])
1568 self.queryset = prj.get_all_compatible_distros()
1569 self.queryset = self.queryset.order_by(self.default_orderby)
1570
1571 self.static_context_extra['current_layers'] = \
1572 self.project_layers = \
1573 prj.get_project_layer_versions(pk=True)
1574
1575 def setup_columns(self, *args, **kwargs):
1576
1577 self.add_column(title="Distro",
1578 hideable=False,
1579 orderable=True,
1580 field_name="name")
1581
1582 self.add_column(title="Description",
1583 field_name="description")
1584
1585 layer_link_template = '''
1586 <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}">
1587 {{data.layer_version.layer.name}}</a>
1588 '''
1589
1590 self.add_column(title="Layer",
1591 static_data_name="layer_version__layer__name",
1592 static_data_template=layer_link_template,
1593 orderable=True)
1594
1595 self.add_column(title="Git revision",
1596 help_text="The Git branch, tag or commit. For the layers from the OpenEmbedded layer source, the revision is always the branch compatible with the Yocto Project version you selected for this project",
1597 hidden=True,
1598 field_name="layer_version__get_vcs_reference")
1599
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001600 distro_file_template = '''<code>conf/distro/{{data.name}}.conf</code>
1601 {% if 'None' not in data.get_vcs_distro_file_link_url %}<a href="{{data.get_vcs_distro_file_link_url}}" target="_blank"><span class="glyphicon glyphicon-new-window"></i></a>{% endif %}'''
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001602 self.add_column(title="Distro file",
1603 hidden=True,
1604 static_data_name="templatefile",
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001605 static_data_template=distro_file_template)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001606
1607 self.add_column(title="Select",
1608 help_text="Sets the selected distro to the project",
1609 hideable=False,
1610 filter_name="in_current_project",
1611 static_data_name="add-del-layers",
1612 static_data_template='{% include "distro_btn.html" %}')
1613