blob: c712b06a6e840bd3ba53b46be4bc732613b42e0b [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) 2013 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
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022
Patrick Williamsc0f7c042017-02-23 20:41:17 -060023import re
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025from django.db.models import F, Q, Sum
26from django.db import IntegrityError
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050027from django.shortcuts import render, redirect, get_object_or_404
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080028from django.utils.http import urlencode
Patrick Williamsc0f7c042017-02-23 20:41:17 -060029from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe
30from orm.models import LogMessage, Variable, Package_Dependency, Package
31from orm.models import Task_Dependency, Package_File
32from orm.models import Target_Installed_Package, Target_File
33from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File
Patrick Williamsf1e5d692016-03-30 15:21:19 -050034from orm.models import BitbakeVersion, CustomImageRecipe
Patrick Williamsc0f7c042017-02-23 20:41:17 -060035
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036from django.core.urlresolvers import reverse, resolve
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050037from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
Brad Bishopd7bf8c12018-02-25 22:55:05 -050039from django.http import HttpResponseNotFound, JsonResponse
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040from django.utils import timezone
Patrick Williamsd7e96312015-09-22 08:09:05 -050041from datetime import timedelta, datetime
Patrick Williamsc124f4f2015-09-15 14:41:29 -050042from toastergui.templatetags.projecttags import json as jsonfilter
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043from decimal import Decimal
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044import json
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050045import os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050046from os.path import dirname
Patrick Williamsf1e5d692016-03-30 15:21:19 -050047import mimetypes
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048
49import logging
50
51logger = logging.getLogger("toaster")
52
Brad Bishopd7bf8c12018-02-25 22:55:05 -050053# Project creation and managed build enable
54project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER'))
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080055is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC'))
Patrick Williamsc0f7c042017-02-23 20:41:17 -060056
Patrick Williamsd7e96312015-09-22 08:09:05 -050057class MimeTypeFinder(object):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050058 # setting this to False enables additional non-standard mimetypes
59 # to be included in the guess
60 _strict = False
Patrick Williamsd7e96312015-09-22 08:09:05 -050061
Patrick Williamsf1e5d692016-03-30 15:21:19 -050062 # returns the mimetype for a file path as a string,
63 # or 'application/octet-stream' if the type couldn't be guessed
Patrick Williamsd7e96312015-09-22 08:09:05 -050064 @classmethod
65 def get_mimetype(self, path):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050066 guess = mimetypes.guess_type(path, self._strict)
67 guessed_type = guess[0]
68 if guessed_type == None:
69 guessed_type = 'application/octet-stream'
70 return guessed_type
Patrick Williamsd7e96312015-09-22 08:09:05 -050071
Brad Bishopd7bf8c12018-02-25 22:55:05 -050072# single point to add global values into the context before rendering
73def toaster_render(request, page, context):
74 context['project_enable'] = project_enable
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080075 context['project_specific'] = is_project_specific
Brad Bishopd7bf8c12018-02-25 22:55:05 -050076 return render(request, page, context)
77
78
Patrick Williamsc124f4f2015-09-15 14:41:29 -050079# all new sessions should come through the landing page;
80# determine in which mode we are running in, and redirect appropriately
81def landing(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -050082 # in build mode, we redirect to the command-line builds page
83 # if there are any builds for the default (cli builds) project
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050084 default_project = Project.objects.get_or_create_default_project()
Patrick Williamsf1e5d692016-03-30 15:21:19 -050085 default_project_builds = Build.objects.filter(project = default_project)
86
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087 # we only redirect to projects page if there is a user-generated project
Patrick Williamsf1e5d692016-03-30 15:21:19 -050088 num_builds = Build.objects.all().count()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050089 user_projects = Project.objects.filter(is_default = False)
90 has_user_project = user_projects.count() > 0
91
Patrick Williamsf1e5d692016-03-30 15:21:19 -050092 if num_builds == 0 and has_user_project:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050093 return redirect(reverse('all-projects'), permanent = False)
94
Patrick Williamsf1e5d692016-03-30 15:21:19 -050095 if num_builds > 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050096 return redirect(reverse('all-builds'), permanent = False)
97
98 context = {'lvs_nos' : Layer_Version.objects.all().count()}
99
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500100 return toaster_render(request, 'landing.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500101
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500102def objtojson(obj):
103 from django.db.models.query import QuerySet
104 from django.db.models import Model
105
106 if isinstance(obj, datetime):
107 return obj.isoformat()
108 elif isinstance(obj, timedelta):
109 return obj.total_seconds()
110 elif isinstance(obj, QuerySet) or isinstance(obj, set):
111 return list(obj)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500112 elif isinstance(obj, Decimal):
113 return str(obj)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500114 elif type(obj).__name__ == "RelatedManager":
115 return [x.pk for x in obj.all()]
116 elif hasattr( obj, '__dict__') and isinstance(obj, Model):
117 d = obj.__dict__
118 nd = dict(d)
119 for di in d.keys():
120 if di.startswith("_"):
121 del nd[di]
122 elif isinstance(d[di], Model):
123 nd[di] = d[di].pk
124 elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
125 nd[di] = getattr(obj, "get_%s_display" % di)()
126 return nd
127 elif isinstance( obj, type(lambda x:x)):
128 import inspect
129 return inspect.getsourcelines(obj)[0]
130 else:
131 raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
132
133
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500134def _lv_to_dict(prj, x = None):
135 if x is None:
136 def wrapper(x):
137 return _lv_to_dict(prj, x)
138 return wrapper
139
140 return {"id": x.pk,
141 "name": x.layer.name,
142 "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()),
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600143 "detail": "(%s" % x.layer.vcs_url + (")" if x.release == None else " | "+x.get_vcs_reference()+")"),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500144 "giturl": x.layer.vcs_url,
145 "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
146 "revision" : x.get_vcs_reference(),
147 }
148
149
150def _build_page_range(paginator, index = 1):
151 try:
152 page = paginator.page(index)
153 except PageNotAnInteger:
154 page = paginator.page(1)
155 except EmptyPage:
156 page = paginator.page(paginator.num_pages)
157
158
159 page.page_range = [page.number]
160 crt_range = 0
161 for i in range(1,5):
162 if (page.number + i) <= paginator.num_pages:
163 page.page_range = page.page_range + [ page.number + i]
164 crt_range +=1
165 if (page.number - i) > 0:
166 page.page_range = [page.number -i] + page.page_range
167 crt_range +=1
168 if crt_range == 4:
169 break
170 return page
171
172
173def _verify_parameters(g, mandatory_parameters):
174 miss = []
175 for mp in mandatory_parameters:
176 if not mp in g:
177 miss.append(mp)
178 if len(miss):
179 return miss
180 return None
181
182def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600183 try:
184 from urllib import unquote, urlencode
185 except ImportError:
186 from urllib.parse import unquote, urlencode
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500187 url = reverse(view, kwargs=kwargs)
188 params = {}
189 for i in g:
190 params[i] = g[i]
191 for i in mandatory_parameters:
192 if not i in params:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600193 params[i] = unquote(str(mandatory_parameters[i]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500194
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600195 return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196
197class RedirectException(Exception):
198 def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
199 super(RedirectException, self).__init__()
200 self.view = view
201 self.g = g
202 self.mandatory_parameters = mandatory_parameters
203 self.oargs = args
204 self.okwargs = kwargs
205
206 def get_redirect_response(self):
207 return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
208
209FIELD_SEPARATOR = ":"
210AND_VALUE_SEPARATOR = "!"
211OR_VALUE_SEPARATOR = "|"
212DESCENDING = "-"
213
214def __get_q_for_val(name, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600215 if "OR" in value or "AND" in value:
216 result = None
217 for x in value.split("OR"):
218 x = __get_q_for_val(name, x)
219 result = result | x if result else x
220 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500221 if "AND" in value:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600222 result = None
223 for x in value.split("AND"):
224 x = __get_q_for_val(name, x)
225 result = result & x if result else x
226 return result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500227 if value.startswith("NOT"):
228 value = value[3:]
229 if value == 'None':
230 value = None
231 kwargs = { name : value }
232 return ~Q(**kwargs)
233 else:
234 if value == 'None':
235 value = None
236 kwargs = { name : value }
237 return Q(**kwargs)
238
239def _get_filtering_query(filter_string):
240
241 search_terms = filter_string.split(FIELD_SEPARATOR)
242 and_keys = search_terms[0].split(AND_VALUE_SEPARATOR)
243 and_values = search_terms[1].split(AND_VALUE_SEPARATOR)
244
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600245 and_query = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500246 for kv in zip(and_keys, and_values):
247 or_keys = kv[0].split(OR_VALUE_SEPARATOR)
248 or_values = kv[1].split(OR_VALUE_SEPARATOR)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600249 query = None
250 for key, val in zip(or_keys, or_values):
251 x = __get_q_for_val(key, val)
252 query = query | x if query else x
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500253
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600254 and_query = and_query & query if and_query else query
255
256 return and_query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500257
258def _get_toggle_order(request, orderkey, toggle_reverse = False):
259 if toggle_reverse:
260 return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
261 else:
262 return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
263
264def _get_toggle_order_icon(request, orderkey):
265 if request.GET.get('orderby', "") == "%s:+"%orderkey:
266 return "down"
267 elif request.GET.get('orderby', "") == "%s:-"%orderkey:
268 return "up"
269 else:
270 return None
271
272# we check that the input comes in a valid form that we can recognize
273def _validate_input(field_input, model):
274
275 invalid = None
276
277 if field_input:
278 field_input_list = field_input.split(FIELD_SEPARATOR)
279
280 # Check we have only one colon
281 if len(field_input_list) != 2:
282 invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list)
283 return None, invalid
284
285 # Check we have an equal number of terms both sides of the colon
286 if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)):
287 invalid = "Not all arg names got values"
288 return None, invalid + str(field_input_list)
289
290 # Check we are looking for a valid field
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500291 valid_fields = [f.name for f in model._meta.get_fields()]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292 for field in field_input_list[0].split(AND_VALUE_SEPARATOR):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600293 if True in [field.startswith(x) for x in valid_fields]:
294 break
295 else:
296 return None, (field, valid_fields)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500297
298 return field_input, invalid
299
300# uses search_allowed_fields in orm/models.py to create a search query
301# for these fields with the supplied input text
302def _get_search_results(search_term, queryset, model):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600303 search_object = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304 for st in search_term.split(" "):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305 queries = None
306 for field in model.search_allowed_fields:
307 query = Q(**{field + '__icontains': st})
308 queries = queries | query if queries else query
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600310 search_object = search_object & queries if search_object else queries
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500311 queryset = queryset.filter(search_object)
312
313 return queryset
314
315
316# function to extract the search/filter/ordering parameters from the request
317# it uses the request and the model to validate input for the filter and orderby values
318def _search_tuple(request, model):
319 ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
320 if invalid:
321 raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
322
323 filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
324 if invalid:
325 raise BaseException("Invalid filter " + str(invalid))
326
327 search_term = request.GET.get('search', '')
328 return (filter_string, search_term, ordering_string)
329
330
331# returns a lazy-evaluated queryset for a filter/search/order combination
332def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
333 if filter_string:
334 filter_query = _get_filtering_query(filter_string)
335 queryset = queryset.filter(filter_query)
336 else:
337 queryset = queryset.all()
338
339 if search_term:
340 queryset = _get_search_results(search_term, queryset, model)
341
342 if ordering_string:
343 column, order = ordering_string.split(':')
344 if column == re.sub('-','',ordering_secondary):
345 ordering_secondary=''
346 if order.lower() == DESCENDING:
347 column = '-' + column
348 if ordering_secondary:
349 queryset = queryset.order_by(column, ordering_secondary)
350 else:
351 queryset = queryset.order_by(column)
352
353 # insure only distinct records (e.g. from multiple search hits) are returned
354 return queryset.distinct()
355
356# returns the value of entries per page and the name of the applied sorting field.
357# if the value is given explicitly as a GET parameter it will be the first selected,
358# otherwise the cookie value will be used.
359def _get_parameters_values(request, default_count, default_order):
360 current_url = resolve(request.path_info).url_name
361 pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
362 orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
363 return (pagesize, orderby)
364
365
366# set cookies for parameters. this is usefull in case parameters are set
367# manually from the GET values of the link
368def _set_parameters_values(pagesize, orderby, request):
369 from django.core.urlresolvers import resolve
370 current_url = resolve(request.path_info).url_name
371 request.session['%s_count' % current_url] = pagesize
372 request.session['%s_orderby' % current_url] =orderby
373
374# date range: normalize GUI's dd/mm/yyyy to date object
375def _normalize_input_date(date_str,default):
376 date_str=re.sub('/', '-', date_str)
377 # accept dd/mm/yyyy to d/m/yy
378 try:
379 date_in = datetime.strptime(date_str, "%d-%m-%Y")
380 except ValueError:
381 # courtesy try with two digit year
382 try:
383 date_in = datetime.strptime(date_str, "%d-%m-%y")
384 except ValueError:
385 return default
386 date_in = date_in.replace(tzinfo=default.tzinfo)
387 return date_in
388
389# convert and normalize any received date range filter, for example:
390# "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to
391# "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02"
392def _modify_date_range_filter(filter_string):
393 # was the date range radio button selected?
394 if 0 > filter_string.find('_daterange'):
395 return filter_string,''
396 # normalize GUI dates to database format
397 filter_string = filter_string.replace('_daterange','').replace(':','!');
398 filter_list = filter_string.split('!');
399 if 4 != len(filter_list):
400 return filter_string
401 today = timezone.localtime(timezone.now())
402 date_id = filter_list[1]
403 date_from = _normalize_input_date(filter_list[2],today)
404 date_to = _normalize_input_date(filter_list[3],today)
405 # swap dates if manually set dates are out of order
406 if date_to < date_from:
407 date_to,date_from = date_from,date_to
408 # convert to strings, make 'date_to' inclusive by moving to begining of next day
409 date_from_str = date_from.strftime("%Y-%m-%d")
410 date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d")
411 filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str
412 daterange_selected = re.sub('__.*','', date_id)
413 return filter_string,daterange_selected
414
415def _add_daterange_context(queryset_all, request, daterange_list):
416 # calculate the exact begining of local today and yesterday
417 today_begin = timezone.localtime(timezone.now())
Patrick Williamsd7e96312015-09-22 08:09:05 -0500418 yesterday_begin = today_begin - timedelta(days=1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500419 # add daterange persistent
420 context_date = {}
421 context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y"))
422 context_date['last_date_to' ] = request.GET.get('last_date_to' ,context_date['last_date_from'])
423 # calculate the date ranges, avoid second sort for 'created'
424 # fetch the respective max range from the database
425 context_date['daterange_filter']=''
426 for key in daterange_list:
427 queryset_key = queryset_all.order_by(key)
428 try:
429 context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y")
430 except AttributeError:
431 context_date['dateMin_'+key]=timezone.localtime(timezone.now())
432 try:
433 context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y")
434 except AttributeError:
435 context_date['dateMax_'+key]=timezone.localtime(timezone.now())
436 return context_date,today_begin,yesterday_begin
437
438
439##
440# build dashboard for a single build, coming in as argument
441# Each build may contain multiple targets and each target
442# may generate multiple image files. display them all.
443#
444def builddashboard( request, build_id ):
445 template = "builddashboard.html"
446 if Build.objects.filter( pk=build_id ).count( ) == 0 :
447 return redirect( builds )
448 build = Build.objects.get( pk = build_id );
449 layerVersionId = Layer_Version.objects.filter( build = build_id );
450 recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
451 tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
452
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500453 # set up custom target list with computed package and image data
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600454 targets = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500455 ntargets = 0
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600456
457 # True if at least one target for this build has an SDK artifact
458 # or image file
459 has_artifacts = False
460
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500461 for t in tgts:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600462 elem = {}
463 elem['target'] = t
464
465 target_has_images = False
466 image_files = []
467
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500468 npkg = 0
469 pkgsz = 0
470 package = None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500471 # Chunk the query to avoid "too many SQL variables" error
472 package_set = t.target_installed_package_set.all()
473 package_set_len = len(package_set)
474 for ps_start in range(0,package_set_len,500):
475 ps_stop = min(ps_start+500,package_set_len)
476 for package in Package.objects.filter(id__in = [x.package_id for x in package_set[ps_start:ps_stop]]):
477 pkgsz = pkgsz + package.size
478 if package.installed_name:
479 npkg = npkg + 1
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600480 elem['npkg'] = npkg
481 elem['pkgsz'] = pkgsz
482 ti = Target_Image_File.objects.filter(target_id = t.id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500483 for i in ti:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600484 ndx = i.file_name.rfind('/')
485 if ndx < 0:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500486 ndx = 0;
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600487 f = i.file_name[ndx + 1:]
488 image_files.append({
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500489 'id': i.id,
490 'path': f,
491 'size': i.file_size,
492 'suffix': i.suffix
493 })
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600494 if len(image_files) > 0:
495 target_has_images = True
496 elem['targetHasImages'] = target_has_images
497
498 elem['imageFiles'] = image_files
499 elem['target_kernel_artifacts'] = t.targetkernelfile_set.all()
500
501 target_sdk_files = t.targetsdkfile_set.all()
502 target_sdk_artifacts_count = target_sdk_files.count()
503 elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count
504 elem['target_sdk_artifacts'] = target_sdk_files
505
506 if target_has_images or target_sdk_artifacts_count > 0:
507 has_artifacts = True
508
509 targets.append(elem)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500510
511 ##
512 # how many packages in this build - ignore anonymous ones
513 #
514
515 packageCount = 0
516 packages = Package.objects.filter( build_id = build_id )
517 for p in packages:
518 if ( p.installed_name ):
519 packageCount = packageCount + 1
520
521 logmessages = list(LogMessage.objects.filter( build = build_id ))
522
523 context = {
524 'build' : build,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500525 'project' : build.project,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600526 'hasArtifacts' : has_artifacts,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500527 'ntargets' : ntargets,
528 'targets' : targets,
529 'recipecount' : recipeCount,
530 'packagecount' : packageCount,
531 'logmessages' : logmessages,
532 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500533 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500534
535
536
537def generateCoveredList2( revlist = None ):
538 if not revlist:
539 revlist = []
540 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
541 while len(covered_list):
542 revlist = [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
543 if len(revlist) > 0:
544 return revlist
545
546 newlist = _find_task_revdep_list(covered_list)
547
548 revlist = list(set(revlist + newlist))
549 covered_list = [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
550 return revlist
551
552def task( request, build_id, task_id ):
553 template = "task.html"
554 tasks_list = Task.objects.filter( pk=task_id )
555 if tasks_list.count( ) == 0:
556 return redirect( builds )
557 task_object = tasks_list[ 0 ];
558 dependencies = sorted(
559 _find_task_dep( task_object ),
560 key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
561 reverse_dependencies = sorted(
562 _find_task_revdep( task_object ),
563 key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
564 coveredBy = '';
565 if ( task_object.outcome == Task.OUTCOME_COVERED ):
566# _list = generateCoveredList( task )
567 coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name)
568 log_head = ''
569 log_body = ''
570 if task_object.outcome == task_object.OUTCOME_FAILED:
571 pass
572
573 uri_list= [ ]
574 variables = Variable.objects.filter(build=build_id)
575 v=variables.filter(variable_name='SSTATE_DIR')
576 if v.count() > 0:
577 uri_list.append(v[0].variable_value)
578 v=variables.filter(variable_name='SSTATE_MIRRORS')
579 if (v.count() > 0):
580 for mirror in v[0].variable_value.split('\\n'):
581 s=re.sub('.* ','',mirror.strip(' \t\n\r'))
582 if len(s):
583 uri_list.append(s)
584
585 context = {
586 'build' : Build.objects.filter( pk = build_id )[ 0 ],
587 'object' : task_object,
588 'task' : task_object,
589 'covered_by' : coveredBy,
590 'deps' : dependencies,
591 'rdeps' : reverse_dependencies,
592 'log_head' : log_head,
593 'log_body' : log_body,
594 'showing_matches' : False,
595 'uri_list' : uri_list,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600596 'task_in_tasks_table_pg': int(task_object.order / 25) + 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500597 }
598 if request.GET.get( 'show_matches', "" ):
599 context[ 'showing_matches' ] = True
600 context[ 'matching_tasks' ] = Task.objects.filter(
601 sstate_checksum=task_object.sstate_checksum ).filter(
602 build__completed_on__lt=task_object.build.completed_on).exclude(
603 order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
604
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500605 return toaster_render( request, template, context )
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500606
607def recipe(request, build_id, recipe_id, active_tab="1"):
608 template = "recipe.html"
609 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
610 return redirect(builds)
611
612 recipe_object = Recipe.objects.get(pk=recipe_id)
613 layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id)
614 layer = Layer.objects.get(pk=layer_version.layer_id)
615 tasks_list = Task.objects.filter(recipe_id = recipe_id, build_id = build_id).exclude(order__isnull=True).exclude(task_name__endswith='_setscene').exclude(outcome=Task.OUTCOME_NA)
616 package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
617
618 if active_tab != '1' and active_tab != '3' and active_tab != '4' :
619 active_tab = '1'
620 tab_states = {'1': '', '3': '', '4': ''}
621 tab_states[active_tab] = 'active'
622
623 context = {
624 'build' : Build.objects.get(pk=build_id),
625 'object' : recipe_object,
626 'layer_version' : layer_version,
627 'layer' : layer,
628 'tasks' : tasks_list,
629 'package_count' : package_count,
630 'tab_states' : tab_states,
631 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500632 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633
634def recipe_packages(request, build_id, recipe_id):
635 template = "recipe_packages.html"
636 if Recipe.objects.filter(pk=recipe_id).count() == 0 :
637 return redirect(builds)
638
639 (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+')
640 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
641 retval = _verify_parameters( request.GET, mandatory_parameters )
642 if retval:
643 return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id)
644 (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
645
646 recipe_object = Recipe.objects.get(pk=recipe_id)
647 queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
648 package_count = queryset.count()
649 queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
650
651 packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
652
653 context = {
654 'build' : Build.objects.get(pk=build_id),
655 'recipe' : recipe_object,
656 'objects' : packages,
657 'object_count' : package_count,
658 'tablecols':[
659 {
660 'name':'Package',
661 'orderfield': _get_toggle_order(request,"name"),
662 'ordericon': _get_toggle_order_icon(request,"name"),
663 'orderkey': "name",
664 },
665 {
666 'name':'Version',
667 },
668 {
669 'name':'Size',
670 'orderfield': _get_toggle_order(request,"size", True),
671 'ordericon': _get_toggle_order_icon(request,"size"),
672 'orderkey': 'size',
673 'dclass': 'sizecol span2',
674 },
675 ]
676 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500677 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500678 _set_parameters_values(pagesize, orderby, request)
679 return response
680
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500681from django.core.serializers.json import DjangoJSONEncoder
682from django.http import HttpResponse
683def xhr_dirinfo(request, build_id, target_id):
684 top = request.GET.get('start', '/')
685 return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
686
687from django.utils.functional import Promise
688from django.utils.encoding import force_text
689class LazyEncoder(json.JSONEncoder):
690 def default(self, obj):
691 if isinstance(obj, Promise):
692 return force_text(obj)
693 return super(LazyEncoder, self).default(obj)
694
695from toastergui.templatetags.projecttags import filtered_filesizeformat
696import os
697def _get_dir_entries(build_id, target_id, start):
698 node_str = {
699 Target_File.ITYPE_REGULAR : '-',
700 Target_File.ITYPE_DIRECTORY : 'd',
701 Target_File.ITYPE_SYMLINK : 'l',
702 Target_File.ITYPE_SOCKET : 's',
703 Target_File.ITYPE_FIFO : 'p',
704 Target_File.ITYPE_CHARACTER : 'c',
705 Target_File.ITYPE_BLOCK : 'b',
706 }
707 response = []
708 objects = Target_File.objects.filter(target__exact=target_id, directory__path=start)
709 target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
710 for o in objects:
711 # exclude root inode '/'
712 if o.path == '/':
713 continue
714 try:
715 entry = {}
716 entry['parent'] = start
717 entry['name'] = os.path.basename(o.path)
718 entry['fullpath'] = o.path
719
720 # set defaults, not all dentries have packages
721 entry['installed_package'] = None
722 entry['package_id'] = None
723 entry['package'] = None
724 entry['link_to'] = None
725 if o.inodetype == Target_File.ITYPE_DIRECTORY:
726 entry['isdir'] = 1
727 # is there content in directory
728 entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
729 else:
730 entry['isdir'] = 0
731
732 # resolve the file to get the package from the resolved file
733 resolved_id = o.sym_target_id
734 resolved_path = o.path
735 if target_packages.count():
736 while resolved_id != "" and resolved_id != None:
737 tf = Target_File.objects.get(pk=resolved_id)
738 resolved_path = tf.path
739 resolved_id = tf.sym_target_id
740
741 thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
742 if thisfile.count():
743 p = Package.objects.get(pk=thisfile[0].package_id)
744 entry['installed_package'] = p.installed_name
745 entry['package_id'] = str(p.id)
746 entry['package'] = p.name
747 # don't use resolved path from above, show immediate link-to
748 if o.sym_target_id != "" and o.sym_target_id != None:
749 entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
750 entry['size'] = filtered_filesizeformat(o.size)
751 if entry['link_to'] != None:
752 entry['permission'] = node_str[o.inodetype] + o.permission
753 else:
754 entry['permission'] = node_str[o.inodetype] + o.permission
755 entry['owner'] = o.owner
756 entry['group'] = o.group
757 response.append(entry)
758
759 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600760 print("Exception ", e)
761 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500762
763 # sort by directories first, then by name
764 rsorted = sorted(response, key=lambda entry : entry['name'])
765 rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True)
766 return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
767
768def dirinfo(request, build_id, target_id, file_path=None):
769 template = "dirinfo.html"
770 objects = _get_dir_entries(build_id, target_id, '/')
771 packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
772 dir_list = None
773 if file_path != None:
774 """
775 Link from the included package detail file list page and is
776 requesting opening the dir info to a specific file path.
777 Provide the list of directories to expand and the full path to
778 highlight in the page.
779 """
780 # Aassume target's path separator matches host's, that is, os.sep
781 sep = os.sep
782 dir_list = []
783 head = file_path
784 while head != sep:
785 (head, tail) = os.path.split(head)
786 if head != sep:
787 dir_list.insert(0, head)
788
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500789 build = Build.objects.get(pk=build_id)
790
791 context = { 'build': build,
792 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500793 'target': Target.objects.get(pk=target_id),
794 'packages_sum': packages_sum['installed_size__sum'],
795 'objects': objects,
796 'dir_list': dir_list,
797 'file_path': file_path,
798 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500799 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500800
801def _find_task_dep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600802 tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0)
803 tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on")
804 return [x.depends_on for x in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500805
806def _find_task_revdep(task_object):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600807 tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0)
808 tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
809
810 # exclude self-dependencies to prevent infinite dependency loop
811 # in generateCoveredList2()
812 tdeps = tdeps.exclude(task=task_object)
813
814 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500815
816def _find_task_revdep_list(tasklist):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600817 tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0)
818 tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
819
820 # exclude self-dependencies to prevent infinite dependency loop
821 # in generateCoveredList2()
822 tdeps = tdeps.exclude(task=F('depends_on'))
823
824 return [tdep.task for tdep in tdeps]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500825
826def _find_task_provider(task_object):
827 task_revdeps = _find_task_revdep(task_object)
828 for tr in task_revdeps:
829 if tr.outcome != Task.OUTCOME_COVERED:
830 return tr
831 for tr in task_revdeps:
832 trc = _find_task_provider(tr)
833 if trc is not None:
834 return trc
835 return None
836
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500837def configuration(request, build_id):
838 template = 'configuration.html'
839
840 var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
841 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
842 context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
843 .values_list('variable_name', 'variable_value'))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500844 build = Build.objects.get(pk=build_id)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500845 context.update({'objectname': 'configuration',
846 'object_search_display':'variables',
847 'filter_search_display':'variables',
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500848 'build': build,
849 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500850 'targets': Target.objects.filter(build=build_id)})
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500851 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500852
853
854def configvars(request, build_id):
855 template = 'configvars.html'
856 (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
857 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
858 retval = _verify_parameters( request.GET, mandatory_parameters )
859 (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
860 if retval:
861 # if new search, clear the default filter
862 if search_term and len(search_term):
863 mandatory_parameters['filter']=''
864 return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
865
866 queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
867 queryset_with_search = _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
868 queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
869 # remove records where the value is empty AND there are no history files
870 queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
871
872 variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
873
874 # show all matching files (not just the last one)
875 file_filter= search_term + ":"
876 if filter_string.find('/conf/') > 0:
877 file_filter += 'conf/(local|bblayers).conf'
878 if filter_string.find('conf/machine/') > 0:
879 file_filter += 'conf/machine/'
880 if filter_string.find('conf/distro/') > 0:
881 file_filter += 'conf/distro/'
882 if filter_string.find('/bitbake.conf') > 0:
883 file_filter += '/bitbake.conf'
884 build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
885
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500886 build = Build.objects.get(pk=build_id)
887
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500888 context = {
889 'objectname': 'configvars',
890 'object_search_display':'BitBake variables',
891 'filter_search_display':'variables',
892 'file_filter': file_filter,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500893 'build': build,
894 'project': build.project,
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500895 'objects' : variables,
896 'total_count':queryset_with_search.count(),
897 'default_orderby' : 'variable_name:+',
898 'search_term':search_term,
899 # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
900 'tablecols' : [
901 {'name': 'Variable',
902 'qhelp': "BitBake is a generic task executor that considers a list of tasks with dependencies and handles metadata that consists of variables in a certain format that get passed to the tasks",
903 'orderfield': _get_toggle_order(request, "variable_name"),
904 'ordericon':_get_toggle_order_icon(request, "variable_name"),
905 },
906 {'name': 'Value',
907 'qhelp': "The value assigned to the variable",
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500908 },
909 {'name': 'Set in file',
910 'qhelp': "The last configuration file that touched the variable value",
911 'clclass': 'file', 'hidden' : 0,
912 'orderkey' : 'vhistory__file_name',
913 'filter' : {
914 'class' : 'vhistory__file_name',
915 'label': 'Show:',
916 'options' : [
917 ('Local configuration variables', 'vhistory__file_name__contains:'+build_dir+'/conf/',queryset_with_search.filter(vhistory__file_name__contains=build_dir+'/conf/').count(), 'Select this filter to see variables set by the <code>local.conf</code> and <code>bblayers.conf</code> configuration files inside the <code>/build/conf/</code> directory'),
918 ('Machine configuration variables', 'vhistory__file_name__contains:conf/machine/',queryset_with_search.filter(vhistory__file_name__contains='conf/machine').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/machine/</code> directory'),
919 ('Distro configuration variables', 'vhistory__file_name__contains:conf/distro/',queryset_with_search.filter(vhistory__file_name__contains='conf/distro').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/distro/</code> directory'),
920 ('Layer configuration variables', 'vhistory__file_name__contains:conf/layer.conf',queryset_with_search.filter(vhistory__file_name__contains='conf/layer.conf').count(), 'Select this filter to see variables set by the <code>layer.conf</code> configuration file inside your layers'),
921 ('bitbake.conf variables', 'vhistory__file_name__contains:/bitbake.conf',queryset_with_search.filter(vhistory__file_name__contains='/bitbake.conf').count(), 'Select this filter to see variables set by the <code>bitbake.conf</code> configuration file'),
922 ]
923 },
924 },
925 {'name': 'Description',
926 'qhelp': "A brief explanation of the variable",
927 'clclass': 'description', 'hidden' : 0,
928 'dclass': "span4",
929 'filter' : {
930 'class' : 'description',
931 'label': 'Show:',
932 'options' : [
933 ('Variables with description', 'description__regex:.+', queryset_with_search.filter(description__regex='.+').count(), 'We provide descriptions for the most common BitBake variables. The list of descriptions lives in <code>meta/conf/documentation.conf</code>'),
934 ]
935 },
936 },
937 ],
938 }
939
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500940 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500941 _set_parameters_values(pagesize, orderby, request)
942 return response
943
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500944def bfile(request, build_id, package_id):
945 template = 'bfile.html'
946 files = Package_File.objects.filter(package = package_id)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500947 build = Build.objects.get(pk=build_id)
948 context = {
949 'build': build,
950 'project': build.project,
951 'objects' : files
952 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500953 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500954
955
956# A set of dependency types valid for both included and built package views
957OTHER_DEPENDS_BASE = [
958 Package_Dependency.TYPE_RSUGGESTS,
959 Package_Dependency.TYPE_RPROVIDES,
960 Package_Dependency.TYPE_RREPLACES,
961 Package_Dependency.TYPE_RCONFLICTS,
962 ]
963
964# value for invalid row id
965INVALID_KEY = -1
966
967"""
968Given a package id, target_id retrieves two sets of this image and package's
969dependencies. The return value is a dictionary consisting of two other
970lists: a list of 'runtime' dependencies, that is, having RDEPENDS
971values in source package's recipe, and a list of other dependencies, that is
972the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
973the RRECOMMENDS or TRECOMMENDS value.
974The lists are built in the sort order specified for the package runtime
975dependency views.
976"""
977def _get_package_dependencies(package_id, target_id = INVALID_KEY):
978 runtime_deps = []
979 other_deps = []
980 other_depends_types = OTHER_DEPENDS_BASE
981
982 if target_id != INVALID_KEY :
983 rdepends_type = Package_Dependency.TYPE_TRDEPENDS
984 other_depends_types += [Package_Dependency.TYPE_TRECOMMENDS]
985 else :
986 rdepends_type = Package_Dependency.TYPE_RDEPENDS
987 other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
988
989 package = Package.objects.get(pk=package_id)
990 if target_id != INVALID_KEY :
991 alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
992 else :
993 alldeps = package.package_dependencies_source.all()
994 for idep in alldeps:
995 dep_package = Package.objects.get(pk=idep.depends_on_id)
996 dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
997 if dep_package.version == '' :
998 version = ''
999 else :
1000 version = dep_package.version + "-" + dep_package.revision
1001 installed = False
1002 if target_id != INVALID_KEY :
1003 if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
1004 installed = True
1005 dep = {
1006 'name' : dep_package.name,
1007 'version' : version,
1008 'size' : dep_package.size,
1009 'dep_type' : idep.dep_type,
1010 'dep_type_display' : dep_entry[0].capitalize(),
1011 'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
1012 'depends_on_id' : dep_package.id,
1013 'installed' : installed,
1014 }
1015
1016 if target_id != INVALID_KEY:
1017 dep['alias'] = _get_package_alias(dep_package)
1018
1019 if idep.dep_type == rdepends_type :
1020 runtime_deps.append(dep)
1021 elif idep.dep_type in other_depends_types :
1022 other_deps.append(dep)
1023
1024 rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1025 odep_sorted = sorted(
1026 sorted(other_deps, key=lambda k: k['name']),
1027 key=lambda k: k['dep_type'])
1028 retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1029 return retvalues
1030
1031# Return the count of packages dependent on package for this target_id image
1032def _get_package_reverse_dep_count(package, target_id):
1033 return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1034
1035# Return the count of the packages that this package_id is dependent on.
1036# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1037# installed, or else RDEPENDS if only built.
1038def _get_package_dependency_count(package, target_id, is_installed):
1039 if is_installed :
1040 return package.package_dependencies_source.filter(target_id__exact = target_id,
1041 dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1042 else :
1043 return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1044
1045def _get_package_alias(package):
1046 alias = package.installed_name
1047 if alias != None and alias != '' and alias != package.name:
1048 return alias
1049 else:
1050 return ''
1051
1052def _get_fullpackagespec(package):
1053 r = package.name
1054 version_good = package.version != None and package.version != ''
1055 revision_good = package.revision != None and package.revision != ''
1056 if version_good or revision_good:
1057 r += '_'
1058 if version_good:
1059 r += package.version
1060 if revision_good:
1061 r += '-'
1062 if revision_good:
1063 r += package.revision
1064 return r
1065
1066def package_built_detail(request, build_id, package_id):
1067 template = "package_built_detail.html"
1068 if Build.objects.filter(pk=build_id).count() == 0 :
1069 return redirect(builds)
1070
1071 # follow convention for pagination w/ search although not used for this view
1072 queryset = Package_File.objects.filter(package_id__exact=package_id)
1073 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1074 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1075 retval = _verify_parameters( request.GET, mandatory_parameters )
1076 if retval:
1077 return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1078
1079 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1080 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1081
1082 package = Package.objects.get(pk=package_id)
1083 package.fullpackagespec = _get_fullpackagespec(package)
1084 context = {
1085 'build' : Build.objects.get(pk=build_id),
1086 'package' : package,
1087 'dependency_count' : _get_package_dependency_count(package, -1, False),
1088 'objects' : paths,
1089 'tablecols':[
1090 {
1091 'name':'File',
1092 'orderfield': _get_toggle_order(request, "path"),
1093 'ordericon':_get_toggle_order_icon(request, "path"),
1094 },
1095 {
1096 'name':'Size',
1097 'orderfield': _get_toggle_order(request, "size", True),
1098 'ordericon':_get_toggle_order_icon(request, "size"),
1099 'dclass': 'sizecol span2',
1100 },
1101 ]
1102 }
1103 if paths.all().count() < 2:
1104 context['disable_sort'] = True;
1105
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001106 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001107 _set_parameters_values(pagesize, orderby, request)
1108 return response
1109
1110def package_built_dependencies(request, build_id, package_id):
1111 template = "package_built_dependencies.html"
1112 if Build.objects.filter(pk=build_id).count() == 0 :
1113 return redirect(builds)
1114
1115 package = Package.objects.get(pk=package_id)
1116 package.fullpackagespec = _get_fullpackagespec(package)
1117 dependencies = _get_package_dependencies(package_id)
1118 context = {
1119 'build' : Build.objects.get(pk=build_id),
1120 'package' : package,
1121 'runtime_deps' : dependencies['runtime_deps'],
1122 'other_deps' : dependencies['other_deps'],
1123 'dependency_count' : _get_package_dependency_count(package, -1, False)
1124 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001125 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001126
1127
1128def package_included_detail(request, build_id, target_id, package_id):
1129 template = "package_included_detail.html"
1130 if Build.objects.filter(pk=build_id).count() == 0 :
1131 return redirect(builds)
1132
1133 # follow convention for pagination w/ search although not used for this view
1134 (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1135 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby' : orderby }
1136 retval = _verify_parameters( request.GET, mandatory_parameters )
1137 if retval:
1138 return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1139 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1140
1141 queryset = Package_File.objects.filter(package_id__exact=package_id)
1142 paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1143
1144 package = Package.objects.get(pk=package_id)
1145 package.fullpackagespec = _get_fullpackagespec(package)
1146 package.alias = _get_package_alias(package)
1147 target = Target.objects.get(pk=target_id)
1148 context = {
1149 'build' : Build.objects.get(pk=build_id),
1150 'target' : target,
1151 'package' : package,
1152 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1153 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1154 'objects': paths,
1155 'tablecols':[
1156 {
1157 'name':'File',
1158 'orderfield': _get_toggle_order(request, "path"),
1159 'ordericon':_get_toggle_order_icon(request, "path"),
1160 },
1161 {
1162 'name':'Size',
1163 'orderfield': _get_toggle_order(request, "size", True),
1164 'ordericon':_get_toggle_order_icon(request, "size"),
1165 'dclass': 'sizecol span2',
1166 },
1167 ]
1168 }
1169 if paths.all().count() < 2:
1170 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001171 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001172 _set_parameters_values(pagesize, orderby, request)
1173 return response
1174
1175def package_included_dependencies(request, build_id, target_id, package_id):
1176 template = "package_included_dependencies.html"
1177 if Build.objects.filter(pk=build_id).count() == 0 :
1178 return redirect(builds)
1179
1180 package = Package.objects.get(pk=package_id)
1181 package.fullpackagespec = _get_fullpackagespec(package)
1182 package.alias = _get_package_alias(package)
1183 target = Target.objects.get(pk=target_id)
1184
1185 dependencies = _get_package_dependencies(package_id, target_id)
1186 context = {
1187 'build' : Build.objects.get(pk=build_id),
1188 'package' : package,
1189 'target' : target,
1190 'runtime_deps' : dependencies['runtime_deps'],
1191 'other_deps' : dependencies['other_deps'],
1192 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1193 'dependency_count' : _get_package_dependency_count(package, target_id, True)
1194 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001195 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001196
1197def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1198 template = "package_included_reverse_dependencies.html"
1199 if Build.objects.filter(pk=build_id).count() == 0 :
1200 return redirect(builds)
1201
1202 (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1203 mandatory_parameters = { 'count': pagesize, 'page' : 1, 'orderby': orderby }
1204 retval = _verify_parameters( request.GET, mandatory_parameters )
1205 if retval:
1206 return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1207 (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1208
1209 queryset = Package_Dependency.objects.select_related('depends_on__name', 'depends_on__size').filter(depends_on=package_id, target_id=target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS)
1210 objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1211
1212 package = Package.objects.get(pk=package_id)
1213 package.fullpackagespec = _get_fullpackagespec(package)
1214 package.alias = _get_package_alias(package)
1215 target = Target.objects.get(pk=target_id)
1216 for o in objects:
1217 if o.package.version != '':
1218 o.package.version += '-' + o.package.revision
1219 o.alias = _get_package_alias(o.package)
1220 context = {
1221 'build' : Build.objects.get(pk=build_id),
1222 'package' : package,
1223 'target' : target,
1224 'objects' : objects,
1225 'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1226 'dependency_count' : _get_package_dependency_count(package, target_id, True),
1227 'tablecols':[
1228 {
1229 'name':'Package',
1230 'orderfield': _get_toggle_order(request, "package__name"),
1231 'ordericon': _get_toggle_order_icon(request, "package__name"),
1232 },
1233 {
1234 'name':'Version',
1235 },
1236 {
1237 'name':'Size',
1238 'orderfield': _get_toggle_order(request, "package__size", True),
1239 'ordericon': _get_toggle_order_icon(request, "package__size"),
1240 'dclass': 'sizecol span2',
1241 },
1242 ]
1243 }
1244 if objects.all().count() < 2:
1245 context['disable_sort'] = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001246 response = toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001247 _set_parameters_values(pagesize, orderby, request)
1248 return response
1249
1250def image_information_dir(request, build_id, target_id, packagefile_id):
1251 # stubbed for now
1252 return redirect(builds)
1253 # the context processor that supplies data used across all the pages
1254
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001255# a context processor which runs on every request; this provides the
1256# projects and non_cli_projects (i.e. projects created by the user)
1257# variables referred to in templates, which used to determine the
1258# visibility of UI elements like the "New build" button
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001259def managedcontextprocessor(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001260 projects = Project.objects.all()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001261 ret = {
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001262 "projects": projects,
1263 "non_cli_projects": projects.exclude(is_default=True),
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001264 "DEBUG" : toastermain.settings.DEBUG,
1265 "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
1266 "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
1267 }
1268 return ret
1269
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001270# REST-based API calls to return build/building status to external Toaster
1271# managers and aggregators via JSON
1272
1273def _json_build_status(build_id,extend):
1274 build_stat = None
1275 try:
1276 build = Build.objects.get( pk = build_id )
1277 build_stat = {}
1278 build_stat['id'] = build.id
1279 build_stat['name'] = build.build_name
1280 build_stat['machine'] = build.machine
1281 build_stat['distro'] = build.distro
1282 build_stat['start'] = build.started_on
1283 # look up target name
1284 target= Target.objects.get( build = build )
1285 if target:
1286 if target.task:
1287 build_stat['target'] = '%s:%s' % (target.target,target.task)
1288 else:
1289 build_stat['target'] = '%s' % (target.target)
1290 else:
1291 build_stat['target'] = ''
1292 # look up project name
1293 project = Project.objects.get( build = build )
1294 if project:
1295 build_stat['project'] = project.name
1296 else:
1297 build_stat['project'] = ''
1298 if Build.IN_PROGRESS == build.outcome:
1299 now = timezone.now()
1300 timediff = now - build.started_on
1301 build_stat['seconds']='%.3f' % timediff.total_seconds()
1302 build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone)
1303 build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse)
1304 tf = Task.objects.filter(build = build)
1305 tfc = tf.count()
1306 if tfc > 0:
1307 tfd = tf.exclude(order__isnull=True).count()
1308 else:
1309 tfd = 0
1310 build_stat['task']='%d:%d' % (tfd,tfc)
1311 else:
1312 build_stat['outcome'] = build.get_outcome_text()
1313 timediff = build.completed_on - build.started_on
1314 build_stat['seconds']='%.3f' % timediff.total_seconds()
1315 build_stat['stop'] = build.completed_on
1316 messages = LogMessage.objects.all().filter(build = build)
1317 errors = len(messages.filter(level=LogMessage.ERROR) |
1318 messages.filter(level=LogMessage.EXCEPTION) |
1319 messages.filter(level=LogMessage.CRITICAL))
1320 build_stat['errors'] = errors
1321 warnings = len(messages.filter(level=LogMessage.WARNING))
1322 build_stat['warnings'] = warnings
1323 if extend:
1324 build_stat['cooker_log'] = build.cooker_log_path
1325 except Exception as e:
1326 build_state = str(e)
1327 return build_stat
1328
1329def json_builds(request):
1330 build_table = []
1331 builds = []
1332 try:
1333 builds = Build.objects.exclude(outcome=Build.IN_PROGRESS).order_by("-started_on")
1334 for build in builds:
1335 build_table.append(_json_build_status(build.id,False))
1336 except Exception as e:
1337 build_table = str(e)
1338 return JsonResponse({'builds' : build_table, 'count' : len(builds)})
1339
1340def json_building(request):
1341 build_table = []
1342 builds = []
1343 try:
1344 builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on")
1345 for build in builds:
1346 build_table.append(_json_build_status(build.id,False))
1347 except Exception as e:
1348 build_table = str(e)
1349 return JsonResponse({'building' : build_table, 'count' : len(builds)})
1350
1351def json_build(request,build_id):
1352 return JsonResponse({'build' : _json_build_status(build_id,True)})
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353
1354
1355import toastermain.settings
1356
1357from orm.models import Project, ProjectLayer, ProjectTarget, ProjectVariable
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001358from bldcontrol.models import BuildEnvironment
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001359
1360# we have a set of functions if we're in managed mode, or
1361# a default "page not available" simple functions for interactive mode
1362
1363if True:
1364 from django.contrib.auth.models import User
1365 from django.contrib.auth import authenticate, login
1366 from django.contrib.auth.decorators import login_required
1367
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001368 from orm.models import LayerSource, ToasterSetting, Release, Machine, LayerVersionDependency
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001369 from bldcontrol.models import BuildRequest
1370
1371 import traceback
1372
1373 class BadParameterException(Exception):
1374 ''' The exception raised on invalid POST requests '''
1375 pass
1376
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001377 # new project
1378 def newproject(request):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001379 if not project_enable:
1380 return redirect( landing )
1381
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001382 template = "newproject.html"
1383 context = {
1384 'email': request.user.email if request.user.is_authenticated() else '',
1385 'username': request.user.username if request.user.is_authenticated() else '',
1386 'releases': Release.objects.order_by("description"),
1387 }
1388
1389 try:
1390 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1391 except ToasterSetting.DoesNotExist:
1392 pass
1393
1394 if request.method == "GET":
1395 # render new project page
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001396 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001397 elif request.method == "POST":
1398 mandatory_fields = ['projectname', 'ptype']
1399 try:
1400 ptype = request.POST.get('ptype')
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001401 if ptype == "import":
1402 mandatory_fields.append('importdir')
1403 else:
1404 mandatory_fields.append('projectversion')
1405 # make sure we have values for all mandatory_fields
1406 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1407 if missing:
1408 # set alert for missing fields
1409 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
1410
1411 if not request.user.is_authenticated():
1412 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1413 if user is None:
1414 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1415
1416 user = authenticate(username = user.username, password = 'nopass')
1417 login(request, user)
1418
1419 # save the project
1420 if ptype == "import":
1421 if not os.path.isdir('%s/conf' % request.POST['importdir']):
1422 raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir'])
1423 from django.core import management
1424 management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'], interactive=False)
1425 prj = Project.objects.get(name = request.POST['projectname'])
1426 prj.merged_attr = True
1427 prj.save()
1428 else:
1429 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1430 prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
1431 prj.user_id = request.user.pk
1432 if 'mergeattr' == request.POST.get('mergeattr', ''):
1433 prj.merged_attr = True
1434 prj.save()
1435
1436 return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
1437
1438 except (IntegrityError, BadParameterException) as e:
1439 # fill in page with previously submitted values
1440 for field in mandatory_fields:
1441 context.__setitem__(field, request.POST.get(field, "-- missing"))
1442 if isinstance(e, IntegrityError) and "username" in str(e):
1443 context['alert'] = "Your chosen username is already used"
1444 else:
1445 context['alert'] = str(e)
1446 return toaster_render(request, template, context)
1447
1448 raise Exception("Invalid HTTP method for this page")
1449
1450 # new project
1451 def newproject_specific(request, pid):
1452 if not project_enable:
1453 return redirect( landing )
1454
1455 project = Project.objects.get(pk=pid)
1456 template = "newproject_specific.html"
1457 context = {
1458 'email': request.user.email if request.user.is_authenticated() else '',
1459 'username': request.user.username if request.user.is_authenticated() else '',
1460 'releases': Release.objects.order_by("description"),
1461 'projectname': project.name,
1462 'project_pk': project.pk,
1463 }
1464
1465 # WORKAROUND: if we already know release, redirect 'newproject_specific' to 'project_specific'
1466 if '1' == project.get_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE'):
1467 return redirect(reverse(project_specific, args=(project.pk,)))
1468
1469 try:
1470 context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1471 except ToasterSetting.DoesNotExist:
1472 pass
1473
1474 if request.method == "GET":
1475 # render new project page
1476 return toaster_render(request, template, context)
1477 elif request.method == "POST":
1478 mandatory_fields = ['projectname', 'ptype']
1479 try:
1480 ptype = request.POST.get('ptype')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001481 if ptype == "build":
1482 mandatory_fields.append('projectversion')
1483 # make sure we have values for all mandatory_fields
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001484 missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1485 if missing:
1486 # set alert for missing fields
1487 raise BadParameterException("Fields missing: %s" % ", ".join(missing))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001488
1489 if not request.user.is_authenticated():
1490 user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1491 if user is None:
1492 user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1493
1494 user = authenticate(username = user.username, password = 'nopass')
1495 login(request, user)
1496
1497 # save the project
1498 if ptype == "analysis":
1499 release = None
1500 else:
1501 release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1502
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001503 prj = Project.objects.create_project(name = request.POST['projectname'], release = release, existing_project = project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001504 prj.user_id = request.user.pk
1505 prj.save()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001506 return redirect(reverse(project_specific, args=(prj.pk,)) + "?notify=new-project")
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001507
1508 except (IntegrityError, BadParameterException) as e:
1509 # fill in page with previously submitted values
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001510 for field in mandatory_fields:
1511 context.__setitem__(field, request.POST.get(field, "-- missing"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001512 if isinstance(e, IntegrityError) and "username" in str(e):
1513 context['alert'] = "Your chosen username is already used"
1514 else:
1515 context['alert'] = str(e)
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001516 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001517
1518 raise Exception("Invalid HTTP method for this page")
1519
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001520 # Shows the edit project page
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001521 def project(request, pid):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001522 project = Project.objects.get(pk=pid)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001523
1524 if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'):
1525 if request.GET:
1526 #Example:request.GET=<QueryDict: {'setMachine': ['qemuarm']}>
1527 params = urlencode(request.GET).replace('%5B%27','').replace('%27%5D','')
1528 return redirect("%s?%s" % (reverse(project_specific, args=(project.pk,)),params))
1529 else:
1530 return redirect(reverse(project_specific, args=(project.pk,)))
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001531 context = {"project": project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001532 return toaster_render(request, "project.html", context)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001533
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001534 # Shows the edit project-specific page
1535 def project_specific(request, pid):
1536 project = Project.objects.get(pk=pid)
1537
1538 # Are we refreshing from a successful project specific update clone?
1539 if Project.PROJECT_SPECIFIC_CLONING_SUCCESS == project.get_variable(Project.PROJECT_SPECIFIC_STATUS):
1540 return redirect(reverse(landing_specific,args=(project.pk,)))
1541
1542 context = {
1543 "project": project,
1544 "is_new" : project.get_variable(Project.PROJECT_SPECIFIC_ISNEW),
1545 "default_image_recipe" : project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE),
1546 "mru" : Build.objects.all().filter(project=project,outcome=Build.IN_PROGRESS),
1547 }
1548 if project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0:
1549 context['build_in_progress_none_completed'] = True
1550 else:
1551 context['build_in_progress_none_completed'] = False
1552 return toaster_render(request, "project.html", context)
1553
1554 # perform the final actions for the project specific page
1555 def project_specific_finalize(cmnd, pid):
1556 project = Project.objects.get(pk=pid)
1557 callback = project.get_variable(Project.PROJECT_SPECIFIC_CALLBACK)
1558 if "update" == cmnd:
1559 # Delete all '_PROJECT_PREPARE_' builds
1560 for b in Build.objects.all().filter(project=project):
1561 delete_build = False
1562 for t in b.target_set.all():
1563 if '_PROJECT_PREPARE_' == t.target:
1564 delete_build = True
1565 if delete_build:
1566 from django.core import management
1567 management.call_command('builddelete', str(b.id), interactive=False)
1568 # perform callback at this last moment if defined, in case Toaster gets shutdown next
1569 default_target = project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE)
1570 if callback:
1571 callback = callback.replace("<IMAGE>",default_target)
1572 if "cancel" == cmnd:
1573 if callback:
1574 callback = callback.replace("<IMAGE>","none")
1575 callback = callback.replace("--update","--cancel")
1576 # perform callback at this last moment if defined, in case this Toaster gets shutdown next
1577 ret = ''
1578 if callback:
1579 ret = os.system('bash -c "%s"' % callback)
1580 project.set_variable(Project.PROJECT_SPECIFIC_CALLBACK,'')
1581 # Delete the temp project specific variables
1582 project.set_variable(Project.PROJECT_SPECIFIC_ISNEW,'')
1583 project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_NONE)
1584 # WORKAROUND: Release this workaround flag
1585 project.set_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE','')
1586
1587 # Shows the final landing page for project specific update
1588 def landing_specific(request, pid):
1589 project_specific_finalize("update", pid)
1590 context = {
1591 "install_dir": os.environ['TOASTER_DIR'],
1592 }
1593 return toaster_render(request, "landing_specific.html", context)
1594
1595 # Shows the related landing-specific page
1596 def landing_specific_cancel(request, pid):
1597 project_specific_finalize("cancel", pid)
1598 context = {
1599 "install_dir": os.environ['TOASTER_DIR'],
1600 "status": "cancel",
1601 }
1602 return toaster_render(request, "landing_specific.html", context)
1603
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001604 def jsunittests(request):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001605 """ Provides a page for the js unit tests """
1606 bbv = BitbakeVersion.objects.filter(branch="master").first()
1607 release = Release.objects.filter(bitbake_version=bbv).first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001608
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001609 name = "_js_unit_test_prj_"
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001610
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001611 # If there is an existing project by this name delete it.
1612 # We don't want Lots of duplicates cluttering up the projects.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001613 Project.objects.filter(name=name).delete()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001614
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001615 new_project = Project.objects.create_project(name=name,
1616 release=release)
1617 # Add a layer
1618 layer = new_project.get_all_compatible_layer_versions().first()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001619
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001620 ProjectLayer.objects.get_or_create(layercommit=layer,
1621 project=new_project)
1622
1623 # make sure we have a machine set for this project
1624 ProjectVariable.objects.get_or_create(project=new_project,
1625 name="MACHINE",
1626 value="qemux86")
1627 context = {'project': new_project}
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001628 return toaster_render(request, "js-unit-tests.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001629
1630 from django.views.decorators.csrf import csrf_exempt
1631 @csrf_exempt
1632 def xhr_testreleasechange(request, pid):
1633 def response(data):
1634 return HttpResponse(jsonfilter(data),
1635 content_type="application/json")
1636
1637 """ returns layer versions that would be deleted on the new
1638 release__pk """
1639 try:
1640 prj = Project.objects.get(pk = pid)
1641 new_release_id = request.GET['new_release_id']
1642
1643 # If we're already on this project do nothing
1644 if prj.release.pk == int(new_release_id):
1645 return reponse({"error": "ok", "rows": []})
1646
1647 retval = []
1648
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001649 for project in prj.projectlayer_set.all():
1650 release = Release.objects.get(pk = new_release_id)
1651
1652 layer_versions = prj.get_all_compatible_layer_versions()
1653 layer_versions = layer_versions.filter(release = release)
1654 layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
1655
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001656 # there is no layer_version with the new release id,
1657 # and the same name
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001658 if layer_versions.count() < 1:
1659 retval.append(project)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001660
1661 return response({"error":"ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001662 "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001663 })
1664
1665 except Exception as e:
1666 return response({"error": str(e) })
1667
1668 def xhr_configvaredit(request, pid):
1669 try:
1670 prj = Project.objects.get(id = pid)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001671 # There are cases where user can add variables which hold values
1672 # like http://, file:/// etc. In such case a simple split(":")
1673 # would fail. One example is SSTATE_MIRRORS variable. So we use
1674 # max_split var to handle them.
1675 max_split = 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001676 # add conf variables
1677 if 'configvarAdd' in request.POST:
1678 t=request.POST['configvarAdd'].strip()
1679 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001680 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001681 else:
1682 variable = t
1683 value = ""
1684
1685 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value)
1686 # change conf variables
1687 if 'configvarChange' in request.POST:
1688 t=request.POST['configvarChange'].strip()
1689 if ":" in t:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001690 variable, value = t.split(":", max_split)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001691 else:
1692 variable = t
1693 value = ""
1694
1695 pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable)
1696 pt.value=value
1697 pt.save()
1698 # remove conf variables
1699 if 'configvarDel' in request.POST:
1700 t=request.POST['configvarDel'].strip()
1701 pt = ProjectVariable.objects.get(pk = int(t)).delete()
1702
1703 # return all project settings, filter out blacklist and elsewhere-managed variables
1704 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1705 configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
1706 for var in vars_managed:
1707 configvars_query = configvars_query.exclude(name = var)
1708 for var in vars_blacklist:
1709 configvars_query = configvars_query.exclude(name = var)
1710
1711 return_data = {
1712 "error": "ok",
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001713 'configvars': [(x.name, x.value, x.pk) for x in configvars_query]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001714 }
1715 try:
1716 return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value,
1717 except ProjectVariable.DoesNotExist:
1718 pass
1719 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001720 return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value,
1721 except ProjectVariable.DoesNotExist:
1722 pass
1723 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001724 return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
1725 except ProjectVariable.DoesNotExist:
1726 pass
1727 try:
1728 return_data['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value,
1729 except ProjectVariable.DoesNotExist:
1730 pass
1731 try:
1732 return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value,
1733 except ProjectVariable.DoesNotExist:
1734 pass
1735 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001736 return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001737 except ProjectVariable.DoesNotExist:
1738 pass
1739
1740 return HttpResponse(json.dumps( return_data ), content_type = "application/json")
1741
1742 except Exception as e:
1743 return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1744
1745
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001746 def customrecipe_download(request, pid, recipe_id):
1747 recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
1748
1749 file_data = recipe.generate_recipe_file_contents()
1750
1751 response = HttpResponse(file_data, content_type='text/plain')
1752 response['Content-Disposition'] = \
1753 'attachment; filename="%s_%s.bb"' % (recipe.name,
1754 recipe.version)
1755
1756 return response
1757
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001758 def importlayer(request, pid):
1759 template = "importlayer.html"
1760 context = {
1761 'project': Project.objects.get(id=pid),
1762 }
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001763 return toaster_render(request, template, context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001764
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001765 def layerdetails(request, pid, layerid):
1766 project = Project.objects.get(pk=pid)
1767 layer_version = Layer_Version.objects.get(pk=layerid)
1768
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001769 project_layers = ProjectLayer.objects.filter(
1770 project=project).values_list("layercommit_id",
1771 flat=True)
1772
1773 context = {
1774 'project': project,
1775 'layer_source': LayerSource.types_dict(),
1776 'layerversion': layer_version,
1777 'layerdeps': {
1778 "list": [
1779 {
1780 "id": dep.id,
1781 "name": dep.layer.name,
1782 "layerdetailurl": reverse('layerdetails',
1783 args=(pid, dep.pk)),
1784 "vcs_url": dep.layer.vcs_url,
1785 "vcs_reference": dep.get_vcs_reference()
1786 }
1787 for dep in layer_version.get_alldeps(project.id)]
1788 },
1789 'projectlayers': list(project_layers)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001790 }
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001791
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001792 return toaster_render(request, 'layerdetails.html', context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001793
1794
1795 def get_project_configvars_context():
1796 # Vars managed outside of this view
1797 vars_managed = {
1798 'MACHINE', 'BBLAYERS'
1799 }
1800
1801 vars_blacklist = {
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001802 'PARALLEL_MAKE','BB_NUMBER_THREADS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001803 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001804 'PARALLEL_MAKE','TMPDIR',
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001805 'all_proxy','ftp_proxy','http_proxy ','https_proxy'
1806 }
1807
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001808 vars_fstypes = Target_Image_File.SUFFIXES
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001809
1810 return(vars_managed,sorted(vars_fstypes),vars_blacklist)
1811
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001812 def projectconf(request, pid):
1813
1814 try:
1815 prj = Project.objects.get(id = pid)
1816 except Project.DoesNotExist:
1817 return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1818
1819 # remove blacklist and externally managed varaibles from this list
1820 vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1821 configvars = ProjectVariable.objects.filter(project_id = pid).all()
1822 for var in vars_managed:
1823 configvars = configvars.exclude(name = var)
1824 for var in vars_blacklist:
1825 configvars = configvars.exclude(name = var)
1826
1827 context = {
1828 'project': prj,
1829 'configvars': configvars,
1830 'vars_managed': vars_managed,
1831 'vars_fstypes': vars_fstypes,
1832 'vars_blacklist': vars_blacklist,
1833 }
1834
1835 try:
1836 context['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value
1837 context['distro_defined'] = "1"
1838 except ProjectVariable.DoesNotExist:
1839 pass
1840 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001841 if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads":
1842 be = BuildEnvironment.objects.get(pk = str(1))
1843 dl_dir = os.path.join(dirname(be.builddir), "downloads")
1844 context['dl_dir'] = dl_dir
1845 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR")
1846 pv.value = dl_dir
1847 pv.save()
1848 else:
1849 context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value
1850 context['dl_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001851 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001852 pass
1853 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001854 context['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
1855 context['fstypes_defined'] = "1"
1856 except ProjectVariable.DoesNotExist:
1857 pass
1858 try:
1859 context['image_install_append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL_append").value
1860 context['image_install_append_defined'] = "1"
1861 except ProjectVariable.DoesNotExist:
1862 pass
1863 try:
1864 context['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value
1865 context['package_classes_defined'] = "1"
1866 except ProjectVariable.DoesNotExist:
1867 pass
1868 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001869 if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache":
1870 be = BuildEnvironment.objects.get(pk = str(1))
1871 sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache")
1872 context['sstate_dir'] = sstate_dir
1873 pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR")
1874 pv.value = sstate_dir
1875 pv.save()
1876 else:
1877 context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value
1878 context['sstate_dir_defined'] = "1"
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001879 except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001880 pass
1881
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001882 return toaster_render(request, "projectconf.html", context)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001883
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001884 def _file_names_for_artifact(build, artifact_type, artifact_id):
1885 """
1886 Return a tuple (file path, file name for the download response) for an
1887 artifact of type artifact_type with ID artifact_id for build; if
1888 artifact type is not supported, returns (None, None)
1889 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001890 file_name = None
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001891 response_file_name = None
1892
1893 if artifact_type == "cookerlog":
1894 file_name = build.cooker_log_path
1895 response_file_name = "cooker.log"
1896
1897 elif artifact_type == "imagefile":
1898 file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001899
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001900 elif artifact_type == "targetkernelartifact":
1901 target = TargetKernelFile.objects.get(pk=artifact_id)
1902 file_name = target.file_name
1903
1904 elif artifact_type == "targetsdkartifact":
1905 target = TargetSDKFile.objects.get(pk=artifact_id)
1906 file_name = target.file_name
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001907
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001908 elif artifact_type == "licensemanifest":
1909 file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001910
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001911 elif artifact_type == "packagemanifest":
1912 file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path
1913
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001914 elif artifact_type == "tasklogfile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001915 file_name = Task.objects.get(build = build, pk = artifact_id).logfile
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001916
1917 elif artifact_type == "logmessagefile":
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001918 file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001919
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001920 if file_name and not response_file_name:
1921 response_file_name = os.path.basename(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001922
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001923 return (file_name, response_file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001924
1925 def build_artifact(request, build_id, artifact_type, artifact_id):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001926 """
1927 View which returns a build artifact file as a response
1928 """
1929 file_name = None
1930 response_file_name = None
1931
1932 try:
1933 build = Build.objects.get(pk = build_id)
1934 file_name, response_file_name = _file_names_for_artifact(
1935 build, artifact_type, artifact_id
1936 )
1937
1938 if file_name and response_file_name:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001939 fsock = open(file_name, "rb")
Patrick Williamsd7e96312015-09-22 08:09:05 -05001940 content_type = MimeTypeFinder.get_mimetype(file_name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001941
1942 response = HttpResponse(fsock, content_type = content_type)
1943
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001944 disposition = "attachment; filename=" + response_file_name
1945 response["Content-Disposition"] = disposition
Patrick Williamsd7e96312015-09-22 08:09:05 -05001946
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001947 return response
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001948 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001949 return toaster_render(request, "unavailable_artifact.html")
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001950 except (ObjectDoesNotExist, IOError):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001951 return toaster_render(request, "unavailable_artifact.html")
1952