diff options
author | Michael Wood <michael.g.wood@intel.com> | 2016-09-26 13:59:29 +0300 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2016-09-30 16:52:21 +0100 |
commit | 8ba7ccd25247539cbea8786026b375a2c9169aad (patch) | |
tree | 753608bcad31b9a4ba027215261cf3a0b4daf2d8 /bitbake/lib/toaster/toastergui | |
parent | 4d0bc8d52130fcc62c0d239fd7e0c79bec188c88 (diff) | |
download | poky-8ba7ccd25247539cbea8786026b375a2c9169aad.tar.gz |
bitbake: toaster: move MostRecentBuildsView to its own widget
This view is specific to the builds dashboard rather than gernic api so
like ToasterTable and ToasterTypeAhead we class it as a widget as it has
a single purpose. Also clean up some flake8 identified issues.
Original author of the code moved in this commit is Elliot Smith.
(Bitbake rev: 05428514e64ec896faae4055619e149e98bc8f57)
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/toaster/toastergui')
-rw-r--r-- | bitbake/lib/toaster/toastergui/api.py | 108 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/urls.py | 3 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/widgets.py | 141 |
3 files changed, 127 insertions, 125 deletions
diff --git a/bitbake/lib/toaster/toastergui/api.py b/bitbake/lib/toaster/toastergui/api.py index 8876409964..5589118027 100644 --- a/bitbake/lib/toaster/toastergui/api.py +++ b/bitbake/lib/toaster/toastergui/api.py | |||
@@ -33,10 +33,8 @@ from bldcontrol import bbcontroller | |||
33 | from django.http import HttpResponse, JsonResponse | 33 | from django.http import HttpResponse, JsonResponse |
34 | from django.views.generic import View | 34 | from django.views.generic import View |
35 | from django.core.urlresolvers import reverse | 35 | from django.core.urlresolvers import reverse |
36 | from django.utils import timezone | ||
37 | from django.db.models import Q, F | 36 | from django.db.models import Q, F |
38 | from django.db import Error | 37 | from django.db import Error |
39 | from toastergui.templatetags.projecttags import json, sectohms, get_tasks | ||
40 | from toastergui.templatetags.projecttags import filtered_filesizeformat | 38 | from toastergui.templatetags.projecttags import filtered_filesizeformat |
41 | 39 | ||
42 | logger = logging.getLogger("toaster") | 40 | logger = logging.getLogger("toaster") |
@@ -227,112 +225,6 @@ class XhrLayer(View): | |||
227 | }) | 225 | }) |
228 | 226 | ||
229 | 227 | ||
230 | class MostRecentBuildsView(View): | ||
231 | def _was_yesterday_or_earlier(self, completed_on): | ||
232 | now = timezone.now() | ||
233 | delta = now - completed_on | ||
234 | |||
235 | if delta.days >= 1: | ||
236 | return True | ||
237 | |||
238 | return False | ||
239 | |||
240 | def get(self, request, *args, **kwargs): | ||
241 | """ | ||
242 | Returns a list of builds in JSON format. | ||
243 | """ | ||
244 | project = None | ||
245 | |||
246 | project_id = request.GET.get('project_id', None) | ||
247 | if project_id: | ||
248 | try: | ||
249 | project = Project.objects.get(pk=project_id) | ||
250 | except: | ||
251 | # if project lookup fails, assume no project | ||
252 | pass | ||
253 | |||
254 | recent_build_objs = Build.get_recent(project) | ||
255 | recent_builds = [] | ||
256 | |||
257 | for build_obj in recent_build_objs: | ||
258 | dashboard_url = reverse('builddashboard', args=(build_obj.pk,)) | ||
259 | buildtime_url = reverse('buildtime', args=(build_obj.pk,)) | ||
260 | rebuild_url = \ | ||
261 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
262 | cancel_url = \ | ||
263 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
264 | |||
265 | build = {} | ||
266 | build['id'] = build_obj.pk | ||
267 | build['dashboard_url'] = dashboard_url | ||
268 | |||
269 | buildrequest_id = None | ||
270 | if hasattr(build_obj, 'buildrequest'): | ||
271 | buildrequest_id = build_obj.buildrequest.pk | ||
272 | build['buildrequest_id'] = buildrequest_id | ||
273 | |||
274 | build['recipes_parsed_percentage'] = \ | ||
275 | int((build_obj.recipes_parsed / | ||
276 | build_obj.recipes_to_parse) * 100) | ||
277 | |||
278 | tasks_complete_percentage = 0 | ||
279 | if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED): | ||
280 | tasks_complete_percentage = 100 | ||
281 | elif build_obj.outcome == Build.IN_PROGRESS: | ||
282 | tasks_complete_percentage = build_obj.completeper() | ||
283 | build['tasks_complete_percentage'] = tasks_complete_percentage | ||
284 | |||
285 | build['state'] = build_obj.get_state() | ||
286 | |||
287 | build['errors'] = build_obj.errors.count() | ||
288 | build['dashboard_errors_url'] = dashboard_url + '#errors' | ||
289 | |||
290 | build['warnings'] = build_obj.warnings.count() | ||
291 | build['dashboard_warnings_url'] = dashboard_url + '#warnings' | ||
292 | |||
293 | build['buildtime'] = sectohms(build_obj.timespent_seconds) | ||
294 | build['buildtime_url'] = buildtime_url | ||
295 | |||
296 | build['rebuild_url'] = rebuild_url | ||
297 | build['cancel_url'] = cancel_url | ||
298 | |||
299 | build['is_default_project_build'] = build_obj.project.is_default | ||
300 | |||
301 | build['build_targets_json'] = \ | ||
302 | json(get_tasks(build_obj.target_set.all())) | ||
303 | |||
304 | # convert completed_on time to user's timezone | ||
305 | completed_on = timezone.localtime(build_obj.completed_on) | ||
306 | |||
307 | completed_on_template = '%H:%M' | ||
308 | if self._was_yesterday_or_earlier(completed_on): | ||
309 | completed_on_template = '%d/%m/%Y ' + completed_on_template | ||
310 | build['completed_on'] = completed_on.strftime( | ||
311 | completed_on_template) | ||
312 | |||
313 | targets = [] | ||
314 | target_objs = build_obj.get_sorted_target_list() | ||
315 | for target_obj in target_objs: | ||
316 | if target_obj.task: | ||
317 | targets.append(target_obj.target + ':' + target_obj.task) | ||
318 | else: | ||
319 | targets.append(target_obj.target) | ||
320 | build['targets'] = ' '.join(targets) | ||
321 | |||
322 | # abbreviated form of the full target list | ||
323 | abbreviated_targets = '' | ||
324 | num_targets = len(targets) | ||
325 | if num_targets > 0: | ||
326 | abbreviated_targets = targets[0] | ||
327 | if num_targets > 1: | ||
328 | abbreviated_targets += (' +%s' % (num_targets - 1)) | ||
329 | build['targets_abbreviated'] = abbreviated_targets | ||
330 | |||
331 | recent_builds.append(build) | ||
332 | |||
333 | return JsonResponse(recent_builds, safe=False) | ||
334 | |||
335 | |||
336 | class XhrCustomRecipe(View): | 228 | class XhrCustomRecipe(View): |
337 | """ Create a custom image recipe """ | 229 | """ Create a custom image recipe """ |
338 | 230 | ||
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index 1232611e2e..0002a5a2ee 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py | |||
@@ -24,6 +24,7 @@ from toastergui import tables | |||
24 | from toastergui import buildtables | 24 | from toastergui import buildtables |
25 | from toastergui import typeaheads | 25 | from toastergui import typeaheads |
26 | from toastergui import api | 26 | from toastergui import api |
27 | from toastergui import widgets | ||
27 | 28 | ||
28 | urlpatterns = patterns('toastergui.views', | 29 | urlpatterns = patterns('toastergui.views', |
29 | # landing page | 30 | # landing page |
@@ -224,7 +225,7 @@ urlpatterns = patterns('toastergui.views', | |||
224 | api.XhrProject.as_view(), | 225 | api.XhrProject.as_view(), |
225 | name='xhr_project'), | 226 | name='xhr_project'), |
226 | 227 | ||
227 | url(r'^mostrecentbuilds$', api.MostRecentBuildsView.as_view(), | 228 | url(r'^mostrecentbuilds$', widgets.MostRecentBuildsView.as_view(), |
228 | name='most_recent_builds'), | 229 | name='most_recent_builds'), |
229 | 230 | ||
230 | # default redirection | 231 | # default redirection |
diff --git a/bitbake/lib/toaster/toastergui/widgets.py b/bitbake/lib/toaster/toastergui/widgets.py index 005a5620db..026903d35d 100644 --- a/bitbake/lib/toaster/toastergui/widgets.py +++ b/bitbake/lib/toaster/toastergui/widgets.py | |||
@@ -22,23 +22,24 @@ | |||
22 | from django.views.generic import View, TemplateView | 22 | from django.views.generic import View, TemplateView |
23 | from django.views.decorators.cache import cache_control | 23 | from django.views.decorators.cache import cache_control |
24 | from django.shortcuts import HttpResponse | 24 | from django.shortcuts import HttpResponse |
25 | from django.http import HttpResponseBadRequest | ||
26 | from django.core import serializers | ||
27 | from django.core.cache import cache | 25 | from django.core.cache import cache |
28 | from django.core.paginator import Paginator, EmptyPage | 26 | from django.core.paginator import Paginator, EmptyPage |
29 | from django.db.models import Q | 27 | from django.db.models import Q |
30 | from orm.models import Project, ProjectLayer, Layer_Version | 28 | from orm.models import Project, Build |
31 | from django.template import Context, Template | 29 | from django.template import Context, Template |
32 | from django.template import VariableDoesNotExist | 30 | from django.template import VariableDoesNotExist |
33 | from django.template import TemplateSyntaxError | 31 | from django.template import TemplateSyntaxError |
34 | from django.core.serializers.json import DjangoJSONEncoder | 32 | from django.core.serializers.json import DjangoJSONEncoder |
35 | from django.core.exceptions import FieldError | 33 | from django.core.exceptions import FieldError |
36 | from django.conf.urls import url, patterns | 34 | from django.utils import timezone |
35 | from toastergui.templatetags.projecttags import sectohms, get_tasks | ||
36 | from toastergui.templatetags.projecttags import json as template_json | ||
37 | from django.http import JsonResponse | ||
38 | from django.core.urlresolvers import reverse | ||
37 | 39 | ||
38 | import types | 40 | import types |
39 | import json | 41 | import json |
40 | import collections | 42 | import collections |
41 | import operator | ||
42 | import re | 43 | import re |
43 | 44 | ||
44 | try: | 45 | try: |
@@ -55,6 +56,7 @@ from toastergui.tablefilter import TableFilterMap | |||
55 | class NoFieldOrDataName(Exception): | 56 | class NoFieldOrDataName(Exception): |
56 | pass | 57 | pass |
57 | 58 | ||
59 | |||
58 | class ToasterTable(TemplateView): | 60 | class ToasterTable(TemplateView): |
59 | def __init__(self, *args, **kwargs): | 61 | def __init__(self, *args, **kwargs): |
60 | super(ToasterTable, self).__init__() | 62 | super(ToasterTable, self).__init__() |
@@ -81,7 +83,7 @@ class ToasterTable(TemplateView): | |||
81 | def get_context_data(self, **kwargs): | 83 | def get_context_data(self, **kwargs): |
82 | context = super(ToasterTable, self).get_context_data(**kwargs) | 84 | context = super(ToasterTable, self).get_context_data(**kwargs) |
83 | context['title'] = self.title | 85 | context['title'] = self.title |
84 | context['table_name'] = type(self).__name__.lower() | 86 | context['table_name'] = type(self).__name__.lower() |
85 | context['empty_state'] = self.empty_state | 87 | context['empty_state'] = self.empty_state |
86 | 88 | ||
87 | return context | 89 | return context |
@@ -406,7 +408,6 @@ class ToasterTable(TemplateView): | |||
406 | return data | 408 | return data |
407 | 409 | ||
408 | 410 | ||
409 | |||
410 | class ToasterTypeAhead(View): | 411 | class ToasterTypeAhead(View): |
411 | """ A typeahead mechanism to support the front end typeahead widgets """ | 412 | """ A typeahead mechanism to support the front end typeahead widgets """ |
412 | MAX_RESULTS = 6 | 413 | MAX_RESULTS = 6 |
@@ -427,34 +428,142 @@ class ToasterTypeAhead(View): | |||
427 | error = "ok" | 428 | error = "ok" |
428 | 429 | ||
429 | search_term = request.GET.get("search", None) | 430 | search_term = request.GET.get("search", None) |
430 | if search_term == None: | 431 | if search_term is None: |
431 | # We got no search value so return empty reponse | 432 | # We got no search value so return empty reponse |
432 | return response({'error' : error , 'results': []}) | 433 | return response({'error': error, 'results': []}) |
433 | 434 | ||
434 | try: | 435 | try: |
435 | prj = Project.objects.get(pk=kwargs['pid']) | 436 | prj = Project.objects.get(pk=kwargs['pid']) |
436 | except KeyError: | 437 | except KeyError: |
437 | prj = None | 438 | prj = None |
438 | 439 | ||
439 | results = self.apply_search(search_term, prj, request)[:ToasterTypeAhead.MAX_RESULTS] | 440 | results = self.apply_search(search_term, |
441 | prj, | ||
442 | request)[:ToasterTypeAhead.MAX_RESULTS] | ||
440 | 443 | ||
441 | if len(results) > 0: | 444 | if len(results) > 0: |
442 | try: | 445 | try: |
443 | self.validate_fields(results[0]) | 446 | self.validate_fields(results[0]) |
444 | except MissingFieldsException as e: | 447 | except self.MissingFieldsException as e: |
445 | error = e | 448 | error = e |
446 | 449 | ||
447 | data = { 'results' : results, | 450 | data = {'results': results, |
448 | 'error' : error, | 451 | 'error': error} |
449 | } | ||
450 | 452 | ||
451 | return response(data) | 453 | return response(data) |
452 | 454 | ||
453 | def validate_fields(self, result): | 455 | def validate_fields(self, result): |
454 | if 'name' in result == False or 'detail' in result == False: | 456 | if 'name' in result is False or 'detail' in result is False: |
455 | raise MissingFieldsException("name and detail are required fields") | 457 | raise self.MissingFieldsException( |
458 | "name and detail are required fields") | ||
456 | 459 | ||
457 | def apply_search(self, search_term, prj): | 460 | def apply_search(self, search_term, prj): |
458 | """ Override this function to implement search. Return an array of | 461 | """ Override this function to implement search. Return an array of |
459 | dictionaries with a minium of a name and detail field""" | 462 | dictionaries with a minium of a name and detail field""" |
460 | pass | 463 | pass |
464 | |||
465 | |||
466 | class MostRecentBuildsView(View): | ||
467 | def _was_yesterday_or_earlier(self, completed_on): | ||
468 | now = timezone.now() | ||
469 | delta = now - completed_on | ||
470 | |||
471 | if delta.days >= 1: | ||
472 | return True | ||
473 | |||
474 | return False | ||
475 | |||
476 | def get(self, request, *args, **kwargs): | ||
477 | """ | ||
478 | Returns a list of builds in JSON format. | ||
479 | """ | ||
480 | project = None | ||
481 | |||
482 | project_id = request.GET.get('project_id', None) | ||
483 | if project_id: | ||
484 | try: | ||
485 | project = Project.objects.get(pk=project_id) | ||
486 | except: | ||
487 | # if project lookup fails, assume no project | ||
488 | pass | ||
489 | |||
490 | recent_build_objs = Build.get_recent(project) | ||
491 | recent_builds = [] | ||
492 | |||
493 | for build_obj in recent_build_objs: | ||
494 | dashboard_url = reverse('builddashboard', args=(build_obj.pk,)) | ||
495 | buildtime_url = reverse('buildtime', args=(build_obj.pk,)) | ||
496 | rebuild_url = \ | ||
497 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
498 | cancel_url = \ | ||
499 | reverse('xhr_buildrequest', args=(build_obj.project.pk,)) | ||
500 | |||
501 | build = {} | ||
502 | build['id'] = build_obj.pk | ||
503 | build['dashboard_url'] = dashboard_url | ||
504 | |||
505 | buildrequest_id = None | ||
506 | if hasattr(build_obj, 'buildrequest'): | ||
507 | buildrequest_id = build_obj.buildrequest.pk | ||
508 | build['buildrequest_id'] = buildrequest_id | ||
509 | |||
510 | build['recipes_parsed_percentage'] = \ | ||
511 | int((build_obj.recipes_parsed / | ||
512 | build_obj.recipes_to_parse) * 100) | ||
513 | |||
514 | tasks_complete_percentage = 0 | ||
515 | if build_obj.outcome in (Build.SUCCEEDED, Build.FAILED): | ||
516 | tasks_complete_percentage = 100 | ||
517 | elif build_obj.outcome == Build.IN_PROGRESS: | ||
518 | tasks_complete_percentage = build_obj.completeper() | ||
519 | build['tasks_complete_percentage'] = tasks_complete_percentage | ||
520 | |||
521 | build['state'] = build_obj.get_state() | ||
522 | |||
523 | build['errors'] = build_obj.errors.count() | ||
524 | build['dashboard_errors_url'] = dashboard_url + '#errors' | ||
525 | |||
526 | build['warnings'] = build_obj.warnings.count() | ||
527 | build['dashboard_warnings_url'] = dashboard_url + '#warnings' | ||
528 | |||
529 | build['buildtime'] = sectohms(build_obj.timespent_seconds) | ||
530 | build['buildtime_url'] = buildtime_url | ||
531 | |||
532 | build['rebuild_url'] = rebuild_url | ||
533 | build['cancel_url'] = cancel_url | ||
534 | |||
535 | build['is_default_project_build'] = build_obj.project.is_default | ||
536 | |||
537 | build['build_targets_json'] = \ | ||
538 | template_json(get_tasks(build_obj.target_set.all())) | ||
539 | |||
540 | # convert completed_on time to user's timezone | ||
541 | completed_on = timezone.localtime(build_obj.completed_on) | ||
542 | |||
543 | completed_on_template = '%H:%M' | ||
544 | if self._was_yesterday_or_earlier(completed_on): | ||
545 | completed_on_template = '%d/%m/%Y ' + completed_on_template | ||
546 | build['completed_on'] = completed_on.strftime( | ||
547 | completed_on_template) | ||
548 | |||
549 | targets = [] | ||
550 | target_objs = build_obj.get_sorted_target_list() | ||
551 | for target_obj in target_objs: | ||
552 | if target_obj.task: | ||
553 | targets.append(target_obj.target + ':' + target_obj.task) | ||
554 | else: | ||
555 | targets.append(target_obj.target) | ||
556 | build['targets'] = ' '.join(targets) | ||
557 | |||
558 | # abbreviated form of the full target list | ||
559 | abbreviated_targets = '' | ||
560 | num_targets = len(targets) | ||
561 | if num_targets > 0: | ||
562 | abbreviated_targets = targets[0] | ||
563 | if num_targets > 1: | ||
564 | abbreviated_targets += (' +%s' % (num_targets - 1)) | ||
565 | build['targets_abbreviated'] = abbreviated_targets | ||
566 | |||
567 | recent_builds.append(build) | ||
568 | |||
569 | return JsonResponse(recent_builds, safe=False) | ||