xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/toaster/toastergui/tables.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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            &nbsp;
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