#
# BitBake Toaster Implementation
#
# Copyright (C) 2015 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
#
from toastergui.widgets import ToasterTable
from orm.models import Recipe, ProjectLayer, Layer_Version, Project
from orm.models import CustomImageRecipe, Package, Target, Build, LogMessage, Task
from orm.models import CustomImagePackage, Package_DependencyManager
from django.db.models import Q, Sum, Count, When, Case, Value, IntegerField
from toastergui.tablefilter import TableFilter
from toastergui.tablefilter import TableFilterActionToggle
from toastergui.tablefilter import TableFilterActionDateRange
from toastergui.tablefilter import TableFilterActionDay
import os
class ProjectFilters(object):
@staticmethod
def in_project(project_layers):
return Q(layer_version__in=project_layers)
@staticmethod
def not_in_project(project_layers):
return ~(ProjectFilters.in_project(project_layers))
class LayersTable(ToasterTable):
"""Table of layers in Toaster"""
def __init__(self, *args, **kwargs):
super(LayersTable, self).__init__(*args, **kwargs)
self.default_orderby = "layer__name"
self.title = "Compatible layers"
def get_context_data(self, **kwargs):
context = super(LayersTable, self).get_context_data(**kwargs)
project = Project.objects.get(pk=kwargs['pid'])
context['project'] = project
return context
def setup_filters(self, *args, **kwargs):
project = Project.objects.get(pk=kwargs['pid'])
self.project_layers = ProjectLayer.objects.filter(project=project)
in_current_project_filter = TableFilter(
"in_current_project",
"Filter by project layers"
)
criteria = Q(projectlayer__in=self.project_layers)
in_project_action = TableFilterActionToggle(
"in_project",
"Layers added to this project",
criteria
)
not_in_project_action = TableFilterActionToggle(
"not_in_project",
"Layers not added to this project",
~criteria
)
in_current_project_filter.add_action(in_project_action)
in_current_project_filter.add_action(not_in_project_action)
self.add_filter(in_current_project_filter)
def setup_queryset(self, *args, **kwargs):
prj = Project.objects.get(pk = kwargs['pid'])
compatible_layers = prj.get_all_compatible_layer_versions()
self.static_context_extra['current_layers'] = \
prj.get_project_layer_versions(pk=True)
self.queryset = compatible_layers.order_by(self.default_orderby)
def setup_columns(self, *args, **kwargs):
layer_link_template = '''
{{data.layer.name}}
'''
self.add_column(title="Layer",
hideable=False,
orderable=True,
static_data_name="layer__name",
static_data_template=layer_link_template)
self.add_column(title="Summary",
field_name="layer__summary")
git_url_template = '''
{% if data.layer.local_source_dir %}
{{data.layer.local_source_dir}}
{% else %}
{{data.layer.vcs_url}}
{% endif %}
{% if data.get_vcs_link_url %}
{% endif %}
'''
self.add_column(title="Layer source code location",
help_text="A Git repository or an absolute path to a directory",
hidden=True,
static_data_name="layer__vcs_url",
static_data_template=git_url_template)
git_dir_template = '''
{% if data.layer.local_source_dir %}
Not applicable
{% else %}
{{data.dirpath}}
{% endif %}
{% if data.dirpath and data.get_vcs_dirpath_link_url %}
{% endif %}'''
self.add_column(title="Subdirectory",
help_text="The layer directory within the Git repository",
hidden=True,
static_data_name="git_subdir",
static_data_template=git_dir_template)
revision_template = '''
{% if data.layer.local_source_dir %}
Not applicable
{% else %}
{% with vcs_ref=data.get_vcs_reference %}
{% include 'snippets/gitrev_popover.html' %}
{% endwith %}
{% endif %}
'''
self.add_column(title="Git revision",
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",
static_data_name="revision",
static_data_template=revision_template)
deps_template = '''
{% with ods=data.dependencies.all%}
{% if ods.count %}
{{data.layer.name}} dependencies"
data-content="
conf/machine/{{data.name}}.conf
'''
self.add_column(title="Machine file",
hidden=True,
static_data_name="machinefile",
static_data_template=machine_file_template)
self.add_column(title="Select",
help_text="Sets the selected machine as the project machine. You can only have one machine per project",
hideable=False,
filter_name="in_current_project",
static_data_name="add-del-layers",
static_data_template='{% include "machine_btn.html" %}')
class LayerMachinesTable(MachinesTable):
""" Smaller version of the Machines table for use in layer details """
def __init__(self, *args, **kwargs):
super(LayerMachinesTable, self).__init__(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(LayerMachinesTable, self).get_context_data(**kwargs)
context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
return context
def setup_queryset(self, *args, **kwargs):
MachinesTable.setup_queryset(self, *args, **kwargs)
self.queryset = self.queryset.filter(layer_version__pk=int(kwargs['layerid']))
self.queryset = self.queryset.order_by(self.default_orderby)
self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count()
def setup_columns(self, *args, **kwargs):
self.add_column(title="Machine",
hideable=False,
orderable=True,
field_name="name")
self.add_column(title="Description",
field_name="description")
select_btn_template = '''
Select machine
'''
self.add_column(title="Select machine",
static_data_name="add-del-layers",
static_data_template=select_btn_template)
class RecipesTable(ToasterTable):
"""Table of All Recipes in Toaster"""
def __init__(self, *args, **kwargs):
super(RecipesTable, self).__init__(*args, **kwargs)
self.empty_state = "Toaster has no recipe information. To generate recipe information you need to run a build."
build_col = { 'title' : "Build",
'help_text' : "Before building a recipe, you might need to add the corresponding layer to your project",
'hideable' : False,
'filter_name' : "in_current_project",
'static_data_name' : "add-del-layers",
'static_data_template' : '{% include "recipe_btn.html" %}'}
if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'):
build_col['static_data_template'] = '{% include "recipe_add_btn.html" %}'
def get_context_data(self, **kwargs):
project = Project.objects.get(pk=kwargs['pid'])
context = super(RecipesTable, self).get_context_data(**kwargs)
context['project'] = project
context['projectlayers'] = [player.layercommit.id for player in ProjectLayer.objects.filter(project=context['project'])]
return context
def setup_filters(self, *args, **kwargs):
table_filter = TableFilter(
'in_current_project',
'Filter by project recipes'
)
in_project_action = TableFilterActionToggle(
'in_project',
'Recipes provided by layers added to this project',
ProjectFilters.in_project(self.project_layers)
)
not_in_project_action = TableFilterActionToggle(
'not_in_project',
'Recipes provided by layers not added to this project',
ProjectFilters.not_in_project(self.project_layers)
)
table_filter.add_action(in_project_action)
table_filter.add_action(not_in_project_action)
self.add_filter(table_filter)
def setup_queryset(self, *args, **kwargs):
prj = Project.objects.get(pk = kwargs['pid'])
# Project layers used by the filters
self.project_layers = prj.get_project_layer_versions(pk=True)
# Project layers used to switch the button states
self.static_context_extra['current_layers'] = self.project_layers
self.queryset = prj.get_all_compatible_recipes()
def setup_columns(self, *args, **kwargs):
self.add_column(title="Version",
hidden=False,
field_name="version")
self.add_column(title="Description",
field_name="get_description_or_summary")
recipe_file_template = '''
{{data.file_path}}
'''
self.add_column(title="Recipe file",
help_text="Path to the recipe .bb file",
hidden=True,
static_data_name="recipe-file",
static_data_template=recipe_file_template)
self.add_column(title="Section",
help_text="The section in which recipes should be categorized",
hidden=True,
orderable=True,
field_name="section")
layer_link_template = '''
{{data.layer_version.layer.name}}
'''
self.add_column(title="Layer",
help_text="The name of the layer providing the recipe",
orderable=True,
static_data_name="layer_version__layer__name",
static_data_template=layer_link_template)
self.add_column(title="License",
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",
hidden=True,
orderable=True,
field_name="license")
revision_link_template = '''
{% if data.layer_version.layer.local_source_dir %}
Not applicable
{% else %}
{{data.layer_version.get_vcs_reference}}
{% endif %}
'''
self.add_column(title="Git revision",
hidden=True,
static_data_name="layer_version__get_vcs_reference",
static_data_template=revision_link_template)
class LayerRecipesTable(RecipesTable):
""" Smaller version of the Recipes table for use in layer details """
def __init__(self, *args, **kwargs):
super(LayerRecipesTable, self).__init__(*args, **kwargs)
self.default_orderby = "name"
def get_context_data(self, **kwargs):
context = super(LayerRecipesTable, self).get_context_data(**kwargs)
context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
return context
def setup_queryset(self, *args, **kwargs):
self.queryset = \
Recipe.objects.filter(layer_version__pk=int(kwargs['layerid']))
self.queryset = self.queryset.order_by(self.default_orderby)
self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) & Q(layercommit=kwargs['layerid'])).count()
def setup_columns(self, *args, **kwargs):
self.add_column(title="Recipe",
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",
hideable=False,
orderable=True,
field_name="name")
self.add_column(title="Version",
field_name="version")
self.add_column(title="Description",
field_name="get_description_or_summary")
build_recipe_template = '''
Build recipe
'''
self.add_column(title="Build recipe",
static_data_name="add-del-layers",
static_data_template=build_recipe_template)
class CustomImagesTable(ToasterTable):
""" Table to display your custom images """
def __init__(self, *args, **kwargs):
super(CustomImagesTable, self).__init__(*args, **kwargs)
self.title = "Custom images"
self.default_orderby = "name"
def get_context_data(self, **kwargs):
context = super(CustomImagesTable, self).get_context_data(**kwargs)
empty_state_template = '''
You have not created any custom images yet.
Create your first custom image
'''
context['empty_state'] = self.render_static_data(empty_state_template,
kwargs)
project = Project.objects.get(pk=kwargs['pid'])
# TODO put project into the ToasterTable base class
context['project'] = project
return context
def setup_queryset(self, *args, **kwargs):
prj = Project.objects.get(pk = kwargs['pid'])
self.queryset = CustomImageRecipe.objects.filter(project=prj)
self.queryset = self.queryset.order_by(self.default_orderby)
def setup_columns(self, *args, **kwargs):
name_link_template = '''
{{data.name}}
'''
self.add_column(title="Custom image",
hideable=False,
orderable=True,
field_name="name",
static_data_name="name",
static_data_template=name_link_template)
recipe_file_template = '''
{% if data.get_base_recipe_file %}
{{data.name}}_{{data.version}}.bb
{% endif %}'''
self.add_column(title="Recipe file",
static_data_name='recipe_file_download',
static_data_template=recipe_file_template)
approx_packages_template = '''
{% if data.get_all_packages.count > 0 %}
{{data.get_all_packages.count}}
{% endif %}'''
self.add_column(title="Packages",
static_data_name='approx_packages',
static_data_template=approx_packages_template)
build_btn_template = '''
'''
self.add_column(title="Build",
hideable=False,
static_data_name='build_custom_img',
static_data_template=build_btn_template)
class ImageRecipesTable(RecipesTable):
""" A subset of the recipes table which displayed just image recipes """
def __init__(self, *args, **kwargs):
super(ImageRecipesTable, self).__init__(*args, **kwargs)
self.title = "Compatible image recipes"
self.default_orderby = "name"
def setup_queryset(self, *args, **kwargs):
super(ImageRecipesTable, self).setup_queryset(*args, **kwargs)
custom_image_recipes = CustomImageRecipe.objects.filter(
project=kwargs['pid'])
self.queryset = self.queryset.filter(
Q(is_image=True) & ~Q(pk__in=custom_image_recipes))
self.queryset = self.queryset.order_by(self.default_orderby)
def setup_columns(self, *args, **kwargs):
name_link_template = '''
{{data.name}}
'''
self.add_column(title="Image recipe",
help_text="When you build an image recipe, you get an "
"image: a root file system you can"
"deploy to a machine",
hideable=False,
orderable=True,
static_data_name="name",
static_data_template=name_link_template,
field_name="name")
super(ImageRecipesTable, self).setup_columns(*args, **kwargs)
self.add_column(**RecipesTable.build_col)
class NewCustomImagesTable(ImageRecipesTable):
""" Table which displays Images recipes which can be customised """
def __init__(self, *args, **kwargs):
super(NewCustomImagesTable, self).__init__(*args, **kwargs)
self.title = "Select the image recipe you want to customise"
def setup_queryset(self, *args, **kwargs):
super(ImageRecipesTable, self).setup_queryset(*args, **kwargs)
prj = Project.objects.get(pk = kwargs['pid'])
self.static_context_extra['current_layers'] = \
prj.get_project_layer_versions(pk=True)
self.queryset = self.queryset.filter(is_image=True)
def setup_columns(self, *args, **kwargs):
name_link_template = '''
{{data.name}}
'''
self.add_column(title="Image recipe",
help_text="When you build an image recipe, you get an "
"image: a root file system you can"
"deploy to a machine",
hideable=False,
orderable=True,
static_data_name="name",
static_data_template=name_link_template,
field_name="name")
super(ImageRecipesTable, self).setup_columns(*args, **kwargs)
self.add_column(title="Customise",
hideable=False,
filter_name="in_current_project",
static_data_name="customise-or-add-recipe",
static_data_template='{% include "customise_btn.html" %}')
class SoftwareRecipesTable(RecipesTable):
""" Displays just the software recipes """
def __init__(self, *args, **kwargs):
super(SoftwareRecipesTable, self).__init__(*args, **kwargs)
self.title = "Compatible software recipes"
self.default_orderby = "name"
def setup_queryset(self, *args, **kwargs):
super(SoftwareRecipesTable, self).setup_queryset(*args, **kwargs)
self.queryset = self.queryset.filter(is_image=False)
self.queryset = self.queryset.order_by(self.default_orderby)
def setup_columns(self, *args, **kwargs):
self.add_column(title="Software recipe",
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",
hideable=False,
orderable=True,
field_name="name")
super(SoftwareRecipesTable, self).setup_columns(*args, **kwargs)
self.add_column(**RecipesTable.build_col)
class PackagesTable(ToasterTable):
""" Table to display the packages in a recipe from it's last successful
build"""
def __init__(self, *args, **kwargs):
super(PackagesTable, self).__init__(*args, **kwargs)
self.title = "Packages included"
self.packages = None
self.default_orderby = "name"
def create_package_list(self, recipe, project_id):
"""Creates a list of packages for the specified recipe by looking for
the last SUCCEEDED build of ther recipe"""
target = Target.objects.filter(Q(target=recipe.name) &
Q(build__project_id=project_id) &
Q(build__outcome=Build.SUCCEEDED)
).last()
if target:
pkgs = target.target_installed_package_set.values_list('package',
flat=True)
return Package.objects.filter(pk__in=pkgs)
# Target/recipe never successfully built so empty queryset
return Package.objects.none()
def get_context_data(self, **kwargs):
"""Context for rendering the sidebar and other items on the recipe
details page """
context = super(PackagesTable, self).get_context_data(**kwargs)
recipe = Recipe.objects.get(pk=kwargs['recipe_id'])
project = Project.objects.get(pk=kwargs['pid'])
in_project = (recipe.layer_version.pk in
project.get_project_layer_versions(pk=True))
packages = self.create_package_list(recipe, project.pk)
context.update({'project': project,
'recipe' : recipe,
'packages': packages,
'approx_pkg_size' : packages.aggregate(Sum('size')),
'in_project' : in_project,
})
return context
def setup_queryset(self, *args, **kwargs):
recipe = Recipe.objects.get(pk=kwargs['recipe_id'])
self.static_context_extra['target_name'] = recipe.name
self.queryset = self.create_package_list(recipe, kwargs['pid'])
self.queryset = self.queryset.order_by('name')
def setup_columns(self, *args, **kwargs):
self.add_column(title="Package",
hideable=False,
orderable=True,
field_name="name")
self.add_column(title="Package Version",
field_name="version",
hideable=False)
self.add_column(title="Approx Size",
orderable=True,
field_name="size",
static_data_name="size",
static_data_template="{% load projecttags %} \
{{data.size|filtered_filesizeformat}}")
self.add_column(title="License",
field_name="license",
orderable=True,
hidden=True)
self.add_column(title="Dependencies",
static_data_name="dependencies",
static_data_template='\
{% include "snippets/pkg_dependencies_popover.html" %}')
self.add_column(title="Reverse dependencies",
static_data_name="reverse_dependencies",
static_data_template='\
{% include "snippets/pkg_revdependencies_popover.html" %}',
hidden=True)
self.add_column(title="Recipe",
field_name="recipe__name",
orderable=True,
hidden=True)
self.add_column(title="Recipe version",
field_name="recipe__version",
hidden=True)
class SelectPackagesTable(PackagesTable):
""" Table to display the packages to add and remove from an image """
def __init__(self, *args, **kwargs):
super(SelectPackagesTable, self).__init__(*args, **kwargs)
self.title = "Add | Remove packages"
def setup_queryset(self, *args, **kwargs):
self.cust_recipe =\
CustomImageRecipe.objects.get(pk=kwargs['custrecipeid'])
prj = Project.objects.get(pk = kwargs['pid'])
current_packages = self.cust_recipe.get_all_packages()
current_recipes = prj.get_available_recipes()
# only show packages where recipes->layers are in the project
self.queryset = CustomImagePackage.objects.filter(
~Q(recipe=None) &
Q(recipe__in=current_recipes))
self.queryset = self.queryset.order_by('name')
# This target is the target used to work out which group of dependences
# to display, if we've built the custom image we use it otherwise we
# can use the based recipe instead
if prj.build_set.filter(target__target=self.cust_recipe.name).count()\
> 0:
self.static_context_extra['target_name'] = self.cust_recipe.name
else:
self.static_context_extra['target_name'] =\
Package_DependencyManager.TARGET_LATEST
self.static_context_extra['recipe_id'] = kwargs['custrecipeid']
self.static_context_extra['current_packages'] = \
current_packages.values_list('pk', flat=True)
def get_context_data(self, **kwargs):
# to reuse the Super class map the custrecipeid to the recipe_id
kwargs['recipe_id'] = kwargs['custrecipeid']
context = super(SelectPackagesTable, self).get_context_data(**kwargs)
custom_recipe = \
CustomImageRecipe.objects.get(pk=kwargs['custrecipeid'])
context['recipe'] = custom_recipe
context['approx_pkg_size'] = \
custom_recipe.get_all_packages().aggregate(Sum('size'))
return context
def setup_columns(self, *args, **kwargs):
super(SelectPackagesTable, self).setup_columns(*args, **kwargs)
add_remove_template = '{% include "pkg_add_rm_btn.html" %}'
self.add_column(title="Add | Remove",
hideable=False,
help_text="Use the add and remove buttons to modify "
"the package content of your custom image",
static_data_name="add_rm_pkg_btn",
static_data_template=add_remove_template,
filter_name='in_current_image_filter')
def setup_filters(self, *args, **kwargs):
in_current_image_filter = TableFilter(
'in_current_image_filter',
'Filter by added packages'
)
in_image_action = TableFilterActionToggle(
'in_image',
'Packages in %s' % self.cust_recipe.name,
Q(pk__in=self.static_context_extra['current_packages'])
)
not_in_image_action = TableFilterActionToggle(
'not_in_image',
'Packages not added to %s' % self.cust_recipe.name,
~Q(pk__in=self.static_context_extra['current_packages'])
)
in_current_image_filter.add_action(in_image_action)
in_current_image_filter.add_action(not_in_image_action)
self.add_filter(in_current_image_filter)
class ProjectsTable(ToasterTable):
"""Table of projects in Toaster"""
def __init__(self, *args, **kwargs):
super(ProjectsTable, self).__init__(*args, **kwargs)
self.default_orderby = '-updated'
self.title = 'All projects'
self.static_context_extra['Build'] = Build
def get_context_data(self, **kwargs):
return super(ProjectsTable, self).get_context_data(**kwargs)
def setup_queryset(self, *args, **kwargs):
queryset = Project.objects.all()
# annotate each project with its number of builds
queryset = queryset.annotate(num_builds=Count('build'))
# exclude the command line builds project if it has no builds
q_default_with_builds = Q(is_default=True) & Q(num_builds__gt=0)
queryset = queryset.filter(Q(is_default=False) |
q_default_with_builds)
# order rows
queryset = queryset.order_by(self.default_orderby)
self.queryset = queryset
# columns: last activity on (updated) - DEFAULT, project (name), release,
# machine, number of builds, last build outcome, recipe (name), errors,
# warnings, image files
def setup_columns(self, *args, **kwargs):
name_template = '''
{% load project_url_tag %}
{{data.name}}
'''
last_activity_on_template = '''
{% load project_url_tag %}
{{data.updated | date:"d/m/y H:i"}}
'''
release_template = '''
{% if data.release %}
{{data.release.name}}
{% elif data.is_default %}
Not applicable
{% else %}
No release available
{% endif %}
'''
machine_template = '''
{% if data.is_default %}
Not applicable
{% else %}
{{data.get_current_machine_name}}
{% endif %}
'''
number_of_builds_template = '''
{% if data.get_number_of_builds > 0 %}
{{data.get_number_of_builds}}
{% endif %}
'''
last_build_outcome_template = '''
{% if data.get_number_of_builds > 0 %}
{% if data.get_last_outcome == extra.Build.SUCCEEDED %}
{% elif data.get_last_outcome == extra.Build.FAILED %}
{% endif %}
{% endif %}
'''
recipe_template = '''
{% if data.get_number_of_builds > 0 %}
{{data.get_last_target}}
{% endif %}
'''
errors_template = '''
{% if data.get_number_of_builds > 0 and data.get_last_errors > 0 %}
{{data.get_last_errors}} error{{data.get_last_errors | pluralize}}
{% endif %}
'''
warnings_template = '''
{% if data.get_number_of_builds > 0 and data.get_last_warnings > 0 %}
{{data.get_last_warnings}} warning{{data.get_last_warnings | pluralize}}
{% endif %}
'''
image_files_template = '''
{% if data.get_number_of_builds > 0 and data.get_last_outcome == extra.Build.SUCCEEDED %}
{{data.get_last_build_extensions}}
{% endif %}
'''
self.add_column(title='Project',
hideable=False,
orderable=True,
static_data_name='name',
static_data_template=name_template)
self.add_column(title='Last activity on',
help_text='Starting date and time of the \
last project build. If the project has no \
builds, this shows the date the project was \
created.',
hideable=False,
orderable=True,
static_data_name='updated',
static_data_template=last_activity_on_template)
self.add_column(title='Release',
help_text='The version of the build system used by \
the project',
hideable=False,
orderable=True,
static_data_name='release',
static_data_template=release_template)
self.add_column(title='Machine',
help_text='The hardware currently selected for the \
project',
hideable=False,
orderable=False,
static_data_name='machine',
static_data_template=machine_template)
self.add_column(title='Builds',
help_text='The number of builds which have been run \
for the project',
hideable=False,
orderable=False,
static_data_name='number_of_builds',
static_data_template=number_of_builds_template)
self.add_column(title='Last build outcome',
help_text='Indicates whether the last project build \
completed successfully or failed',
hideable=True,
orderable=False,
static_data_name='last_build_outcome',
static_data_template=last_build_outcome_template)
self.add_column(title='Recipe',
help_text='The last recipe which was built in this \
project',
hideable=True,
orderable=False,
static_data_name='recipe_name',
static_data_template=recipe_template)
self.add_column(title='Errors',
help_text='The number of errors encountered during \
the last project build (if any)',
hideable=True,
orderable=False,
static_data_name='errors',
static_data_template=errors_template)
self.add_column(title='Warnings',
help_text='The number of warnings encountered during \
the last project build (if any)',
hideable=True,
hidden=True,
orderable=False,
static_data_name='warnings',
static_data_template=warnings_template)
self.add_column(title='Image files',
help_text='The root file system types produced by \
the last project build',
hideable=True,
hidden=True,
orderable=False,
static_data_name='image_files',
static_data_template=image_files_template)
class BuildsTable(ToasterTable):
"""Table of builds in Toaster"""
def __init__(self, *args, **kwargs):
super(BuildsTable, self).__init__(*args, **kwargs)
self.default_orderby = '-completed_on'
self.static_context_extra['Build'] = Build
self.static_context_extra['Task'] = Task
# attributes that are overridden in subclasses
# title for the page
self.title = ''
# 'project' or 'all'; determines how the mrb (most recent builds)
# section is displayed
self.mrb_type = ''
def get_builds(self):
"""
overridden in ProjectBuildsTable to return builds for a
single project
"""
return Build.objects.all()
def get_context_data(self, **kwargs):
context = super(BuildsTable, self).get_context_data(**kwargs)
# should be set in subclasses
context['mru'] = []
context['mrb_type'] = self.mrb_type
return context
def setup_queryset(self, *args, **kwargs):
"""
The queryset is annotated so that it can be sorted by number of
errors and number of warnings; but note that the criteria for
finding the log messages to populate these fields should match those
used in the Build model (orm/models.py) to populate the errors and
warnings properties
"""
queryset = self.get_builds()
# Don't include in progress builds pr cancelled builds
queryset = queryset.exclude(Q(outcome=Build.IN_PROGRESS) |
Q(outcome=Build.CANCELLED))
# sort
queryset = queryset.order_by(self.default_orderby)
# annotate with number of ERROR, EXCEPTION and CRITICAL log messages
criteria = (Q(logmessage__level=LogMessage.ERROR) |
Q(logmessage__level=LogMessage.EXCEPTION) |
Q(logmessage__level=LogMessage.CRITICAL))
queryset = queryset.annotate(
errors_no=Count(
Case(
When(criteria, then=Value(1)),
output_field=IntegerField()
)
)
)
# annotate with number of WARNING log messages
queryset = queryset.annotate(
warnings_no=Count(
Case(
When(logmessage__level=LogMessage.WARNING, then=Value(1)),
output_field=IntegerField()
)
)
)
self.queryset = queryset
def setup_columns(self, *args, **kwargs):
outcome_template = '''
{% if data.outcome == data.SUCCEEDED %}
{% elif data.outcome == data.FAILED %}
{% endif %}
{% if data.cooker_log_path %}
{% endif %}
'''
recipe_template = '''
{% for target_label in data.target_labels %}
{{target_label}}
conf/distro/{{data.name}}.conf
{% if 'None' not in data.get_vcs_distro_file_link_url %}{% endif %}'''
self.add_column(title="Distro file",
hidden=True,
static_data_name="templatefile",
static_data_template=distro_file_template)
self.add_column(title="Select",
help_text="Sets the selected distro to the project",
hideable=False,
filter_name="in_current_project",
static_data_name="add-del-layers",
static_data_template='{% include "distro_btn.html" %}')