blob: 9c9cda4e954c78d320f8762d37f57a38faa10dba [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# BitBake Toaster Implementation
6#
7# Copyright (C) 2015 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22from toastergui.widgets import ToasterTable
23from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
Patrick Williamsf1e5d692016-03-30 15:21:19 -050024from orm.models import CustomImageRecipe, Package
Patrick Williamsc124f4f2015-09-15 14:41:29 -050025from django.db.models import Q, Max
26from django.conf.urls import url
27from django.core.urlresolvers import reverse
28from django.views.generic import TemplateView
29
30
31class ProjectFiltersMixin(object):
32 """Common mixin for recipe, machine in project filters"""
33
34 def filter_in_project(self, count_only=False):
35 query = self.queryset.filter(layer_version__in=self.project_layers)
36 if count_only:
37 return query.count()
38
39 self.queryset = query
40
41 def filter_not_in_project(self, count_only=False):
42 query = self.queryset.exclude(layer_version__in=self.project_layers)
43 if count_only:
44 return query.count()
45
46 self.queryset = query
47
48class LayersTable(ToasterTable):
49 """Table of layers in Toaster"""
50
51 def __init__(self, *args, **kwargs):
52 super(LayersTable, self).__init__(*args, **kwargs)
53 self.default_orderby = "layer__name"
Patrick Williamsf1e5d692016-03-30 15:21:19 -050054 self.title = "Compatible layers"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055
56 def get_context_data(self, **kwargs):
57 context = super(LayersTable, self).get_context_data(**kwargs)
58
59 project = Project.objects.get(pk=kwargs['pid'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -050060 context['project'] = project
Patrick Williamsc124f4f2015-09-15 14:41:29 -050061
62 return context
63
64
65 def setup_filters(self, *args, **kwargs):
66 project = Project.objects.get(pk=kwargs['pid'])
67 self.project_layers = ProjectLayer.objects.filter(project=project)
68
69
70 self.add_filter(title="Filter by project layers",
71 name="in_current_project",
72 filter_actions=[
73 self.make_filter_action("in_project", "Layers added to this project", self.filter_in_project),
74 self.make_filter_action("not_in_project", "Layers not added to this project", self.filter_not_in_project)
75 ])
76
77 def filter_in_project(self, count_only=False):
78 query = self.queryset.filter(projectlayer__in=self.project_layers)
79 if count_only:
80 return query.count()
81
82 self.queryset = query
83
84 def filter_not_in_project(self, count_only=False):
85 query = self.queryset.exclude(projectlayer__in=self.project_layers)
86 if count_only:
87 return query.count()
88
89 self.queryset = query
90
91
92 def setup_queryset(self, *args, **kwargs):
93 prj = Project.objects.get(pk = kwargs['pid'])
Patrick Williamsf1e5d692016-03-30 15:21:19 -050094 compatible_layers = prj.get_all_compatible_layer_versions()
95
96 self.static_context_extra['current_layers'] = \
97 prj.get_project_layer_versions(pk=True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098
99 self.queryset = compatible_layers.order_by(self.default_orderby)
100
101 def setup_columns(self, *args, **kwargs):
102
103 layer_link_template = '''
104 <a href="{% url 'layerdetails' extra.pid data.id %}">
105 {{data.layer.name}}
106 </a>
107 '''
108
109 self.add_column(title="Layer",
110 hideable=False,
111 orderable=True,
112 static_data_name="layer__name",
113 static_data_template=layer_link_template)
114
115 self.add_column(title="Summary",
116 field_name="layer__summary")
117
118 git_url_template = '''
119 <a href="{% url 'layerdetails' extra.pid data.id %}">
120 <code>{{data.layer.vcs_url}}</code>
121 </a>
122 {% if data.get_vcs_link_url %}
123 <a target="_blank" href="{{ data.get_vcs_link_url }}">
124 <i class="icon-share get-info"></i>
125 </a>
126 {% endif %}
127 '''
128
129 self.add_column(title="Git repository URL",
130 help_text="The Git repository for the layer source code",
131 hidden=True,
132 static_data_name="layer__vcs_url",
133 static_data_template=git_url_template)
134
135 git_dir_template = '''
136 <a href="{% url 'layerdetails' extra.pid data.id %}">
137 <code>{{data.dirpath}}</code>
138 </a>
139 {% if data.dirpath and data.get_vcs_dirpath_link_url %}
140 <a target="_blank" href="{{ data.get_vcs_dirpath_link_url }}">
141 <i class="icon-share get-info"></i>
142 </a>
143 {% endif %}'''
144
145 self.add_column(title="Subdirectory",
146 help_text="The layer directory within the Git repository",
147 hidden=True,
148 static_data_name="git_subdir",
149 static_data_template=git_dir_template)
150
151 revision_template = '''
152 {% load projecttags %}
153 {% with vcs_ref=data.get_vcs_reference %}
154 {% if vcs_ref|is_shaid %}
155 <a class="btn" data-content="<ul class='unstyled'> <li>{{vcs_ref}}</li> </ul>">
156 {{vcs_ref|truncatechars:10}}
157 </a>
158 {% else %}
159 {{vcs_ref}}
160 {% endif %}
161 {% endwith %}
162 '''
163
164 self.add_column(title="Revision",
165 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",
166 static_data_name="revision",
167 static_data_template=revision_template)
168
169 deps_template = '''
170 {% with ods=data.dependencies.all%}
171 {% if ods.count %}
172 <a class="btn" title="<a href='{% url "layerdetails" extra.pid data.id %}'>{{data.layer.name}}</a> dependencies"
173 data-content="<ul class='unstyled'>
174 {% for i in ods%}
175 <li><a href='{% url "layerdetails" extra.pid i.depends_on.pk %}'>{{i.depends_on.layer.name}}</a></li>
176 {% endfor %}
177 </ul>">
178 {{ods.count}}
179 </a>
180 {% endif %}
181 {% endwith %}
182 '''
183
184 self.add_column(title="Dependencies",
185 help_text="Other layers a layer depends upon",
186 static_data_name="dependencies",
187 static_data_template=deps_template)
188
189 self.add_column(title="Add | Delete",
190 help_text="Add or delete layers to / from your project",
191 hideable=False,
192 filter_name="in_current_project",
193 static_data_name="add-del-layers",
194 static_data_template='{% include "layer_btn.html" %}')
195
196 project = Project.objects.get(pk=kwargs['pid'])
197 self.add_column(title="LayerDetailsUrl",
198 displayable = False,
199 field_name="layerdetailurl",
200 computation = lambda x: reverse('layerdetails', args=(project.id, x.id)))
201
202 self.add_column(title="name",
203 displayable = False,
204 field_name="name",
205 computation = lambda x: x.layer.name)
206
207
208class MachinesTable(ToasterTable, ProjectFiltersMixin):
209 """Table of Machines in Toaster"""
210
211 def __init__(self, *args, **kwargs):
212 super(MachinesTable, self).__init__(*args, **kwargs)
213 self.empty_state = "No machines maybe you need to do a build?"
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500214 self.title = "Compatible machines"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215 self.default_orderby = "name"
216
217 def get_context_data(self, **kwargs):
218 context = super(MachinesTable, self).get_context_data(**kwargs)
219 context['project'] = Project.objects.get(pk=kwargs['pid'])
220 context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
221 return context
222
223 def setup_filters(self, *args, **kwargs):
224 project = Project.objects.get(pk=kwargs['pid'])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500225 self.project_layers = project.get_project_layer_versions()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500226
227 self.add_filter(title="Filter by project machines",
228 name="in_current_project",
229 filter_actions=[
230 self.make_filter_action("in_project", "Machines provided by layers added to this project", self.filter_in_project),
231 self.make_filter_action("not_in_project", "Machines provided by layers not added to this project", self.filter_not_in_project)
232 ])
233
234 def setup_queryset(self, *args, **kwargs):
235 prj = Project.objects.get(pk = kwargs['pid'])
236 self.queryset = prj.get_all_compatible_machines()
237 self.queryset = self.queryset.order_by(self.default_orderby)
238
239 def setup_columns(self, *args, **kwargs):
240
241 self.add_column(title="Machine",
242 hideable=False,
243 orderable=True,
244 field_name="name")
245
246 self.add_column(title="Description",
247 field_name="description")
248
249 layer_link_template = '''
250 <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}">
251 {{data.layer_version.layer.name}}</a>
252 '''
253
254 self.add_column(title="Layer",
255 static_data_name="layer_version__layer__name",
256 static_data_template=layer_link_template,
257 orderable=True)
258
259 self.add_column(title="Revision",
260 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",
261 hidden=True,
262 field_name="layer_version__get_vcs_reference")
263
264 machine_file_template = '''<code>conf/machine/{{data.name}}.conf</code>
265 <a href="{{data.get_vcs_machine_file_link_url}}" target="_blank"><i class="icon-share get-info"></i></a>'''
266
267 self.add_column(title="Machine file",
268 hidden=True,
269 static_data_name="machinefile",
270 static_data_template=machine_file_template)
271
272 self.add_column(title="Select",
273 help_text="Sets the selected machine as the project machine. You can only have one machine per project",
274 hideable=False,
275 filter_name="in_current_project",
276 static_data_name="add-del-layers",
277 static_data_template='{% include "machine_btn.html" %}')
278
279
280class LayerMachinesTable(MachinesTable):
281 """ Smaller version of the Machines table for use in layer details """
282
283 def __init__(self, *args, **kwargs):
284 super(LayerMachinesTable, self).__init__(*args, **kwargs)
285
286 def get_context_data(self, **kwargs):
287 context = super(LayerMachinesTable, self).get_context_data(**kwargs)
288 context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
289 return context
290
291
292 def setup_queryset(self, *args, **kwargs):
293 MachinesTable.setup_queryset(self, *args, **kwargs)
294
295 self.queryset = self.queryset.filter(layer_version__pk=int(kwargs['layerid']))
296 self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count()
297
298 def setup_columns(self, *args, **kwargs):
299 self.add_column(title="Machine",
300 hideable=False,
301 orderable=True,
302 field_name="name")
303
304 self.add_column(title="Description",
305 field_name="description")
306
307 select_btn_template = '<a href="{% url "project" extra.pid %}?setMachine={{data.name}}" class="btn btn-block select-machine-btn" {% if extra.in_prj == 0%}disabled="disabled"{%endif%}>Select machine</a>'
308
309 self.add_column(title="Select machine",
310 static_data_name="add-del-layers",
311 static_data_template=select_btn_template)
312
313
314class RecipesTable(ToasterTable, ProjectFiltersMixin):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500315 """Table of All Recipes in Toaster"""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316
317 def __init__(self, *args, **kwargs):
318 super(RecipesTable, self).__init__(*args, **kwargs)
319 self.empty_state = "Toaster has no recipe information. To generate recipe information you can configure a layer source then run a build."
320 self.default_orderby = "name"
321
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500322 build_col = { 'title' : "Build",
323 'help_text' : "Add or delete recipes to and from your project",
324 'hideable' : False,
325 'filter_name' : "in_current_project",
326 'static_data_name' : "add-del-layers",
327 'static_data_template' : '{% include "recipe_btn.html" %}'}
328
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329 def get_context_data(self, **kwargs):
330 project = Project.objects.get(pk=kwargs['pid'])
331 context = super(RecipesTable, self).get_context_data(**kwargs)
332
333 context['project'] = project
334
335 context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
336
337 return context
338
339 def setup_filters(self, *args, **kwargs):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500340 self.add_filter(title="Filter by project recipes",
341 name="in_current_project",
342 filter_actions=[
343 self.make_filter_action("in_project", "Recipes provided by layers added to this project", self.filter_in_project),
344 self.make_filter_action("not_in_project", "Recipes provided by layers not added to this project", self.filter_not_in_project)
345 ])
346
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500347 def setup_queryset(self, *args, **kwargs):
348 prj = Project.objects.get(pk = kwargs['pid'])
349
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500350 # Project layers used by the filters
351 self.project_layers = prj.get_project_layer_versions(pk=True)
352
353 # Project layers used to switch the button states
354 self.static_context_extra['current_layers'] = self.project_layers
355
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500356 self.queryset = prj.get_all_compatible_recipes()
357 self.queryset = self.queryset.order_by(self.default_orderby)
358
359
360 def setup_columns(self, *args, **kwargs):
361
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500362 self.add_column(title="Version",
363 hidden=False,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500364 field_name="version")
365
366 self.add_column(title="Description",
367 field_name="get_description_or_summary")
368
369 recipe_file_template = '''
370 <code>{{data.file_path}}</code>
371 <a href="{{data.get_vcs_recipe_file_link_url}}" target="_blank">
372 <i class="icon-share get-info"></i>
373 </a>
374 '''
375
376 self.add_column(title="Recipe file",
377 help_text="Path to the recipe .bb file",
378 hidden=True,
379 static_data_name="recipe-file",
380 static_data_template=recipe_file_template)
381
382 self.add_column(title="Section",
383 help_text="The section in which recipes should be categorized",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500384 hidden=True,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500385 orderable=True,
386 field_name="section")
387
388 layer_link_template = '''
389 <a href="{% url 'layerdetails' extra.pid data.layer_version.id %}">
390 {{data.layer_version.layer.name}}</a>
391 '''
392
393 self.add_column(title="Layer",
394 help_text="The name of the layer providing the recipe",
395 orderable=True,
396 static_data_name="layer_version__layer__name",
397 static_data_template=layer_link_template)
398
399 self.add_column(title="License",
400 help_text="The list of source licenses for the recipe. Multiple license names separated by the pipe character indicates a choice between licenses. Multiple license names separated by the ampersand character indicates multiple licenses exist that cover different parts of the source",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500401 hidden=True,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500402 orderable=True,
403 field_name="license")
404
405 self.add_column(title="Revision",
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500406 hidden=True,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500407 field_name="layer_version__get_vcs_reference")
408
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500409
410class LayerRecipesTable(RecipesTable):
411 """ Smaller version of the Recipes table for use in layer details """
412
413 def __init__(self, *args, **kwargs):
414 super(LayerRecipesTable, self).__init__(*args, **kwargs)
415
416 def get_context_data(self, **kwargs):
417 context = super(LayerRecipesTable, self).get_context_data(**kwargs)
418 context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
419 return context
420
421
422 def setup_queryset(self, *args, **kwargs):
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500423 self.queryset = \
424 Recipe.objects.filter(layer_version__pk=int(kwargs['layerid']))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425
426 self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count()
427
428 def setup_columns(self, *args, **kwargs):
429 self.add_column(title="Recipe",
430 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",
431 hideable=False,
432 orderable=True,
433 field_name="name")
434
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500435 self.add_column(title="Version",
436 field_name="version")
437
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438 self.add_column(title="Description",
439 field_name="get_description_or_summary")
440
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500441 build_recipe_template ='<button class="btn btn-block build-recipe-btn" data-recipe-name="{{data.name}}" {%if extra.in_prj == 0 %}disabled="disabled"{%endif%}>Build recipe</button>'
442
443 self.add_column(title="Build recipe",
444 static_data_name="add-del-layers",
445 static_data_template=build_recipe_template)
446
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500447class CustomImagesTable(ToasterTable):
448 """ Table to display your custom images """
449 def __init__(self, *args, **kwargs):
450 super(CustomImagesTable, self).__init__(*args, **kwargs)
451 self.title = "Custom images"
452
453 def get_context_data(self, **kwargs):
454 context = super(CustomImagesTable, self).get_context_data(**kwargs)
455 project = Project.objects.get(pk=kwargs['pid'])
456 context['project'] = project
457 context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
458 return context
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500459
460 def setup_queryset(self, *args, **kwargs):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500461 prj = Project.objects.get(pk = kwargs['pid'])
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500462 self.queryset = CustomImageRecipe.objects.filter(project=prj)
463 self.queryset = self.queryset.order_by('name')
464
465 def setup_columns(self, *args, **kwargs):
466
467 name_link_template = '''
468 <a href="{% url 'customrecipe' extra.pid data.id %}">
469 {{data.name}}
470 </a>
471 '''
472
473 self.add_column(title="Custom image",
474 hideable=False,
475 static_data_name="name",
476 static_data_template=name_link_template)
477
478 self.add_column(title="Recipe file",
479 static_data_name='recipe_file',
480 static_data_template='')
481
482 approx_packages_template = '<a href="#imagedetails">{{data.packages.all|length}}</a>'
483 self.add_column(title="Approx packages",
484 static_data_name='approx_packages',
485 static_data_template=approx_packages_template)
486
487
488 build_btn_template = '''<button data-recipe-name="{{data.name}}"
489 class="btn btn-block build-recipe-btn" style="margin-top: 5px;" >
490 Build</button>'''
491
492 self.add_column(title="Build",
493 hideable=False,
494 static_data_name='build_custom_img',
495 static_data_template=build_btn_template)
496
497class ImageRecipesTable(RecipesTable):
498 """ A subset of the recipes table which displayed just image recipes """
499
500 def __init__(self, *args, **kwargs):
501 super(ImageRecipesTable, self).__init__(*args, **kwargs)
502 self.title = "Compatible image recipes"
503
504 def setup_queryset(self, *args, **kwargs):
505 super(ImageRecipesTable, self).setup_queryset(*args, **kwargs)
506
507 self.queryset = self.queryset.filter(is_image=True)
508
509
510 def setup_columns(self, *args, **kwargs):
511 self.add_column(title="Image recipe",
512 help_text="When you build an image recipe, you get an "
513 "image: a root file system you can"
514 "deploy to a machine",
515 hideable=False,
516 orderable=True,
517 field_name="name")
518
519 super(ImageRecipesTable, self).setup_columns(*args, **kwargs)
520
521 self.add_column(**RecipesTable.build_col)
522
523
524class NewCustomImagesTable(ImageRecipesTable):
525 """ Table which displays Images recipes which can be customised """
526 def __init__(self, *args, **kwargs):
527 super(NewCustomImagesTable, self).__init__(*args, **kwargs)
528 self.title = "Select the image recipe you want to customise"
529
530 def setup_queryset(self, *args, **kwargs):
531 super(ImageRecipesTable, self).setup_queryset(*args, **kwargs)
532
533 self.queryset = self.queryset.filter(is_image=True)
534
535 def setup_columns(self, *args, **kwargs):
536 self.add_column(title="Image recipe",
537 help_text="When you build an image recipe, you get an "
538 "image: a root file system you can"
539 "deploy to a machine",
540 hideable=False,
541 orderable=True,
542 field_name="recipe__name")
543
544 super(ImageRecipesTable, self).setup_columns(*args, **kwargs)
545
546 self.add_column(title="Customise",
547 hideable=False,
548 filter_name="in_current_project",
549 static_data_name="customise-or-add-recipe",
550 static_data_template='{% include "customise_btn.html" %}')
551
552
553class SoftwareRecipesTable(RecipesTable):
554 """ Displays just the software recipes """
555 def __init__(self, *args, **kwargs):
556 super(SoftwareRecipesTable, self).__init__(*args, **kwargs)
557 self.title = "Compatible software recipes"
558
559 def setup_queryset(self, *args, **kwargs):
560 super(SoftwareRecipesTable, self).setup_queryset(*args, **kwargs)
561
562 self.queryset = self.queryset.filter(is_image=False)
563
564
565 def setup_columns(self, *args, **kwargs):
566 self.add_column(title="Software recipe",
567 help_text="Information about a single piece of "
568 "software, including where to download the source, "
569 "configuration options, how to compile the source "
570 "files and how to package the compiled output",
571 hideable=False,
572 orderable=True,
573 field_name="name")
574
575 super(SoftwareRecipesTable, self).setup_columns(*args, **kwargs)
576
577 self.add_column(**RecipesTable.build_col)
578
579
580class SelectPackagesTable(ToasterTable):
581 """ Table to display the packages to add and remove from an image """
582
583 def __init__(self, *args, **kwargs):
584 super(SelectPackagesTable, self).__init__(*args, **kwargs)
585 self.title = "Add | Remove packages"
586
587 def setup_queryset(self, *args, **kwargs):
588 cust_recipe = CustomImageRecipe.objects.get(pk=kwargs['recipeid'])
589 prj = Project.objects.get(pk = kwargs['pid'])
590
591 current_packages = cust_recipe.packages.all()
592
593 # Get all the packages that are in the custom image
594 # Get all the packages built by builds in the current project
595 # but not those ones that are already in the custom image
596 self.queryset = Package.objects.filter(
597 Q(pk__in=current_packages) |
598 (Q(build__project=prj) &
599 ~Q(name__in=current_packages.values_list('name'))))
600
601 self.queryset = self.queryset.order_by('name')
602
603 self.static_context_extra['recipe_id'] = kwargs['recipeid']
604 self.static_context_extra['current_packages'] = \
605 cust_recipe.packages.values_list('pk', flat=True)
606
607 def setup_columns(self, *args, **kwargs):
608 self.add_column(title="Package",
609 hideable=False,
610 orderable=True,
611 field_name="name")
612
613 self.add_column(title="Package Version",
614 field_name="version")
615
616 self.add_column(title="Approx Size",
617 orderable=True,
618 static_data_name="size",
619 static_data_template="{% load projecttags %} \
620 {{data.size|filtered_filesizeformat}}")
621 self.add_column(title="summary",
622 field_name="summary")
623
624 self.add_column(title="Add | Remove",
625 help_text="Use the add and remove buttons to modify "
626 "the package content of you custom image",
627 static_data_name="add_rm_pkg_btn",
628 static_data_template='{% include "pkg_add_rm_btn.html" %}')