1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# BitBake Toaster Implementation 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# Copyright (C) 2015 Intel Corporation 5*4882a593Smuzhiyun# 6*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 7*4882a593Smuzhiyun# 8*4882a593Smuzhiyun 9*4882a593Smuzhiyunfrom toastergui.widgets import ToasterTable 10*4882a593Smuzhiyunfrom orm.models import Recipe, ProjectLayer, Layer_Version, Project 11*4882a593Smuzhiyunfrom orm.models import CustomImageRecipe, Package, Target, Build, LogMessage, Task 12*4882a593Smuzhiyunfrom orm.models import CustomImagePackage, Package_DependencyManager 13*4882a593Smuzhiyunfrom django.db.models import Q, Sum, Count, When, Case, Value, IntegerField 14*4882a593Smuzhiyun 15*4882a593Smuzhiyunfrom toastergui.tablefilter import TableFilter 16*4882a593Smuzhiyunfrom toastergui.tablefilter import TableFilterActionToggle 17*4882a593Smuzhiyunfrom toastergui.tablefilter import TableFilterActionDateRange 18*4882a593Smuzhiyunfrom toastergui.tablefilter import TableFilterActionDay 19*4882a593Smuzhiyun 20*4882a593Smuzhiyunimport os 21*4882a593Smuzhiyun 22*4882a593Smuzhiyunclass ProjectFilters(object): 23*4882a593Smuzhiyun @staticmethod 24*4882a593Smuzhiyun def in_project(project_layers): 25*4882a593Smuzhiyun return Q(layer_version__in=project_layers) 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun @staticmethod 28*4882a593Smuzhiyun def not_in_project(project_layers): 29*4882a593Smuzhiyun return ~(ProjectFilters.in_project(project_layers)) 30*4882a593Smuzhiyun 31*4882a593Smuzhiyunclass LayersTable(ToasterTable): 32*4882a593Smuzhiyun """Table of layers in Toaster""" 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 35*4882a593Smuzhiyun super(LayersTable, self).__init__(*args, **kwargs) 36*4882a593Smuzhiyun self.default_orderby = "layer__name" 37*4882a593Smuzhiyun self.title = "Compatible layers" 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun def get_context_data(self, **kwargs): 40*4882a593Smuzhiyun context = super(LayersTable, self).get_context_data(**kwargs) 41*4882a593Smuzhiyun 42*4882a593Smuzhiyun project = Project.objects.get(pk=kwargs['pid']) 43*4882a593Smuzhiyun context['project'] = project 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun return context 46*4882a593Smuzhiyun 47*4882a593Smuzhiyun def setup_filters(self, *args, **kwargs): 48*4882a593Smuzhiyun project = Project.objects.get(pk=kwargs['pid']) 49*4882a593Smuzhiyun self.project_layers = ProjectLayer.objects.filter(project=project) 50*4882a593Smuzhiyun 51*4882a593Smuzhiyun in_current_project_filter = TableFilter( 52*4882a593Smuzhiyun "in_current_project", 53*4882a593Smuzhiyun "Filter by project layers" 54*4882a593Smuzhiyun ) 55*4882a593Smuzhiyun 56*4882a593Smuzhiyun criteria = Q(projectlayer__in=self.project_layers) 57*4882a593Smuzhiyun 58*4882a593Smuzhiyun in_project_action = TableFilterActionToggle( 59*4882a593Smuzhiyun "in_project", 60*4882a593Smuzhiyun "Layers added to this project", 61*4882a593Smuzhiyun criteria 62*4882a593Smuzhiyun ) 63*4882a593Smuzhiyun 64*4882a593Smuzhiyun not_in_project_action = TableFilterActionToggle( 65*4882a593Smuzhiyun "not_in_project", 66*4882a593Smuzhiyun "Layers not added to this project", 67*4882a593Smuzhiyun ~criteria 68*4882a593Smuzhiyun ) 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun in_current_project_filter.add_action(in_project_action) 71*4882a593Smuzhiyun in_current_project_filter.add_action(not_in_project_action) 72*4882a593Smuzhiyun self.add_filter(in_current_project_filter) 73*4882a593Smuzhiyun 74*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 75*4882a593Smuzhiyun prj = Project.objects.get(pk = kwargs['pid']) 76*4882a593Smuzhiyun compatible_layers = prj.get_all_compatible_layer_versions() 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun self.static_context_extra['current_layers'] = \ 79*4882a593Smuzhiyun prj.get_project_layer_versions(pk=True) 80*4882a593Smuzhiyun 81*4882a593Smuzhiyun self.queryset = compatible_layers.order_by(self.default_orderby) 82*4882a593Smuzhiyun 83*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun layer_link_template = ''' 86*4882a593Smuzhiyun <a href="{% url 'layerdetails' extra.pid data.id %}"> 87*4882a593Smuzhiyun {{data.layer.name}} 88*4882a593Smuzhiyun </a> 89*4882a593Smuzhiyun ''' 90*4882a593Smuzhiyun 91*4882a593Smuzhiyun self.add_column(title="Layer", 92*4882a593Smuzhiyun hideable=False, 93*4882a593Smuzhiyun orderable=True, 94*4882a593Smuzhiyun static_data_name="layer__name", 95*4882a593Smuzhiyun static_data_template=layer_link_template) 96*4882a593Smuzhiyun 97*4882a593Smuzhiyun self.add_column(title="Summary", 98*4882a593Smuzhiyun field_name="layer__summary") 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun git_url_template = ''' 101*4882a593Smuzhiyun <a href="{% url 'layerdetails' extra.pid data.id %}"> 102*4882a593Smuzhiyun {% if data.layer.local_source_dir %} 103*4882a593Smuzhiyun <code>{{data.layer.local_source_dir}}</code> 104*4882a593Smuzhiyun {% else %} 105*4882a593Smuzhiyun <code>{{data.layer.vcs_url}}</code> 106*4882a593Smuzhiyun </a> 107*4882a593Smuzhiyun {% endif %} 108*4882a593Smuzhiyun {% if data.get_vcs_link_url %} 109*4882a593Smuzhiyun <a target="_blank" href="{{ data.get_vcs_link_url }}"> 110*4882a593Smuzhiyun <span class="glyphicon glyphicon-new-window"></span> 111*4882a593Smuzhiyun </a> 112*4882a593Smuzhiyun {% endif %} 113*4882a593Smuzhiyun ''' 114*4882a593Smuzhiyun 115*4882a593Smuzhiyun self.add_column(title="Layer source code location", 116*4882a593Smuzhiyun help_text="A Git repository or an absolute path to a directory", 117*4882a593Smuzhiyun hidden=True, 118*4882a593Smuzhiyun static_data_name="layer__vcs_url", 119*4882a593Smuzhiyun static_data_template=git_url_template) 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun git_dir_template = ''' 122*4882a593Smuzhiyun {% if data.layer.local_source_dir %} 123*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 124*4882a593Smuzhiyun <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*4882a593Smuzhiyun {% else %} 126*4882a593Smuzhiyun <a href="{% url 'layerdetails' extra.pid data.id %}"> 127*4882a593Smuzhiyun <code>{{data.dirpath}}</code> 128*4882a593Smuzhiyun </a> 129*4882a593Smuzhiyun {% endif %} 130*4882a593Smuzhiyun {% if data.dirpath and data.get_vcs_dirpath_link_url %} 131*4882a593Smuzhiyun <a target="_blank" href="{{ data.get_vcs_dirpath_link_url }}"> 132*4882a593Smuzhiyun <span class="glyphicon glyphicon-new-window"></span> 133*4882a593Smuzhiyun </a> 134*4882a593Smuzhiyun {% endif %}''' 135*4882a593Smuzhiyun 136*4882a593Smuzhiyun self.add_column(title="Subdirectory", 137*4882a593Smuzhiyun help_text="The layer directory within the Git repository", 138*4882a593Smuzhiyun hidden=True, 139*4882a593Smuzhiyun static_data_name="git_subdir", 140*4882a593Smuzhiyun static_data_template=git_dir_template) 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun revision_template = ''' 143*4882a593Smuzhiyun {% if data.layer.local_source_dir %} 144*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 145*4882a593Smuzhiyun <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> 146*4882a593Smuzhiyun {% else %} 147*4882a593Smuzhiyun {% with vcs_ref=data.get_vcs_reference %} 148*4882a593Smuzhiyun {% include 'snippets/gitrev_popover.html' %} 149*4882a593Smuzhiyun {% endwith %} 150*4882a593Smuzhiyun {% endif %} 151*4882a593Smuzhiyun ''' 152*4882a593Smuzhiyun 153*4882a593Smuzhiyun self.add_column(title="Git revision", 154*4882a593Smuzhiyun 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*4882a593Smuzhiyun static_data_name="revision", 156*4882a593Smuzhiyun static_data_template=revision_template) 157*4882a593Smuzhiyun 158*4882a593Smuzhiyun deps_template = ''' 159*4882a593Smuzhiyun {% with ods=data.dependencies.all%} 160*4882a593Smuzhiyun {% if ods.count %} 161*4882a593Smuzhiyun <a class="btn btn-default" title="<a href='{% url "layerdetails" extra.pid data.id %}'>{{data.layer.name}}</a> dependencies" 162*4882a593Smuzhiyun data-content="<ul class='list-unstyled'> 163*4882a593Smuzhiyun {% for i in ods%} 164*4882a593Smuzhiyun <li><a href='{% url "layerdetails" extra.pid i.depends_on.pk %}'>{{i.depends_on.layer.name}}</a></li> 165*4882a593Smuzhiyun {% endfor %} 166*4882a593Smuzhiyun </ul>"> 167*4882a593Smuzhiyun {{ods.count}} 168*4882a593Smuzhiyun </a> 169*4882a593Smuzhiyun {% endif %} 170*4882a593Smuzhiyun {% endwith %} 171*4882a593Smuzhiyun ''' 172*4882a593Smuzhiyun 173*4882a593Smuzhiyun self.add_column(title="Dependencies", 174*4882a593Smuzhiyun help_text="Other layers a layer depends upon", 175*4882a593Smuzhiyun static_data_name="dependencies", 176*4882a593Smuzhiyun static_data_template=deps_template) 177*4882a593Smuzhiyun 178*4882a593Smuzhiyun self.add_column(title="Add | Remove", 179*4882a593Smuzhiyun help_text="Add or remove layers to / from your project", 180*4882a593Smuzhiyun hideable=False, 181*4882a593Smuzhiyun filter_name="in_current_project", 182*4882a593Smuzhiyun static_data_name="add-del-layers", 183*4882a593Smuzhiyun static_data_template='{% include "layer_btn.html" %}') 184*4882a593Smuzhiyun 185*4882a593Smuzhiyun 186*4882a593Smuzhiyunclass MachinesTable(ToasterTable): 187*4882a593Smuzhiyun """Table of Machines in Toaster""" 188*4882a593Smuzhiyun 189*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 190*4882a593Smuzhiyun super(MachinesTable, self).__init__(*args, **kwargs) 191*4882a593Smuzhiyun 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." 192*4882a593Smuzhiyun self.title = "Compatible machines" 193*4882a593Smuzhiyun self.default_orderby = "name" 194*4882a593Smuzhiyun 195*4882a593Smuzhiyun def get_context_data(self, **kwargs): 196*4882a593Smuzhiyun context = super(MachinesTable, self).get_context_data(**kwargs) 197*4882a593Smuzhiyun context['project'] = Project.objects.get(pk=kwargs['pid']) 198*4882a593Smuzhiyun return context 199*4882a593Smuzhiyun 200*4882a593Smuzhiyun def setup_filters(self, *args, **kwargs): 201*4882a593Smuzhiyun project = Project.objects.get(pk=kwargs['pid']) 202*4882a593Smuzhiyun 203*4882a593Smuzhiyun in_current_project_filter = TableFilter( 204*4882a593Smuzhiyun "in_current_project", 205*4882a593Smuzhiyun "Filter by project machines" 206*4882a593Smuzhiyun ) 207*4882a593Smuzhiyun 208*4882a593Smuzhiyun in_project_action = TableFilterActionToggle( 209*4882a593Smuzhiyun "in_project", 210*4882a593Smuzhiyun "Machines provided by layers added to this project", 211*4882a593Smuzhiyun ProjectFilters.in_project(self.project_layers) 212*4882a593Smuzhiyun ) 213*4882a593Smuzhiyun 214*4882a593Smuzhiyun not_in_project_action = TableFilterActionToggle( 215*4882a593Smuzhiyun "not_in_project", 216*4882a593Smuzhiyun "Machines provided by layers not added to this project", 217*4882a593Smuzhiyun ProjectFilters.not_in_project(self.project_layers) 218*4882a593Smuzhiyun ) 219*4882a593Smuzhiyun 220*4882a593Smuzhiyun in_current_project_filter.add_action(in_project_action) 221*4882a593Smuzhiyun in_current_project_filter.add_action(not_in_project_action) 222*4882a593Smuzhiyun self.add_filter(in_current_project_filter) 223*4882a593Smuzhiyun 224*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 225*4882a593Smuzhiyun prj = Project.objects.get(pk = kwargs['pid']) 226*4882a593Smuzhiyun self.queryset = prj.get_all_compatible_machines() 227*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 228*4882a593Smuzhiyun 229*4882a593Smuzhiyun self.static_context_extra['current_layers'] = \ 230*4882a593Smuzhiyun self.project_layers = \ 231*4882a593Smuzhiyun prj.get_project_layer_versions(pk=True) 232*4882a593Smuzhiyun 233*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 234*4882a593Smuzhiyun 235*4882a593Smuzhiyun self.add_column(title="Machine", 236*4882a593Smuzhiyun hideable=False, 237*4882a593Smuzhiyun orderable=True, 238*4882a593Smuzhiyun field_name="name") 239*4882a593Smuzhiyun 240*4882a593Smuzhiyun self.add_column(title="Description", 241*4882a593Smuzhiyun field_name="description") 242*4882a593Smuzhiyun 243*4882a593Smuzhiyun layer_link_template = ''' 244*4882a593Smuzhiyun <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}"> 245*4882a593Smuzhiyun {{data.layer_version.layer.name}}</a> 246*4882a593Smuzhiyun ''' 247*4882a593Smuzhiyun 248*4882a593Smuzhiyun self.add_column(title="Layer", 249*4882a593Smuzhiyun static_data_name="layer_version__layer__name", 250*4882a593Smuzhiyun static_data_template=layer_link_template, 251*4882a593Smuzhiyun orderable=True) 252*4882a593Smuzhiyun 253*4882a593Smuzhiyun self.add_column(title="Git revision", 254*4882a593Smuzhiyun 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*4882a593Smuzhiyun hidden=True, 256*4882a593Smuzhiyun field_name="layer_version__get_vcs_reference") 257*4882a593Smuzhiyun 258*4882a593Smuzhiyun machine_file_template = '''<code>conf/machine/{{data.name}}.conf</code> 259*4882a593Smuzhiyun <a href="{{data.get_vcs_machine_file_link_url}}" target="_blank"><span class="glyphicon glyphicon-new-window"></i></a>''' 260*4882a593Smuzhiyun 261*4882a593Smuzhiyun self.add_column(title="Machine file", 262*4882a593Smuzhiyun hidden=True, 263*4882a593Smuzhiyun static_data_name="machinefile", 264*4882a593Smuzhiyun static_data_template=machine_file_template) 265*4882a593Smuzhiyun 266*4882a593Smuzhiyun self.add_column(title="Select", 267*4882a593Smuzhiyun help_text="Sets the selected machine as the project machine. You can only have one machine per project", 268*4882a593Smuzhiyun hideable=False, 269*4882a593Smuzhiyun filter_name="in_current_project", 270*4882a593Smuzhiyun static_data_name="add-del-layers", 271*4882a593Smuzhiyun static_data_template='{% include "machine_btn.html" %}') 272*4882a593Smuzhiyun 273*4882a593Smuzhiyun 274*4882a593Smuzhiyunclass LayerMachinesTable(MachinesTable): 275*4882a593Smuzhiyun """ Smaller version of the Machines table for use in layer details """ 276*4882a593Smuzhiyun 277*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 278*4882a593Smuzhiyun super(LayerMachinesTable, self).__init__(*args, **kwargs) 279*4882a593Smuzhiyun 280*4882a593Smuzhiyun def get_context_data(self, **kwargs): 281*4882a593Smuzhiyun context = super(LayerMachinesTable, self).get_context_data(**kwargs) 282*4882a593Smuzhiyun context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid']) 283*4882a593Smuzhiyun return context 284*4882a593Smuzhiyun 285*4882a593Smuzhiyun 286*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 287*4882a593Smuzhiyun MachinesTable.setup_queryset(self, *args, **kwargs) 288*4882a593Smuzhiyun 289*4882a593Smuzhiyun self.queryset = self.queryset.filter(layer_version__pk=int(kwargs['layerid'])) 290*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 291*4882a593Smuzhiyun self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count() 292*4882a593Smuzhiyun 293*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 294*4882a593Smuzhiyun self.add_column(title="Machine", 295*4882a593Smuzhiyun hideable=False, 296*4882a593Smuzhiyun orderable=True, 297*4882a593Smuzhiyun field_name="name") 298*4882a593Smuzhiyun 299*4882a593Smuzhiyun self.add_column(title="Description", 300*4882a593Smuzhiyun field_name="description") 301*4882a593Smuzhiyun 302*4882a593Smuzhiyun select_btn_template = ''' 303*4882a593Smuzhiyun <a href="{% url "project" extra.pid %}?setMachine={{data.name}}" 304*4882a593Smuzhiyun class="btn btn-default btn-block select-machine-btn 305*4882a593Smuzhiyun {% if extra.in_prj == 0%}disabled{%endif%}">Select machine</a> 306*4882a593Smuzhiyun ''' 307*4882a593Smuzhiyun 308*4882a593Smuzhiyun self.add_column(title="Select machine", 309*4882a593Smuzhiyun static_data_name="add-del-layers", 310*4882a593Smuzhiyun static_data_template=select_btn_template) 311*4882a593Smuzhiyun 312*4882a593Smuzhiyun 313*4882a593Smuzhiyunclass RecipesTable(ToasterTable): 314*4882a593Smuzhiyun """Table of All Recipes in Toaster""" 315*4882a593Smuzhiyun 316*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 317*4882a593Smuzhiyun super(RecipesTable, self).__init__(*args, **kwargs) 318*4882a593Smuzhiyun self.empty_state = "Toaster has no recipe information. To generate recipe information you need to run a build." 319*4882a593Smuzhiyun 320*4882a593Smuzhiyun build_col = { 'title' : "Build", 321*4882a593Smuzhiyun 'help_text' : "Before building a recipe, you might need to add the corresponding layer to your project", 322*4882a593Smuzhiyun 'hideable' : False, 323*4882a593Smuzhiyun 'filter_name' : "in_current_project", 324*4882a593Smuzhiyun 'static_data_name' : "add-del-layers", 325*4882a593Smuzhiyun 'static_data_template' : '{% include "recipe_btn.html" %}'} 326*4882a593Smuzhiyun if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'): 327*4882a593Smuzhiyun build_col['static_data_template'] = '{% include "recipe_add_btn.html" %}' 328*4882a593Smuzhiyun 329*4882a593Smuzhiyun def get_context_data(self, **kwargs): 330*4882a593Smuzhiyun project = Project.objects.get(pk=kwargs['pid']) 331*4882a593Smuzhiyun context = super(RecipesTable, self).get_context_data(**kwargs) 332*4882a593Smuzhiyun 333*4882a593Smuzhiyun context['project'] = project 334*4882a593Smuzhiyun context['projectlayers'] = [player.layercommit.id for player in ProjectLayer.objects.filter(project=context['project'])] 335*4882a593Smuzhiyun 336*4882a593Smuzhiyun return context 337*4882a593Smuzhiyun 338*4882a593Smuzhiyun def setup_filters(self, *args, **kwargs): 339*4882a593Smuzhiyun table_filter = TableFilter( 340*4882a593Smuzhiyun 'in_current_project', 341*4882a593Smuzhiyun 'Filter by project recipes' 342*4882a593Smuzhiyun ) 343*4882a593Smuzhiyun 344*4882a593Smuzhiyun in_project_action = TableFilterActionToggle( 345*4882a593Smuzhiyun 'in_project', 346*4882a593Smuzhiyun 'Recipes provided by layers added to this project', 347*4882a593Smuzhiyun ProjectFilters.in_project(self.project_layers) 348*4882a593Smuzhiyun ) 349*4882a593Smuzhiyun 350*4882a593Smuzhiyun not_in_project_action = TableFilterActionToggle( 351*4882a593Smuzhiyun 'not_in_project', 352*4882a593Smuzhiyun 'Recipes provided by layers not added to this project', 353*4882a593Smuzhiyun ProjectFilters.not_in_project(self.project_layers) 354*4882a593Smuzhiyun ) 355*4882a593Smuzhiyun 356*4882a593Smuzhiyun table_filter.add_action(in_project_action) 357*4882a593Smuzhiyun table_filter.add_action(not_in_project_action) 358*4882a593Smuzhiyun self.add_filter(table_filter) 359*4882a593Smuzhiyun 360*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 361*4882a593Smuzhiyun prj = Project.objects.get(pk = kwargs['pid']) 362*4882a593Smuzhiyun 363*4882a593Smuzhiyun # Project layers used by the filters 364*4882a593Smuzhiyun self.project_layers = prj.get_project_layer_versions(pk=True) 365*4882a593Smuzhiyun 366*4882a593Smuzhiyun # Project layers used to switch the button states 367*4882a593Smuzhiyun self.static_context_extra['current_layers'] = self.project_layers 368*4882a593Smuzhiyun 369*4882a593Smuzhiyun self.queryset = prj.get_all_compatible_recipes() 370*4882a593Smuzhiyun 371*4882a593Smuzhiyun 372*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 373*4882a593Smuzhiyun 374*4882a593Smuzhiyun self.add_column(title="Version", 375*4882a593Smuzhiyun hidden=False, 376*4882a593Smuzhiyun field_name="version") 377*4882a593Smuzhiyun 378*4882a593Smuzhiyun self.add_column(title="Description", 379*4882a593Smuzhiyun field_name="get_description_or_summary") 380*4882a593Smuzhiyun 381*4882a593Smuzhiyun recipe_file_template = ''' 382*4882a593Smuzhiyun <code>{{data.file_path}}</code> 383*4882a593Smuzhiyun <a href="{{data.get_vcs_recipe_file_link_url}}" target="_blank"> 384*4882a593Smuzhiyun <span class="glyphicon glyphicon-new-window"></i> 385*4882a593Smuzhiyun </a> 386*4882a593Smuzhiyun ''' 387*4882a593Smuzhiyun 388*4882a593Smuzhiyun self.add_column(title="Recipe file", 389*4882a593Smuzhiyun help_text="Path to the recipe .bb file", 390*4882a593Smuzhiyun hidden=True, 391*4882a593Smuzhiyun static_data_name="recipe-file", 392*4882a593Smuzhiyun static_data_template=recipe_file_template) 393*4882a593Smuzhiyun 394*4882a593Smuzhiyun self.add_column(title="Section", 395*4882a593Smuzhiyun help_text="The section in which recipes should be categorized", 396*4882a593Smuzhiyun hidden=True, 397*4882a593Smuzhiyun orderable=True, 398*4882a593Smuzhiyun field_name="section") 399*4882a593Smuzhiyun 400*4882a593Smuzhiyun layer_link_template = ''' 401*4882a593Smuzhiyun <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}"> 402*4882a593Smuzhiyun {{data.layer_version.layer.name}}</a> 403*4882a593Smuzhiyun ''' 404*4882a593Smuzhiyun 405*4882a593Smuzhiyun self.add_column(title="Layer", 406*4882a593Smuzhiyun help_text="The name of the layer providing the recipe", 407*4882a593Smuzhiyun orderable=True, 408*4882a593Smuzhiyun static_data_name="layer_version__layer__name", 409*4882a593Smuzhiyun static_data_template=layer_link_template) 410*4882a593Smuzhiyun 411*4882a593Smuzhiyun self.add_column(title="License", 412*4882a593Smuzhiyun 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", 413*4882a593Smuzhiyun hidden=True, 414*4882a593Smuzhiyun orderable=True, 415*4882a593Smuzhiyun field_name="license") 416*4882a593Smuzhiyun 417*4882a593Smuzhiyun revision_link_template = ''' 418*4882a593Smuzhiyun {% if data.layer_version.layer.local_source_dir %} 419*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 420*4882a593Smuzhiyun <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*4882a593Smuzhiyun {% else %} 422*4882a593Smuzhiyun {{data.layer_version.get_vcs_reference}} 423*4882a593Smuzhiyun {% endif %} 424*4882a593Smuzhiyun ''' 425*4882a593Smuzhiyun 426*4882a593Smuzhiyun self.add_column(title="Git revision", 427*4882a593Smuzhiyun hidden=True, 428*4882a593Smuzhiyun static_data_name="layer_version__get_vcs_reference", 429*4882a593Smuzhiyun static_data_template=revision_link_template) 430*4882a593Smuzhiyun 431*4882a593Smuzhiyun 432*4882a593Smuzhiyunclass LayerRecipesTable(RecipesTable): 433*4882a593Smuzhiyun """ Smaller version of the Recipes table for use in layer details """ 434*4882a593Smuzhiyun 435*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 436*4882a593Smuzhiyun super(LayerRecipesTable, self).__init__(*args, **kwargs) 437*4882a593Smuzhiyun self.default_orderby = "name" 438*4882a593Smuzhiyun 439*4882a593Smuzhiyun def get_context_data(self, **kwargs): 440*4882a593Smuzhiyun context = super(LayerRecipesTable, self).get_context_data(**kwargs) 441*4882a593Smuzhiyun context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid']) 442*4882a593Smuzhiyun return context 443*4882a593Smuzhiyun 444*4882a593Smuzhiyun 445*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 446*4882a593Smuzhiyun self.queryset = \ 447*4882a593Smuzhiyun Recipe.objects.filter(layer_version__pk=int(kwargs['layerid'])) 448*4882a593Smuzhiyun 449*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 450*4882a593Smuzhiyun self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count() 451*4882a593Smuzhiyun 452*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 453*4882a593Smuzhiyun self.add_column(title="Recipe", 454*4882a593Smuzhiyun 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*4882a593Smuzhiyun hideable=False, 456*4882a593Smuzhiyun orderable=True, 457*4882a593Smuzhiyun field_name="name") 458*4882a593Smuzhiyun 459*4882a593Smuzhiyun self.add_column(title="Version", 460*4882a593Smuzhiyun field_name="version") 461*4882a593Smuzhiyun 462*4882a593Smuzhiyun self.add_column(title="Description", 463*4882a593Smuzhiyun field_name="get_description_or_summary") 464*4882a593Smuzhiyun 465*4882a593Smuzhiyun build_recipe_template = ''' 466*4882a593Smuzhiyun <a class="btn btn-default btn-block build-recipe-btn 467*4882a593Smuzhiyun {% if extra.in_prj == 0 %}disabled{% endif %}" 468*4882a593Smuzhiyun data-recipe-name="{{data.name}}">Build recipe</a> 469*4882a593Smuzhiyun ''' 470*4882a593Smuzhiyun 471*4882a593Smuzhiyun self.add_column(title="Build recipe", 472*4882a593Smuzhiyun static_data_name="add-del-layers", 473*4882a593Smuzhiyun static_data_template=build_recipe_template) 474*4882a593Smuzhiyun 475*4882a593Smuzhiyunclass CustomImagesTable(ToasterTable): 476*4882a593Smuzhiyun """ Table to display your custom images """ 477*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 478*4882a593Smuzhiyun super(CustomImagesTable, self).__init__(*args, **kwargs) 479*4882a593Smuzhiyun self.title = "Custom images" 480*4882a593Smuzhiyun self.default_orderby = "name" 481*4882a593Smuzhiyun 482*4882a593Smuzhiyun def get_context_data(self, **kwargs): 483*4882a593Smuzhiyun context = super(CustomImagesTable, self).get_context_data(**kwargs) 484*4882a593Smuzhiyun 485*4882a593Smuzhiyun empty_state_template = ''' 486*4882a593Smuzhiyun You have not created any custom images yet. 487*4882a593Smuzhiyun <a href="{% url 'newcustomimage' data.pid %}"> 488*4882a593Smuzhiyun Create your first custom image</a> 489*4882a593Smuzhiyun ''' 490*4882a593Smuzhiyun context['empty_state'] = self.render_static_data(empty_state_template, 491*4882a593Smuzhiyun kwargs) 492*4882a593Smuzhiyun project = Project.objects.get(pk=kwargs['pid']) 493*4882a593Smuzhiyun 494*4882a593Smuzhiyun # TODO put project into the ToasterTable base class 495*4882a593Smuzhiyun context['project'] = project 496*4882a593Smuzhiyun return context 497*4882a593Smuzhiyun 498*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 499*4882a593Smuzhiyun prj = Project.objects.get(pk = kwargs['pid']) 500*4882a593Smuzhiyun self.queryset = CustomImageRecipe.objects.filter(project=prj) 501*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 502*4882a593Smuzhiyun 503*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 504*4882a593Smuzhiyun 505*4882a593Smuzhiyun name_link_template = ''' 506*4882a593Smuzhiyun <a href="{% url 'customrecipe' extra.pid data.id %}"> 507*4882a593Smuzhiyun {{data.name}} 508*4882a593Smuzhiyun </a> 509*4882a593Smuzhiyun ''' 510*4882a593Smuzhiyun 511*4882a593Smuzhiyun self.add_column(title="Custom image", 512*4882a593Smuzhiyun hideable=False, 513*4882a593Smuzhiyun orderable=True, 514*4882a593Smuzhiyun field_name="name", 515*4882a593Smuzhiyun static_data_name="name", 516*4882a593Smuzhiyun static_data_template=name_link_template) 517*4882a593Smuzhiyun 518*4882a593Smuzhiyun recipe_file_template = ''' 519*4882a593Smuzhiyun {% if data.get_base_recipe_file %} 520*4882a593Smuzhiyun <code>{{data.name}}_{{data.version}}.bb</code> 521*4882a593Smuzhiyun <a href="{% url 'customrecipedownload' extra.pid data.pk %}" 522*4882a593Smuzhiyun class="glyphicon glyphicon-download-alt get-help" title="Download recipe file"></a> 523*4882a593Smuzhiyun {% endif %}''' 524*4882a593Smuzhiyun 525*4882a593Smuzhiyun self.add_column(title="Recipe file", 526*4882a593Smuzhiyun static_data_name='recipe_file_download', 527*4882a593Smuzhiyun static_data_template=recipe_file_template) 528*4882a593Smuzhiyun 529*4882a593Smuzhiyun approx_packages_template = ''' 530*4882a593Smuzhiyun {% if data.get_all_packages.count > 0 %} 531*4882a593Smuzhiyun <a href="{% url 'customrecipe' extra.pid data.id %}"> 532*4882a593Smuzhiyun {{data.get_all_packages.count}} 533*4882a593Smuzhiyun </a> 534*4882a593Smuzhiyun {% endif %}''' 535*4882a593Smuzhiyun 536*4882a593Smuzhiyun self.add_column(title="Packages", 537*4882a593Smuzhiyun static_data_name='approx_packages', 538*4882a593Smuzhiyun static_data_template=approx_packages_template) 539*4882a593Smuzhiyun 540*4882a593Smuzhiyun 541*4882a593Smuzhiyun build_btn_template = ''' 542*4882a593Smuzhiyun <button data-recipe-name="{{data.name}}" 543*4882a593Smuzhiyun class="btn btn-default btn-block build-recipe-btn"> 544*4882a593Smuzhiyun Build 545*4882a593Smuzhiyun </button>''' 546*4882a593Smuzhiyun 547*4882a593Smuzhiyun self.add_column(title="Build", 548*4882a593Smuzhiyun hideable=False, 549*4882a593Smuzhiyun static_data_name='build_custom_img', 550*4882a593Smuzhiyun static_data_template=build_btn_template) 551*4882a593Smuzhiyun 552*4882a593Smuzhiyunclass ImageRecipesTable(RecipesTable): 553*4882a593Smuzhiyun """ A subset of the recipes table which displayed just image recipes """ 554*4882a593Smuzhiyun 555*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 556*4882a593Smuzhiyun super(ImageRecipesTable, self).__init__(*args, **kwargs) 557*4882a593Smuzhiyun self.title = "Compatible image recipes" 558*4882a593Smuzhiyun self.default_orderby = "name" 559*4882a593Smuzhiyun 560*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 561*4882a593Smuzhiyun super(ImageRecipesTable, self).setup_queryset(*args, **kwargs) 562*4882a593Smuzhiyun 563*4882a593Smuzhiyun custom_image_recipes = CustomImageRecipe.objects.filter( 564*4882a593Smuzhiyun project=kwargs['pid']) 565*4882a593Smuzhiyun self.queryset = self.queryset.filter( 566*4882a593Smuzhiyun Q(is_image=True) & ~Q(pk__in=custom_image_recipes)) 567*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 568*4882a593Smuzhiyun 569*4882a593Smuzhiyun 570*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 571*4882a593Smuzhiyun 572*4882a593Smuzhiyun name_link_template = ''' 573*4882a593Smuzhiyun <a href="{% url 'recipedetails' extra.pid data.pk %}">{{data.name}}</a> 574*4882a593Smuzhiyun ''' 575*4882a593Smuzhiyun 576*4882a593Smuzhiyun self.add_column(title="Image recipe", 577*4882a593Smuzhiyun help_text="When you build an image recipe, you get an " 578*4882a593Smuzhiyun "image: a root file system you can" 579*4882a593Smuzhiyun "deploy to a machine", 580*4882a593Smuzhiyun hideable=False, 581*4882a593Smuzhiyun orderable=True, 582*4882a593Smuzhiyun static_data_name="name", 583*4882a593Smuzhiyun static_data_template=name_link_template, 584*4882a593Smuzhiyun field_name="name") 585*4882a593Smuzhiyun 586*4882a593Smuzhiyun super(ImageRecipesTable, self).setup_columns(*args, **kwargs) 587*4882a593Smuzhiyun 588*4882a593Smuzhiyun self.add_column(**RecipesTable.build_col) 589*4882a593Smuzhiyun 590*4882a593Smuzhiyun 591*4882a593Smuzhiyunclass NewCustomImagesTable(ImageRecipesTable): 592*4882a593Smuzhiyun """ Table which displays Images recipes which can be customised """ 593*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 594*4882a593Smuzhiyun super(NewCustomImagesTable, self).__init__(*args, **kwargs) 595*4882a593Smuzhiyun self.title = "Select the image recipe you want to customise" 596*4882a593Smuzhiyun 597*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 598*4882a593Smuzhiyun super(ImageRecipesTable, self).setup_queryset(*args, **kwargs) 599*4882a593Smuzhiyun prj = Project.objects.get(pk = kwargs['pid']) 600*4882a593Smuzhiyun self.static_context_extra['current_layers'] = \ 601*4882a593Smuzhiyun prj.get_project_layer_versions(pk=True) 602*4882a593Smuzhiyun 603*4882a593Smuzhiyun self.queryset = self.queryset.filter(is_image=True) 604*4882a593Smuzhiyun 605*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 606*4882a593Smuzhiyun 607*4882a593Smuzhiyun name_link_template = ''' 608*4882a593Smuzhiyun <a href="{% url 'recipedetails' extra.pid data.pk %}">{{data.name}}</a> 609*4882a593Smuzhiyun ''' 610*4882a593Smuzhiyun 611*4882a593Smuzhiyun self.add_column(title="Image recipe", 612*4882a593Smuzhiyun help_text="When you build an image recipe, you get an " 613*4882a593Smuzhiyun "image: a root file system you can" 614*4882a593Smuzhiyun "deploy to a machine", 615*4882a593Smuzhiyun hideable=False, 616*4882a593Smuzhiyun orderable=True, 617*4882a593Smuzhiyun static_data_name="name", 618*4882a593Smuzhiyun static_data_template=name_link_template, 619*4882a593Smuzhiyun field_name="name") 620*4882a593Smuzhiyun 621*4882a593Smuzhiyun super(ImageRecipesTable, self).setup_columns(*args, **kwargs) 622*4882a593Smuzhiyun 623*4882a593Smuzhiyun self.add_column(title="Customise", 624*4882a593Smuzhiyun hideable=False, 625*4882a593Smuzhiyun filter_name="in_current_project", 626*4882a593Smuzhiyun static_data_name="customise-or-add-recipe", 627*4882a593Smuzhiyun static_data_template='{% include "customise_btn.html" %}') 628*4882a593Smuzhiyun 629*4882a593Smuzhiyun 630*4882a593Smuzhiyunclass SoftwareRecipesTable(RecipesTable): 631*4882a593Smuzhiyun """ Displays just the software recipes """ 632*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 633*4882a593Smuzhiyun super(SoftwareRecipesTable, self).__init__(*args, **kwargs) 634*4882a593Smuzhiyun self.title = "Compatible software recipes" 635*4882a593Smuzhiyun self.default_orderby = "name" 636*4882a593Smuzhiyun 637*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 638*4882a593Smuzhiyun super(SoftwareRecipesTable, self).setup_queryset(*args, **kwargs) 639*4882a593Smuzhiyun 640*4882a593Smuzhiyun self.queryset = self.queryset.filter(is_image=False) 641*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 642*4882a593Smuzhiyun 643*4882a593Smuzhiyun 644*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 645*4882a593Smuzhiyun self.add_column(title="Software recipe", 646*4882a593Smuzhiyun help_text="Information about a single piece of " 647*4882a593Smuzhiyun "software, including where to download the source, " 648*4882a593Smuzhiyun "configuration options, how to compile the source " 649*4882a593Smuzhiyun "files and how to package the compiled output", 650*4882a593Smuzhiyun hideable=False, 651*4882a593Smuzhiyun orderable=True, 652*4882a593Smuzhiyun field_name="name") 653*4882a593Smuzhiyun 654*4882a593Smuzhiyun super(SoftwareRecipesTable, self).setup_columns(*args, **kwargs) 655*4882a593Smuzhiyun 656*4882a593Smuzhiyun self.add_column(**RecipesTable.build_col) 657*4882a593Smuzhiyun 658*4882a593Smuzhiyunclass PackagesTable(ToasterTable): 659*4882a593Smuzhiyun """ Table to display the packages in a recipe from it's last successful 660*4882a593Smuzhiyun build""" 661*4882a593Smuzhiyun 662*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 663*4882a593Smuzhiyun super(PackagesTable, self).__init__(*args, **kwargs) 664*4882a593Smuzhiyun self.title = "Packages included" 665*4882a593Smuzhiyun self.packages = None 666*4882a593Smuzhiyun self.default_orderby = "name" 667*4882a593Smuzhiyun 668*4882a593Smuzhiyun def create_package_list(self, recipe, project_id): 669*4882a593Smuzhiyun """Creates a list of packages for the specified recipe by looking for 670*4882a593Smuzhiyun the last SUCCEEDED build of ther recipe""" 671*4882a593Smuzhiyun 672*4882a593Smuzhiyun target = Target.objects.filter(Q(target=recipe.name) & 673*4882a593Smuzhiyun Q(build__project_id=project_id) & 674*4882a593Smuzhiyun Q(build__outcome=Build.SUCCEEDED) 675*4882a593Smuzhiyun ).last() 676*4882a593Smuzhiyun 677*4882a593Smuzhiyun if target: 678*4882a593Smuzhiyun pkgs = target.target_installed_package_set.values_list('package', 679*4882a593Smuzhiyun flat=True) 680*4882a593Smuzhiyun return Package.objects.filter(pk__in=pkgs) 681*4882a593Smuzhiyun 682*4882a593Smuzhiyun # Target/recipe never successfully built so empty queryset 683*4882a593Smuzhiyun return Package.objects.none() 684*4882a593Smuzhiyun 685*4882a593Smuzhiyun def get_context_data(self, **kwargs): 686*4882a593Smuzhiyun """Context for rendering the sidebar and other items on the recipe 687*4882a593Smuzhiyun details page """ 688*4882a593Smuzhiyun context = super(PackagesTable, self).get_context_data(**kwargs) 689*4882a593Smuzhiyun 690*4882a593Smuzhiyun recipe = Recipe.objects.get(pk=kwargs['recipe_id']) 691*4882a593Smuzhiyun project = Project.objects.get(pk=kwargs['pid']) 692*4882a593Smuzhiyun 693*4882a593Smuzhiyun in_project = (recipe.layer_version.pk in 694*4882a593Smuzhiyun project.get_project_layer_versions(pk=True)) 695*4882a593Smuzhiyun 696*4882a593Smuzhiyun packages = self.create_package_list(recipe, project.pk) 697*4882a593Smuzhiyun 698*4882a593Smuzhiyun context.update({'project': project, 699*4882a593Smuzhiyun 'recipe' : recipe, 700*4882a593Smuzhiyun 'packages': packages, 701*4882a593Smuzhiyun 'approx_pkg_size' : packages.aggregate(Sum('size')), 702*4882a593Smuzhiyun 'in_project' : in_project, 703*4882a593Smuzhiyun }) 704*4882a593Smuzhiyun 705*4882a593Smuzhiyun return context 706*4882a593Smuzhiyun 707*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 708*4882a593Smuzhiyun recipe = Recipe.objects.get(pk=kwargs['recipe_id']) 709*4882a593Smuzhiyun self.static_context_extra['target_name'] = recipe.name 710*4882a593Smuzhiyun 711*4882a593Smuzhiyun self.queryset = self.create_package_list(recipe, kwargs['pid']) 712*4882a593Smuzhiyun self.queryset = self.queryset.order_by('name') 713*4882a593Smuzhiyun 714*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 715*4882a593Smuzhiyun self.add_column(title="Package", 716*4882a593Smuzhiyun hideable=False, 717*4882a593Smuzhiyun orderable=True, 718*4882a593Smuzhiyun field_name="name") 719*4882a593Smuzhiyun 720*4882a593Smuzhiyun self.add_column(title="Package Version", 721*4882a593Smuzhiyun field_name="version", 722*4882a593Smuzhiyun hideable=False) 723*4882a593Smuzhiyun 724*4882a593Smuzhiyun self.add_column(title="Approx Size", 725*4882a593Smuzhiyun orderable=True, 726*4882a593Smuzhiyun field_name="size", 727*4882a593Smuzhiyun static_data_name="size", 728*4882a593Smuzhiyun static_data_template="{% load projecttags %} \ 729*4882a593Smuzhiyun {{data.size|filtered_filesizeformat}}") 730*4882a593Smuzhiyun 731*4882a593Smuzhiyun self.add_column(title="License", 732*4882a593Smuzhiyun field_name="license", 733*4882a593Smuzhiyun orderable=True, 734*4882a593Smuzhiyun hidden=True) 735*4882a593Smuzhiyun 736*4882a593Smuzhiyun 737*4882a593Smuzhiyun self.add_column(title="Dependencies", 738*4882a593Smuzhiyun static_data_name="dependencies", 739*4882a593Smuzhiyun static_data_template='\ 740*4882a593Smuzhiyun {% include "snippets/pkg_dependencies_popover.html" %}') 741*4882a593Smuzhiyun 742*4882a593Smuzhiyun self.add_column(title="Reverse dependencies", 743*4882a593Smuzhiyun static_data_name="reverse_dependencies", 744*4882a593Smuzhiyun static_data_template='\ 745*4882a593Smuzhiyun {% include "snippets/pkg_revdependencies_popover.html" %}', 746*4882a593Smuzhiyun hidden=True) 747*4882a593Smuzhiyun 748*4882a593Smuzhiyun self.add_column(title="Recipe", 749*4882a593Smuzhiyun field_name="recipe__name", 750*4882a593Smuzhiyun orderable=True, 751*4882a593Smuzhiyun hidden=True) 752*4882a593Smuzhiyun 753*4882a593Smuzhiyun self.add_column(title="Recipe version", 754*4882a593Smuzhiyun field_name="recipe__version", 755*4882a593Smuzhiyun hidden=True) 756*4882a593Smuzhiyun 757*4882a593Smuzhiyun 758*4882a593Smuzhiyunclass SelectPackagesTable(PackagesTable): 759*4882a593Smuzhiyun """ Table to display the packages to add and remove from an image """ 760*4882a593Smuzhiyun 761*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 762*4882a593Smuzhiyun super(SelectPackagesTable, self).__init__(*args, **kwargs) 763*4882a593Smuzhiyun self.title = "Add | Remove packages" 764*4882a593Smuzhiyun 765*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 766*4882a593Smuzhiyun self.cust_recipe =\ 767*4882a593Smuzhiyun CustomImageRecipe.objects.get(pk=kwargs['custrecipeid']) 768*4882a593Smuzhiyun prj = Project.objects.get(pk = kwargs['pid']) 769*4882a593Smuzhiyun 770*4882a593Smuzhiyun current_packages = self.cust_recipe.get_all_packages() 771*4882a593Smuzhiyun 772*4882a593Smuzhiyun current_recipes = prj.get_available_recipes() 773*4882a593Smuzhiyun 774*4882a593Smuzhiyun # only show packages where recipes->layers are in the project 775*4882a593Smuzhiyun self.queryset = CustomImagePackage.objects.filter( 776*4882a593Smuzhiyun ~Q(recipe=None) & 777*4882a593Smuzhiyun Q(recipe__in=current_recipes)) 778*4882a593Smuzhiyun 779*4882a593Smuzhiyun self.queryset = self.queryset.order_by('name') 780*4882a593Smuzhiyun 781*4882a593Smuzhiyun # This target is the target used to work out which group of dependences 782*4882a593Smuzhiyun # to display, if we've built the custom image we use it otherwise we 783*4882a593Smuzhiyun # can use the based recipe instead 784*4882a593Smuzhiyun if prj.build_set.filter(target__target=self.cust_recipe.name).count()\ 785*4882a593Smuzhiyun > 0: 786*4882a593Smuzhiyun self.static_context_extra['target_name'] = self.cust_recipe.name 787*4882a593Smuzhiyun else: 788*4882a593Smuzhiyun self.static_context_extra['target_name'] =\ 789*4882a593Smuzhiyun Package_DependencyManager.TARGET_LATEST 790*4882a593Smuzhiyun 791*4882a593Smuzhiyun self.static_context_extra['recipe_id'] = kwargs['custrecipeid'] 792*4882a593Smuzhiyun 793*4882a593Smuzhiyun 794*4882a593Smuzhiyun self.static_context_extra['current_packages'] = \ 795*4882a593Smuzhiyun current_packages.values_list('pk', flat=True) 796*4882a593Smuzhiyun 797*4882a593Smuzhiyun def get_context_data(self, **kwargs): 798*4882a593Smuzhiyun # to reuse the Super class map the custrecipeid to the recipe_id 799*4882a593Smuzhiyun kwargs['recipe_id'] = kwargs['custrecipeid'] 800*4882a593Smuzhiyun context = super(SelectPackagesTable, self).get_context_data(**kwargs) 801*4882a593Smuzhiyun custom_recipe = \ 802*4882a593Smuzhiyun CustomImageRecipe.objects.get(pk=kwargs['custrecipeid']) 803*4882a593Smuzhiyun 804*4882a593Smuzhiyun context['recipe'] = custom_recipe 805*4882a593Smuzhiyun context['approx_pkg_size'] = \ 806*4882a593Smuzhiyun custom_recipe.get_all_packages().aggregate(Sum('size')) 807*4882a593Smuzhiyun return context 808*4882a593Smuzhiyun 809*4882a593Smuzhiyun 810*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 811*4882a593Smuzhiyun super(SelectPackagesTable, self).setup_columns(*args, **kwargs) 812*4882a593Smuzhiyun 813*4882a593Smuzhiyun add_remove_template = '{% include "pkg_add_rm_btn.html" %}' 814*4882a593Smuzhiyun 815*4882a593Smuzhiyun self.add_column(title="Add | Remove", 816*4882a593Smuzhiyun hideable=False, 817*4882a593Smuzhiyun help_text="Use the add and remove buttons to modify " 818*4882a593Smuzhiyun "the package content of your custom image", 819*4882a593Smuzhiyun static_data_name="add_rm_pkg_btn", 820*4882a593Smuzhiyun static_data_template=add_remove_template, 821*4882a593Smuzhiyun filter_name='in_current_image_filter') 822*4882a593Smuzhiyun 823*4882a593Smuzhiyun def setup_filters(self, *args, **kwargs): 824*4882a593Smuzhiyun in_current_image_filter = TableFilter( 825*4882a593Smuzhiyun 'in_current_image_filter', 826*4882a593Smuzhiyun 'Filter by added packages' 827*4882a593Smuzhiyun ) 828*4882a593Smuzhiyun 829*4882a593Smuzhiyun in_image_action = TableFilterActionToggle( 830*4882a593Smuzhiyun 'in_image', 831*4882a593Smuzhiyun 'Packages in %s' % self.cust_recipe.name, 832*4882a593Smuzhiyun Q(pk__in=self.static_context_extra['current_packages']) 833*4882a593Smuzhiyun ) 834*4882a593Smuzhiyun 835*4882a593Smuzhiyun not_in_image_action = TableFilterActionToggle( 836*4882a593Smuzhiyun 'not_in_image', 837*4882a593Smuzhiyun 'Packages not added to %s' % self.cust_recipe.name, 838*4882a593Smuzhiyun ~Q(pk__in=self.static_context_extra['current_packages']) 839*4882a593Smuzhiyun ) 840*4882a593Smuzhiyun 841*4882a593Smuzhiyun in_current_image_filter.add_action(in_image_action) 842*4882a593Smuzhiyun in_current_image_filter.add_action(not_in_image_action) 843*4882a593Smuzhiyun self.add_filter(in_current_image_filter) 844*4882a593Smuzhiyun 845*4882a593Smuzhiyunclass ProjectsTable(ToasterTable): 846*4882a593Smuzhiyun """Table of projects in Toaster""" 847*4882a593Smuzhiyun 848*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 849*4882a593Smuzhiyun super(ProjectsTable, self).__init__(*args, **kwargs) 850*4882a593Smuzhiyun self.default_orderby = '-updated' 851*4882a593Smuzhiyun self.title = 'All projects' 852*4882a593Smuzhiyun self.static_context_extra['Build'] = Build 853*4882a593Smuzhiyun 854*4882a593Smuzhiyun def get_context_data(self, **kwargs): 855*4882a593Smuzhiyun return super(ProjectsTable, self).get_context_data(**kwargs) 856*4882a593Smuzhiyun 857*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 858*4882a593Smuzhiyun queryset = Project.objects.all() 859*4882a593Smuzhiyun 860*4882a593Smuzhiyun # annotate each project with its number of builds 861*4882a593Smuzhiyun queryset = queryset.annotate(num_builds=Count('build')) 862*4882a593Smuzhiyun 863*4882a593Smuzhiyun # exclude the command line builds project if it has no builds 864*4882a593Smuzhiyun q_default_with_builds = Q(is_default=True) & Q(num_builds__gt=0) 865*4882a593Smuzhiyun queryset = queryset.filter(Q(is_default=False) | 866*4882a593Smuzhiyun q_default_with_builds) 867*4882a593Smuzhiyun 868*4882a593Smuzhiyun # order rows 869*4882a593Smuzhiyun queryset = queryset.order_by(self.default_orderby) 870*4882a593Smuzhiyun 871*4882a593Smuzhiyun self.queryset = queryset 872*4882a593Smuzhiyun 873*4882a593Smuzhiyun # columns: last activity on (updated) - DEFAULT, project (name), release, 874*4882a593Smuzhiyun # machine, number of builds, last build outcome, recipe (name), errors, 875*4882a593Smuzhiyun # warnings, image files 876*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 877*4882a593Smuzhiyun name_template = ''' 878*4882a593Smuzhiyun {% load project_url_tag %} 879*4882a593Smuzhiyun <span data-project-field="name"> 880*4882a593Smuzhiyun <a href="{% project_url data %}"> 881*4882a593Smuzhiyun {{data.name}} 882*4882a593Smuzhiyun </a> 883*4882a593Smuzhiyun </span> 884*4882a593Smuzhiyun ''' 885*4882a593Smuzhiyun 886*4882a593Smuzhiyun last_activity_on_template = ''' 887*4882a593Smuzhiyun {% load project_url_tag %} 888*4882a593Smuzhiyun <span data-project-field="updated"> 889*4882a593Smuzhiyun {{data.updated | date:"d/m/y H:i"}} 890*4882a593Smuzhiyun </span> 891*4882a593Smuzhiyun ''' 892*4882a593Smuzhiyun 893*4882a593Smuzhiyun release_template = ''' 894*4882a593Smuzhiyun <span data-project-field="release"> 895*4882a593Smuzhiyun {% if data.release %} 896*4882a593Smuzhiyun {{data.release.name}} 897*4882a593Smuzhiyun {% elif data.is_default %} 898*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 899*4882a593Smuzhiyun <span class="glyphicon glyphicon-question-sign get-help hover-help" 900*4882a593Smuzhiyun title="This project does not have a release set. 901*4882a593Smuzhiyun It simply collects information about the builds you start from 902*4882a593Smuzhiyun the command line while Toaster is running" 903*4882a593Smuzhiyun style="visibility: hidden;"> 904*4882a593Smuzhiyun </span> 905*4882a593Smuzhiyun {% else %} 906*4882a593Smuzhiyun No release available 907*4882a593Smuzhiyun {% endif %} 908*4882a593Smuzhiyun </span> 909*4882a593Smuzhiyun ''' 910*4882a593Smuzhiyun 911*4882a593Smuzhiyun machine_template = ''' 912*4882a593Smuzhiyun <span data-project-field="machine"> 913*4882a593Smuzhiyun {% if data.is_default %} 914*4882a593Smuzhiyun <span class="text-muted">Not applicable</span> 915*4882a593Smuzhiyun <span class="glyphicon glyphicon-question-sign get-help hover-help" 916*4882a593Smuzhiyun title="This project does not have a machine 917*4882a593Smuzhiyun set. It simply collects information about the builds you 918*4882a593Smuzhiyun start from the command line while Toaster is running" 919*4882a593Smuzhiyun style="visibility: hidden;"></span> 920*4882a593Smuzhiyun {% else %} 921*4882a593Smuzhiyun {{data.get_current_machine_name}} 922*4882a593Smuzhiyun {% endif %} 923*4882a593Smuzhiyun </span> 924*4882a593Smuzhiyun ''' 925*4882a593Smuzhiyun 926*4882a593Smuzhiyun number_of_builds_template = ''' 927*4882a593Smuzhiyun {% if data.get_number_of_builds > 0 %} 928*4882a593Smuzhiyun <a href="{% url 'projectbuilds' data.id %}"> 929*4882a593Smuzhiyun {{data.get_number_of_builds}} 930*4882a593Smuzhiyun </a> 931*4882a593Smuzhiyun {% endif %} 932*4882a593Smuzhiyun ''' 933*4882a593Smuzhiyun 934*4882a593Smuzhiyun last_build_outcome_template = ''' 935*4882a593Smuzhiyun {% if data.get_number_of_builds > 0 %} 936*4882a593Smuzhiyun {% if data.get_last_outcome == extra.Build.SUCCEEDED %} 937*4882a593Smuzhiyun <span class="glyphicon glyphicon-ok-circle"></span> 938*4882a593Smuzhiyun {% elif data.get_last_outcome == extra.Build.FAILED %} 939*4882a593Smuzhiyun <span class="glyphicon glyphicon-minus-sign"></span> 940*4882a593Smuzhiyun {% endif %} 941*4882a593Smuzhiyun {% endif %} 942*4882a593Smuzhiyun ''' 943*4882a593Smuzhiyun 944*4882a593Smuzhiyun recipe_template = ''' 945*4882a593Smuzhiyun {% if data.get_number_of_builds > 0 %} 946*4882a593Smuzhiyun <a href="{% url "builddashboard" data.get_last_build_id %}"> 947*4882a593Smuzhiyun {{data.get_last_target}} 948*4882a593Smuzhiyun </a> 949*4882a593Smuzhiyun {% endif %} 950*4882a593Smuzhiyun ''' 951*4882a593Smuzhiyun 952*4882a593Smuzhiyun errors_template = ''' 953*4882a593Smuzhiyun {% if data.get_number_of_builds > 0 and data.get_last_errors > 0 %} 954*4882a593Smuzhiyun <a class="errors.count text-danger" 955*4882a593Smuzhiyun href="{% url "builddashboard" data.get_last_build_id %}#errors"> 956*4882a593Smuzhiyun {{data.get_last_errors}} error{{data.get_last_errors | pluralize}} 957*4882a593Smuzhiyun </a> 958*4882a593Smuzhiyun {% endif %} 959*4882a593Smuzhiyun ''' 960*4882a593Smuzhiyun 961*4882a593Smuzhiyun warnings_template = ''' 962*4882a593Smuzhiyun {% if data.get_number_of_builds > 0 and data.get_last_warnings > 0 %} 963*4882a593Smuzhiyun <a class="warnings.count text-warning" 964*4882a593Smuzhiyun href="{% url "builddashboard" data.get_last_build_id %}#warnings"> 965*4882a593Smuzhiyun {{data.get_last_warnings}} warning{{data.get_last_warnings | pluralize}} 966*4882a593Smuzhiyun </a> 967*4882a593Smuzhiyun {% endif %} 968*4882a593Smuzhiyun ''' 969*4882a593Smuzhiyun 970*4882a593Smuzhiyun image_files_template = ''' 971*4882a593Smuzhiyun {% if data.get_number_of_builds > 0 and data.get_last_outcome == extra.Build.SUCCEEDED %} 972*4882a593Smuzhiyun {{data.get_last_build_extensions}} 973*4882a593Smuzhiyun {% endif %} 974*4882a593Smuzhiyun ''' 975*4882a593Smuzhiyun 976*4882a593Smuzhiyun self.add_column(title='Project', 977*4882a593Smuzhiyun hideable=False, 978*4882a593Smuzhiyun orderable=True, 979*4882a593Smuzhiyun static_data_name='name', 980*4882a593Smuzhiyun static_data_template=name_template) 981*4882a593Smuzhiyun 982*4882a593Smuzhiyun self.add_column(title='Last activity on', 983*4882a593Smuzhiyun help_text='Starting date and time of the \ 984*4882a593Smuzhiyun last project build. If the project has no \ 985*4882a593Smuzhiyun builds, this shows the date the project was \ 986*4882a593Smuzhiyun created.', 987*4882a593Smuzhiyun hideable=False, 988*4882a593Smuzhiyun orderable=True, 989*4882a593Smuzhiyun static_data_name='updated', 990*4882a593Smuzhiyun static_data_template=last_activity_on_template) 991*4882a593Smuzhiyun 992*4882a593Smuzhiyun self.add_column(title='Release', 993*4882a593Smuzhiyun help_text='The version of the build system used by \ 994*4882a593Smuzhiyun the project', 995*4882a593Smuzhiyun hideable=False, 996*4882a593Smuzhiyun orderable=True, 997*4882a593Smuzhiyun static_data_name='release', 998*4882a593Smuzhiyun static_data_template=release_template) 999*4882a593Smuzhiyun 1000*4882a593Smuzhiyun self.add_column(title='Machine', 1001*4882a593Smuzhiyun help_text='The hardware currently selected for the \ 1002*4882a593Smuzhiyun project', 1003*4882a593Smuzhiyun hideable=False, 1004*4882a593Smuzhiyun orderable=False, 1005*4882a593Smuzhiyun static_data_name='machine', 1006*4882a593Smuzhiyun static_data_template=machine_template) 1007*4882a593Smuzhiyun 1008*4882a593Smuzhiyun self.add_column(title='Builds', 1009*4882a593Smuzhiyun help_text='The number of builds which have been run \ 1010*4882a593Smuzhiyun for the project', 1011*4882a593Smuzhiyun hideable=False, 1012*4882a593Smuzhiyun orderable=False, 1013*4882a593Smuzhiyun static_data_name='number_of_builds', 1014*4882a593Smuzhiyun static_data_template=number_of_builds_template) 1015*4882a593Smuzhiyun 1016*4882a593Smuzhiyun self.add_column(title='Last build outcome', 1017*4882a593Smuzhiyun help_text='Indicates whether the last project build \ 1018*4882a593Smuzhiyun completed successfully or failed', 1019*4882a593Smuzhiyun hideable=True, 1020*4882a593Smuzhiyun orderable=False, 1021*4882a593Smuzhiyun static_data_name='last_build_outcome', 1022*4882a593Smuzhiyun static_data_template=last_build_outcome_template) 1023*4882a593Smuzhiyun 1024*4882a593Smuzhiyun self.add_column(title='Recipe', 1025*4882a593Smuzhiyun help_text='The last recipe which was built in this \ 1026*4882a593Smuzhiyun project', 1027*4882a593Smuzhiyun hideable=True, 1028*4882a593Smuzhiyun orderable=False, 1029*4882a593Smuzhiyun static_data_name='recipe_name', 1030*4882a593Smuzhiyun static_data_template=recipe_template) 1031*4882a593Smuzhiyun 1032*4882a593Smuzhiyun self.add_column(title='Errors', 1033*4882a593Smuzhiyun help_text='The number of errors encountered during \ 1034*4882a593Smuzhiyun the last project build (if any)', 1035*4882a593Smuzhiyun hideable=True, 1036*4882a593Smuzhiyun orderable=False, 1037*4882a593Smuzhiyun static_data_name='errors', 1038*4882a593Smuzhiyun static_data_template=errors_template) 1039*4882a593Smuzhiyun 1040*4882a593Smuzhiyun self.add_column(title='Warnings', 1041*4882a593Smuzhiyun help_text='The number of warnings encountered during \ 1042*4882a593Smuzhiyun the last project build (if any)', 1043*4882a593Smuzhiyun hideable=True, 1044*4882a593Smuzhiyun hidden=True, 1045*4882a593Smuzhiyun orderable=False, 1046*4882a593Smuzhiyun static_data_name='warnings', 1047*4882a593Smuzhiyun static_data_template=warnings_template) 1048*4882a593Smuzhiyun 1049*4882a593Smuzhiyun self.add_column(title='Image files', 1050*4882a593Smuzhiyun help_text='The root file system types produced by \ 1051*4882a593Smuzhiyun the last project build', 1052*4882a593Smuzhiyun hideable=True, 1053*4882a593Smuzhiyun hidden=True, 1054*4882a593Smuzhiyun orderable=False, 1055*4882a593Smuzhiyun static_data_name='image_files', 1056*4882a593Smuzhiyun static_data_template=image_files_template) 1057*4882a593Smuzhiyun 1058*4882a593Smuzhiyunclass BuildsTable(ToasterTable): 1059*4882a593Smuzhiyun """Table of builds in Toaster""" 1060*4882a593Smuzhiyun 1061*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 1062*4882a593Smuzhiyun super(BuildsTable, self).__init__(*args, **kwargs) 1063*4882a593Smuzhiyun self.default_orderby = '-completed_on' 1064*4882a593Smuzhiyun self.static_context_extra['Build'] = Build 1065*4882a593Smuzhiyun self.static_context_extra['Task'] = Task 1066*4882a593Smuzhiyun 1067*4882a593Smuzhiyun # attributes that are overridden in subclasses 1068*4882a593Smuzhiyun 1069*4882a593Smuzhiyun # title for the page 1070*4882a593Smuzhiyun self.title = '' 1071*4882a593Smuzhiyun 1072*4882a593Smuzhiyun # 'project' or 'all'; determines how the mrb (most recent builds) 1073*4882a593Smuzhiyun # section is displayed 1074*4882a593Smuzhiyun self.mrb_type = '' 1075*4882a593Smuzhiyun 1076*4882a593Smuzhiyun def get_builds(self): 1077*4882a593Smuzhiyun """ 1078*4882a593Smuzhiyun overridden in ProjectBuildsTable to return builds for a 1079*4882a593Smuzhiyun single project 1080*4882a593Smuzhiyun """ 1081*4882a593Smuzhiyun return Build.objects.all() 1082*4882a593Smuzhiyun 1083*4882a593Smuzhiyun def get_context_data(self, **kwargs): 1084*4882a593Smuzhiyun context = super(BuildsTable, self).get_context_data(**kwargs) 1085*4882a593Smuzhiyun 1086*4882a593Smuzhiyun # should be set in subclasses 1087*4882a593Smuzhiyun context['mru'] = [] 1088*4882a593Smuzhiyun 1089*4882a593Smuzhiyun context['mrb_type'] = self.mrb_type 1090*4882a593Smuzhiyun 1091*4882a593Smuzhiyun return context 1092*4882a593Smuzhiyun 1093*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 1094*4882a593Smuzhiyun """ 1095*4882a593Smuzhiyun The queryset is annotated so that it can be sorted by number of 1096*4882a593Smuzhiyun errors and number of warnings; but note that the criteria for 1097*4882a593Smuzhiyun finding the log messages to populate these fields should match those 1098*4882a593Smuzhiyun used in the Build model (orm/models.py) to populate the errors and 1099*4882a593Smuzhiyun warnings properties 1100*4882a593Smuzhiyun """ 1101*4882a593Smuzhiyun queryset = self.get_builds() 1102*4882a593Smuzhiyun 1103*4882a593Smuzhiyun # Don't include in progress builds pr cancelled builds 1104*4882a593Smuzhiyun queryset = queryset.exclude(Q(outcome=Build.IN_PROGRESS) | 1105*4882a593Smuzhiyun Q(outcome=Build.CANCELLED)) 1106*4882a593Smuzhiyun 1107*4882a593Smuzhiyun # sort 1108*4882a593Smuzhiyun queryset = queryset.order_by(self.default_orderby) 1109*4882a593Smuzhiyun 1110*4882a593Smuzhiyun # annotate with number of ERROR, EXCEPTION and CRITICAL log messages 1111*4882a593Smuzhiyun criteria = (Q(logmessage__level=LogMessage.ERROR) | 1112*4882a593Smuzhiyun Q(logmessage__level=LogMessage.EXCEPTION) | 1113*4882a593Smuzhiyun Q(logmessage__level=LogMessage.CRITICAL)) 1114*4882a593Smuzhiyun 1115*4882a593Smuzhiyun queryset = queryset.annotate( 1116*4882a593Smuzhiyun errors_no=Count( 1117*4882a593Smuzhiyun Case( 1118*4882a593Smuzhiyun When(criteria, then=Value(1)), 1119*4882a593Smuzhiyun output_field=IntegerField() 1120*4882a593Smuzhiyun ) 1121*4882a593Smuzhiyun ) 1122*4882a593Smuzhiyun ) 1123*4882a593Smuzhiyun 1124*4882a593Smuzhiyun # annotate with number of WARNING log messages 1125*4882a593Smuzhiyun queryset = queryset.annotate( 1126*4882a593Smuzhiyun warnings_no=Count( 1127*4882a593Smuzhiyun Case( 1128*4882a593Smuzhiyun When(logmessage__level=LogMessage.WARNING, then=Value(1)), 1129*4882a593Smuzhiyun output_field=IntegerField() 1130*4882a593Smuzhiyun ) 1131*4882a593Smuzhiyun ) 1132*4882a593Smuzhiyun ) 1133*4882a593Smuzhiyun 1134*4882a593Smuzhiyun self.queryset = queryset 1135*4882a593Smuzhiyun 1136*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 1137*4882a593Smuzhiyun outcome_template = ''' 1138*4882a593Smuzhiyun {% if data.outcome == data.SUCCEEDED %} 1139*4882a593Smuzhiyun <span class="glyphicon glyphicon-ok-circle"></span> 1140*4882a593Smuzhiyun {% elif data.outcome == data.FAILED %} 1141*4882a593Smuzhiyun <span class="glyphicon glyphicon-minus-sign"></span> 1142*4882a593Smuzhiyun {% endif %} 1143*4882a593Smuzhiyun 1144*4882a593Smuzhiyun {% if data.cooker_log_path %} 1145*4882a593Smuzhiyun 1146*4882a593Smuzhiyun <a href="{% url "build_artifact" data.id "cookerlog" data.id %}"> 1147*4882a593Smuzhiyun <span class="glyphicon glyphicon-download-alt get-help" 1148*4882a593Smuzhiyun data-original-title="Download build log"></span> 1149*4882a593Smuzhiyun </a> 1150*4882a593Smuzhiyun {% endif %} 1151*4882a593Smuzhiyun ''' 1152*4882a593Smuzhiyun 1153*4882a593Smuzhiyun recipe_template = ''' 1154*4882a593Smuzhiyun {% for target_label in data.target_labels %} 1155*4882a593Smuzhiyun <a href="{% url "builddashboard" data.id %}"> 1156*4882a593Smuzhiyun {{target_label}} 1157*4882a593Smuzhiyun </a> 1158*4882a593Smuzhiyun <br /> 1159*4882a593Smuzhiyun {% endfor %} 1160*4882a593Smuzhiyun ''' 1161*4882a593Smuzhiyun 1162*4882a593Smuzhiyun machine_template = ''' 1163*4882a593Smuzhiyun {{data.machine}} 1164*4882a593Smuzhiyun ''' 1165*4882a593Smuzhiyun 1166*4882a593Smuzhiyun started_on_template = ''' 1167*4882a593Smuzhiyun {{data.started_on | date:"d/m/y H:i"}} 1168*4882a593Smuzhiyun ''' 1169*4882a593Smuzhiyun 1170*4882a593Smuzhiyun completed_on_template = ''' 1171*4882a593Smuzhiyun {{data.completed_on | date:"d/m/y H:i"}} 1172*4882a593Smuzhiyun ''' 1173*4882a593Smuzhiyun 1174*4882a593Smuzhiyun failed_tasks_template = ''' 1175*4882a593Smuzhiyun {% if data.failed_tasks.count == 1 %} 1176*4882a593Smuzhiyun <a class="text-danger" href="{% url "task" data.id data.failed_tasks.0.id %}"> 1177*4882a593Smuzhiyun <span> 1178*4882a593Smuzhiyun {{data.failed_tasks.0.recipe.name}} {{data.failed_tasks.0.task_name}} 1179*4882a593Smuzhiyun </span> 1180*4882a593Smuzhiyun </a> 1181*4882a593Smuzhiyun <a href="{% url "build_artifact" data.id "tasklogfile" data.failed_tasks.0.id %}"> 1182*4882a593Smuzhiyun <span class="glyphicon glyphicon-download-alt get-help" 1183*4882a593Smuzhiyun title="Download task log file"> 1184*4882a593Smuzhiyun </span> 1185*4882a593Smuzhiyun </a> 1186*4882a593Smuzhiyun {% elif data.failed_tasks.count > 1 %} 1187*4882a593Smuzhiyun <a href="{% url "tasks" data.id %}?filter=outcome%3A{{extra.Task.OUTCOME_FAILED}}"> 1188*4882a593Smuzhiyun <span class="text-danger">{{data.failed_tasks.count}} tasks</span> 1189*4882a593Smuzhiyun </a> 1190*4882a593Smuzhiyun {% endif %} 1191*4882a593Smuzhiyun ''' 1192*4882a593Smuzhiyun 1193*4882a593Smuzhiyun errors_template = ''' 1194*4882a593Smuzhiyun {% if data.errors_no %} 1195*4882a593Smuzhiyun <a class="errors.count text-danger" href="{% url "builddashboard" data.id %}#errors"> 1196*4882a593Smuzhiyun {{data.errors_no}} error{{data.errors_no|pluralize}} 1197*4882a593Smuzhiyun </a> 1198*4882a593Smuzhiyun {% endif %} 1199*4882a593Smuzhiyun ''' 1200*4882a593Smuzhiyun 1201*4882a593Smuzhiyun warnings_template = ''' 1202*4882a593Smuzhiyun {% if data.warnings_no %} 1203*4882a593Smuzhiyun <a class="warnings.count text-warning" href="{% url "builddashboard" data.id %}#warnings"> 1204*4882a593Smuzhiyun {{data.warnings_no}} warning{{data.warnings_no|pluralize}} 1205*4882a593Smuzhiyun </a> 1206*4882a593Smuzhiyun {% endif %} 1207*4882a593Smuzhiyun ''' 1208*4882a593Smuzhiyun 1209*4882a593Smuzhiyun time_template = ''' 1210*4882a593Smuzhiyun {% load projecttags %} 1211*4882a593Smuzhiyun {% if data.outcome == extra.Build.SUCCEEDED %} 1212*4882a593Smuzhiyun <a href="{% url "buildtime" data.id %}"> 1213*4882a593Smuzhiyun {{data.timespent_seconds | sectohms}} 1214*4882a593Smuzhiyun </a> 1215*4882a593Smuzhiyun {% else %} 1216*4882a593Smuzhiyun {{data.timespent_seconds | sectohms}} 1217*4882a593Smuzhiyun {% endif %} 1218*4882a593Smuzhiyun ''' 1219*4882a593Smuzhiyun 1220*4882a593Smuzhiyun image_files_template = ''' 1221*4882a593Smuzhiyun {% if data.outcome == extra.Build.SUCCEEDED %} 1222*4882a593Smuzhiyun {{data.get_image_file_extensions}} 1223*4882a593Smuzhiyun {% endif %} 1224*4882a593Smuzhiyun ''' 1225*4882a593Smuzhiyun 1226*4882a593Smuzhiyun self.add_column(title='Outcome', 1227*4882a593Smuzhiyun help_text='Final state of the build (successful \ 1228*4882a593Smuzhiyun or failed)', 1229*4882a593Smuzhiyun hideable=False, 1230*4882a593Smuzhiyun orderable=True, 1231*4882a593Smuzhiyun filter_name='outcome_filter', 1232*4882a593Smuzhiyun static_data_name='outcome', 1233*4882a593Smuzhiyun static_data_template=outcome_template) 1234*4882a593Smuzhiyun 1235*4882a593Smuzhiyun self.add_column(title='Recipe', 1236*4882a593Smuzhiyun help_text='What was built (i.e. one or more recipes \ 1237*4882a593Smuzhiyun or image recipes)', 1238*4882a593Smuzhiyun hideable=False, 1239*4882a593Smuzhiyun orderable=False, 1240*4882a593Smuzhiyun static_data_name='target', 1241*4882a593Smuzhiyun static_data_template=recipe_template) 1242*4882a593Smuzhiyun 1243*4882a593Smuzhiyun self.add_column(title='Machine', 1244*4882a593Smuzhiyun help_text='Hardware for which you are building a \ 1245*4882a593Smuzhiyun recipe or image recipe', 1246*4882a593Smuzhiyun hideable=False, 1247*4882a593Smuzhiyun orderable=True, 1248*4882a593Smuzhiyun static_data_name='machine', 1249*4882a593Smuzhiyun static_data_template=machine_template) 1250*4882a593Smuzhiyun 1251*4882a593Smuzhiyun self.add_column(title='Started on', 1252*4882a593Smuzhiyun help_text='The date and time when the build started', 1253*4882a593Smuzhiyun hideable=True, 1254*4882a593Smuzhiyun hidden=True, 1255*4882a593Smuzhiyun orderable=True, 1256*4882a593Smuzhiyun filter_name='started_on_filter', 1257*4882a593Smuzhiyun static_data_name='started_on', 1258*4882a593Smuzhiyun static_data_template=started_on_template) 1259*4882a593Smuzhiyun 1260*4882a593Smuzhiyun self.add_column(title='Completed on', 1261*4882a593Smuzhiyun help_text='The date and time when the build finished', 1262*4882a593Smuzhiyun hideable=False, 1263*4882a593Smuzhiyun orderable=True, 1264*4882a593Smuzhiyun filter_name='completed_on_filter', 1265*4882a593Smuzhiyun static_data_name='completed_on', 1266*4882a593Smuzhiyun static_data_template=completed_on_template) 1267*4882a593Smuzhiyun 1268*4882a593Smuzhiyun self.add_column(title='Failed tasks', 1269*4882a593Smuzhiyun help_text='The number of tasks which failed during \ 1270*4882a593Smuzhiyun the build', 1271*4882a593Smuzhiyun hideable=True, 1272*4882a593Smuzhiyun orderable=False, 1273*4882a593Smuzhiyun filter_name='failed_tasks_filter', 1274*4882a593Smuzhiyun static_data_name='failed_tasks', 1275*4882a593Smuzhiyun static_data_template=failed_tasks_template) 1276*4882a593Smuzhiyun 1277*4882a593Smuzhiyun self.add_column(title='Errors', 1278*4882a593Smuzhiyun help_text='The number of errors encountered during \ 1279*4882a593Smuzhiyun the build (if any)', 1280*4882a593Smuzhiyun hideable=True, 1281*4882a593Smuzhiyun orderable=True, 1282*4882a593Smuzhiyun static_data_name='errors_no', 1283*4882a593Smuzhiyun static_data_template=errors_template) 1284*4882a593Smuzhiyun 1285*4882a593Smuzhiyun self.add_column(title='Warnings', 1286*4882a593Smuzhiyun help_text='The number of warnings encountered during \ 1287*4882a593Smuzhiyun the build (if any)', 1288*4882a593Smuzhiyun hideable=True, 1289*4882a593Smuzhiyun orderable=True, 1290*4882a593Smuzhiyun static_data_name='warnings_no', 1291*4882a593Smuzhiyun static_data_template=warnings_template) 1292*4882a593Smuzhiyun 1293*4882a593Smuzhiyun self.add_column(title='Time', 1294*4882a593Smuzhiyun help_text='How long the build took to finish', 1295*4882a593Smuzhiyun hideable=True, 1296*4882a593Smuzhiyun hidden=True, 1297*4882a593Smuzhiyun orderable=False, 1298*4882a593Smuzhiyun static_data_name='time', 1299*4882a593Smuzhiyun static_data_template=time_template) 1300*4882a593Smuzhiyun 1301*4882a593Smuzhiyun self.add_column(title='Image files', 1302*4882a593Smuzhiyun help_text='The root file system types produced by \ 1303*4882a593Smuzhiyun the build', 1304*4882a593Smuzhiyun hideable=True, 1305*4882a593Smuzhiyun orderable=False, 1306*4882a593Smuzhiyun static_data_name='image_files', 1307*4882a593Smuzhiyun static_data_template=image_files_template) 1308*4882a593Smuzhiyun 1309*4882a593Smuzhiyun def setup_filters(self, *args, **kwargs): 1310*4882a593Smuzhiyun # outcomes 1311*4882a593Smuzhiyun outcome_filter = TableFilter( 1312*4882a593Smuzhiyun 'outcome_filter', 1313*4882a593Smuzhiyun 'Filter builds by outcome' 1314*4882a593Smuzhiyun ) 1315*4882a593Smuzhiyun 1316*4882a593Smuzhiyun successful_builds_action = TableFilterActionToggle( 1317*4882a593Smuzhiyun 'successful_builds', 1318*4882a593Smuzhiyun 'Successful builds', 1319*4882a593Smuzhiyun Q(outcome=Build.SUCCEEDED) 1320*4882a593Smuzhiyun ) 1321*4882a593Smuzhiyun 1322*4882a593Smuzhiyun failed_builds_action = TableFilterActionToggle( 1323*4882a593Smuzhiyun 'failed_builds', 1324*4882a593Smuzhiyun 'Failed builds', 1325*4882a593Smuzhiyun Q(outcome=Build.FAILED) 1326*4882a593Smuzhiyun ) 1327*4882a593Smuzhiyun 1328*4882a593Smuzhiyun outcome_filter.add_action(successful_builds_action) 1329*4882a593Smuzhiyun outcome_filter.add_action(failed_builds_action) 1330*4882a593Smuzhiyun self.add_filter(outcome_filter) 1331*4882a593Smuzhiyun 1332*4882a593Smuzhiyun # started on 1333*4882a593Smuzhiyun started_on_filter = TableFilter( 1334*4882a593Smuzhiyun 'started_on_filter', 1335*4882a593Smuzhiyun 'Filter by date when build was started' 1336*4882a593Smuzhiyun ) 1337*4882a593Smuzhiyun 1338*4882a593Smuzhiyun started_today_action = TableFilterActionDay( 1339*4882a593Smuzhiyun 'today', 1340*4882a593Smuzhiyun 'Today\'s builds', 1341*4882a593Smuzhiyun 'started_on', 1342*4882a593Smuzhiyun 'today' 1343*4882a593Smuzhiyun ) 1344*4882a593Smuzhiyun 1345*4882a593Smuzhiyun started_yesterday_action = TableFilterActionDay( 1346*4882a593Smuzhiyun 'yesterday', 1347*4882a593Smuzhiyun 'Yesterday\'s builds', 1348*4882a593Smuzhiyun 'started_on', 1349*4882a593Smuzhiyun 'yesterday' 1350*4882a593Smuzhiyun ) 1351*4882a593Smuzhiyun 1352*4882a593Smuzhiyun by_started_date_range_action = TableFilterActionDateRange( 1353*4882a593Smuzhiyun 'date_range', 1354*4882a593Smuzhiyun 'Build date range', 1355*4882a593Smuzhiyun 'started_on' 1356*4882a593Smuzhiyun ) 1357*4882a593Smuzhiyun 1358*4882a593Smuzhiyun started_on_filter.add_action(started_today_action) 1359*4882a593Smuzhiyun started_on_filter.add_action(started_yesterday_action) 1360*4882a593Smuzhiyun started_on_filter.add_action(by_started_date_range_action) 1361*4882a593Smuzhiyun self.add_filter(started_on_filter) 1362*4882a593Smuzhiyun 1363*4882a593Smuzhiyun # completed on 1364*4882a593Smuzhiyun completed_on_filter = TableFilter( 1365*4882a593Smuzhiyun 'completed_on_filter', 1366*4882a593Smuzhiyun 'Filter by date when build was completed' 1367*4882a593Smuzhiyun ) 1368*4882a593Smuzhiyun 1369*4882a593Smuzhiyun completed_today_action = TableFilterActionDay( 1370*4882a593Smuzhiyun 'today', 1371*4882a593Smuzhiyun 'Today\'s builds', 1372*4882a593Smuzhiyun 'completed_on', 1373*4882a593Smuzhiyun 'today' 1374*4882a593Smuzhiyun ) 1375*4882a593Smuzhiyun 1376*4882a593Smuzhiyun completed_yesterday_action = TableFilterActionDay( 1377*4882a593Smuzhiyun 'yesterday', 1378*4882a593Smuzhiyun 'Yesterday\'s builds', 1379*4882a593Smuzhiyun 'completed_on', 1380*4882a593Smuzhiyun 'yesterday' 1381*4882a593Smuzhiyun ) 1382*4882a593Smuzhiyun 1383*4882a593Smuzhiyun by_completed_date_range_action = TableFilterActionDateRange( 1384*4882a593Smuzhiyun 'date_range', 1385*4882a593Smuzhiyun 'Build date range', 1386*4882a593Smuzhiyun 'completed_on' 1387*4882a593Smuzhiyun ) 1388*4882a593Smuzhiyun 1389*4882a593Smuzhiyun completed_on_filter.add_action(completed_today_action) 1390*4882a593Smuzhiyun completed_on_filter.add_action(completed_yesterday_action) 1391*4882a593Smuzhiyun completed_on_filter.add_action(by_completed_date_range_action) 1392*4882a593Smuzhiyun self.add_filter(completed_on_filter) 1393*4882a593Smuzhiyun 1394*4882a593Smuzhiyun # failed tasks 1395*4882a593Smuzhiyun failed_tasks_filter = TableFilter( 1396*4882a593Smuzhiyun 'failed_tasks_filter', 1397*4882a593Smuzhiyun 'Filter builds by failed tasks' 1398*4882a593Smuzhiyun ) 1399*4882a593Smuzhiyun 1400*4882a593Smuzhiyun criteria = Q(task_build__outcome=Task.OUTCOME_FAILED) 1401*4882a593Smuzhiyun 1402*4882a593Smuzhiyun with_failed_tasks_action = TableFilterActionToggle( 1403*4882a593Smuzhiyun 'with_failed_tasks', 1404*4882a593Smuzhiyun 'Builds with failed tasks', 1405*4882a593Smuzhiyun criteria 1406*4882a593Smuzhiyun ) 1407*4882a593Smuzhiyun 1408*4882a593Smuzhiyun without_failed_tasks_action = TableFilterActionToggle( 1409*4882a593Smuzhiyun 'without_failed_tasks', 1410*4882a593Smuzhiyun 'Builds without failed tasks', 1411*4882a593Smuzhiyun ~criteria 1412*4882a593Smuzhiyun ) 1413*4882a593Smuzhiyun 1414*4882a593Smuzhiyun failed_tasks_filter.add_action(with_failed_tasks_action) 1415*4882a593Smuzhiyun failed_tasks_filter.add_action(without_failed_tasks_action) 1416*4882a593Smuzhiyun self.add_filter(failed_tasks_filter) 1417*4882a593Smuzhiyun 1418*4882a593Smuzhiyun 1419*4882a593Smuzhiyunclass AllBuildsTable(BuildsTable): 1420*4882a593Smuzhiyun """ Builds page for all builds """ 1421*4882a593Smuzhiyun 1422*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 1423*4882a593Smuzhiyun super(AllBuildsTable, self).__init__(*args, **kwargs) 1424*4882a593Smuzhiyun self.title = 'All builds' 1425*4882a593Smuzhiyun self.mrb_type = 'all' 1426*4882a593Smuzhiyun 1427*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 1428*4882a593Smuzhiyun """ 1429*4882a593Smuzhiyun All builds page shows a column for the project 1430*4882a593Smuzhiyun """ 1431*4882a593Smuzhiyun 1432*4882a593Smuzhiyun super(AllBuildsTable, self).setup_columns(*args, **kwargs) 1433*4882a593Smuzhiyun 1434*4882a593Smuzhiyun project_template = ''' 1435*4882a593Smuzhiyun {% load project_url_tag %} 1436*4882a593Smuzhiyun <a href="{% project_url data.project %}"> 1437*4882a593Smuzhiyun {{data.project.name}} 1438*4882a593Smuzhiyun </a> 1439*4882a593Smuzhiyun {% if data.project.is_default %} 1440*4882a593Smuzhiyun <span class="glyphicon glyphicon-question-sign get-help hover-help" title="" 1441*4882a593Smuzhiyun data-original-title="This project shows information about 1442*4882a593Smuzhiyun the builds you start from the command line while Toaster is 1443*4882a593Smuzhiyun running" style="visibility: hidden;"></span> 1444*4882a593Smuzhiyun {% endif %} 1445*4882a593Smuzhiyun ''' 1446*4882a593Smuzhiyun 1447*4882a593Smuzhiyun self.add_column(title='Project', 1448*4882a593Smuzhiyun hideable=True, 1449*4882a593Smuzhiyun orderable=True, 1450*4882a593Smuzhiyun static_data_name='project', 1451*4882a593Smuzhiyun static_data_template=project_template) 1452*4882a593Smuzhiyun 1453*4882a593Smuzhiyun def get_context_data(self, **kwargs): 1454*4882a593Smuzhiyun """ Get all builds for the recent builds area """ 1455*4882a593Smuzhiyun context = super(AllBuildsTable, self).get_context_data(**kwargs) 1456*4882a593Smuzhiyun context['mru'] = Build.get_recent() 1457*4882a593Smuzhiyun return context 1458*4882a593Smuzhiyun 1459*4882a593Smuzhiyunclass ProjectBuildsTable(BuildsTable): 1460*4882a593Smuzhiyun """ 1461*4882a593Smuzhiyun Builds page for a single project; a BuildsTable, with the queryset 1462*4882a593Smuzhiyun filtered by project 1463*4882a593Smuzhiyun """ 1464*4882a593Smuzhiyun 1465*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 1466*4882a593Smuzhiyun super(ProjectBuildsTable, self).__init__(*args, **kwargs) 1467*4882a593Smuzhiyun self.title = 'All project builds' 1468*4882a593Smuzhiyun self.mrb_type = 'project' 1469*4882a593Smuzhiyun 1470*4882a593Smuzhiyun # set from the querystring 1471*4882a593Smuzhiyun self.project_id = None 1472*4882a593Smuzhiyun 1473*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 1474*4882a593Smuzhiyun """ 1475*4882a593Smuzhiyun Project builds table doesn't show the machines column by default 1476*4882a593Smuzhiyun """ 1477*4882a593Smuzhiyun 1478*4882a593Smuzhiyun super(ProjectBuildsTable, self).setup_columns(*args, **kwargs) 1479*4882a593Smuzhiyun 1480*4882a593Smuzhiyun # hide the machine column 1481*4882a593Smuzhiyun self.set_column_hidden('Machine', True) 1482*4882a593Smuzhiyun 1483*4882a593Smuzhiyun # allow the machine column to be hidden by the user 1484*4882a593Smuzhiyun self.set_column_hideable('Machine', True) 1485*4882a593Smuzhiyun 1486*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 1487*4882a593Smuzhiyun """ 1488*4882a593Smuzhiyun NOTE: self.project_id must be set before calling super(), 1489*4882a593Smuzhiyun as it's used in setup_queryset() 1490*4882a593Smuzhiyun """ 1491*4882a593Smuzhiyun self.project_id = kwargs['pid'] 1492*4882a593Smuzhiyun super(ProjectBuildsTable, self).setup_queryset(*args, **kwargs) 1493*4882a593Smuzhiyun project = Project.objects.get(pk=self.project_id) 1494*4882a593Smuzhiyun self.queryset = self.queryset.filter(project=project) 1495*4882a593Smuzhiyun 1496*4882a593Smuzhiyun def get_context_data(self, **kwargs): 1497*4882a593Smuzhiyun """ 1498*4882a593Smuzhiyun Get recent builds for this project, and the project itself 1499*4882a593Smuzhiyun 1500*4882a593Smuzhiyun NOTE: self.project_id must be set before calling super(), 1501*4882a593Smuzhiyun as it's used in get_context_data() 1502*4882a593Smuzhiyun """ 1503*4882a593Smuzhiyun self.project_id = kwargs['pid'] 1504*4882a593Smuzhiyun context = super(ProjectBuildsTable, self).get_context_data(**kwargs) 1505*4882a593Smuzhiyun 1506*4882a593Smuzhiyun empty_state_template = ''' 1507*4882a593Smuzhiyun This project has no builds. 1508*4882a593Smuzhiyun <a href="{% url 'projectimagerecipes' data.pid %}"> 1509*4882a593Smuzhiyun Choose a recipe to build</a> 1510*4882a593Smuzhiyun ''' 1511*4882a593Smuzhiyun context['empty_state'] = self.render_static_data(empty_state_template, 1512*4882a593Smuzhiyun kwargs) 1513*4882a593Smuzhiyun 1514*4882a593Smuzhiyun project = Project.objects.get(pk=self.project_id) 1515*4882a593Smuzhiyun context['mru'] = Build.get_recent(project) 1516*4882a593Smuzhiyun context['project'] = project 1517*4882a593Smuzhiyun 1518*4882a593Smuzhiyun self.setup_queryset(**kwargs) 1519*4882a593Smuzhiyun if self.queryset.count() == 0 and \ 1520*4882a593Smuzhiyun project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0: 1521*4882a593Smuzhiyun context['build_in_progress_none_completed'] = True 1522*4882a593Smuzhiyun else: 1523*4882a593Smuzhiyun context['build_in_progress_none_completed'] = False 1524*4882a593Smuzhiyun 1525*4882a593Smuzhiyun return context 1526*4882a593Smuzhiyun 1527*4882a593Smuzhiyun 1528*4882a593Smuzhiyunclass DistrosTable(ToasterTable): 1529*4882a593Smuzhiyun """Table of Distros in Toaster""" 1530*4882a593Smuzhiyun 1531*4882a593Smuzhiyun def __init__(self, *args, **kwargs): 1532*4882a593Smuzhiyun super(DistrosTable, self).__init__(*args, **kwargs) 1533*4882a593Smuzhiyun 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*4882a593Smuzhiyun self.title = "Compatible Distros" 1535*4882a593Smuzhiyun self.default_orderby = "name" 1536*4882a593Smuzhiyun 1537*4882a593Smuzhiyun def get_context_data(self, **kwargs): 1538*4882a593Smuzhiyun context = super(DistrosTable, self).get_context_data(**kwargs) 1539*4882a593Smuzhiyun context['project'] = Project.objects.get(pk=kwargs['pid']) 1540*4882a593Smuzhiyun return context 1541*4882a593Smuzhiyun 1542*4882a593Smuzhiyun def setup_filters(self, *args, **kwargs): 1543*4882a593Smuzhiyun project = Project.objects.get(pk=kwargs['pid']) 1544*4882a593Smuzhiyun 1545*4882a593Smuzhiyun in_current_project_filter = TableFilter( 1546*4882a593Smuzhiyun "in_current_project", 1547*4882a593Smuzhiyun "Filter by project Distros" 1548*4882a593Smuzhiyun ) 1549*4882a593Smuzhiyun 1550*4882a593Smuzhiyun in_project_action = TableFilterActionToggle( 1551*4882a593Smuzhiyun "in_project", 1552*4882a593Smuzhiyun "Distro provided by layers added to this project", 1553*4882a593Smuzhiyun ProjectFilters.in_project(self.project_layers) 1554*4882a593Smuzhiyun ) 1555*4882a593Smuzhiyun 1556*4882a593Smuzhiyun not_in_project_action = TableFilterActionToggle( 1557*4882a593Smuzhiyun "not_in_project", 1558*4882a593Smuzhiyun "Distros provided by layers not added to this project", 1559*4882a593Smuzhiyun ProjectFilters.not_in_project(self.project_layers) 1560*4882a593Smuzhiyun ) 1561*4882a593Smuzhiyun 1562*4882a593Smuzhiyun in_current_project_filter.add_action(in_project_action) 1563*4882a593Smuzhiyun in_current_project_filter.add_action(not_in_project_action) 1564*4882a593Smuzhiyun self.add_filter(in_current_project_filter) 1565*4882a593Smuzhiyun 1566*4882a593Smuzhiyun def setup_queryset(self, *args, **kwargs): 1567*4882a593Smuzhiyun prj = Project.objects.get(pk = kwargs['pid']) 1568*4882a593Smuzhiyun self.queryset = prj.get_all_compatible_distros() 1569*4882a593Smuzhiyun self.queryset = self.queryset.order_by(self.default_orderby) 1570*4882a593Smuzhiyun 1571*4882a593Smuzhiyun self.static_context_extra['current_layers'] = \ 1572*4882a593Smuzhiyun self.project_layers = \ 1573*4882a593Smuzhiyun prj.get_project_layer_versions(pk=True) 1574*4882a593Smuzhiyun 1575*4882a593Smuzhiyun def setup_columns(self, *args, **kwargs): 1576*4882a593Smuzhiyun 1577*4882a593Smuzhiyun self.add_column(title="Distro", 1578*4882a593Smuzhiyun hideable=False, 1579*4882a593Smuzhiyun orderable=True, 1580*4882a593Smuzhiyun field_name="name") 1581*4882a593Smuzhiyun 1582*4882a593Smuzhiyun self.add_column(title="Description", 1583*4882a593Smuzhiyun field_name="description") 1584*4882a593Smuzhiyun 1585*4882a593Smuzhiyun layer_link_template = ''' 1586*4882a593Smuzhiyun <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}"> 1587*4882a593Smuzhiyun {{data.layer_version.layer.name}}</a> 1588*4882a593Smuzhiyun ''' 1589*4882a593Smuzhiyun 1590*4882a593Smuzhiyun self.add_column(title="Layer", 1591*4882a593Smuzhiyun static_data_name="layer_version__layer__name", 1592*4882a593Smuzhiyun static_data_template=layer_link_template, 1593*4882a593Smuzhiyun orderable=True) 1594*4882a593Smuzhiyun 1595*4882a593Smuzhiyun self.add_column(title="Git revision", 1596*4882a593Smuzhiyun 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*4882a593Smuzhiyun hidden=True, 1598*4882a593Smuzhiyun field_name="layer_version__get_vcs_reference") 1599*4882a593Smuzhiyun 1600*4882a593Smuzhiyun distro_file_template = '''<code>conf/distro/{{data.name}}.conf</code> 1601*4882a593Smuzhiyun {% 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 %}''' 1602*4882a593Smuzhiyun self.add_column(title="Distro file", 1603*4882a593Smuzhiyun hidden=True, 1604*4882a593Smuzhiyun static_data_name="templatefile", 1605*4882a593Smuzhiyun static_data_template=distro_file_template) 1606*4882a593Smuzhiyun 1607*4882a593Smuzhiyun self.add_column(title="Select", 1608*4882a593Smuzhiyun help_text="Sets the selected distro to the project", 1609*4882a593Smuzhiyun hideable=False, 1610*4882a593Smuzhiyun filter_name="in_current_project", 1611*4882a593Smuzhiyun static_data_name="add-del-layers", 1612*4882a593Smuzhiyun static_data_template='{% include "distro_btn.html" %}') 1613*4882a593Smuzhiyun 1614