From 4f7182775cfa39c589e2e4693b1769127d7dd4d4 Mon Sep 17 00:00:00 2001 From: Alexandru DAMIAN Date: Fri, 23 Jan 2015 17:36:14 +0000 Subject: bitbake: toastergui: update project build listing We update the build listings in the project mode to enable proper display and selection of build requests that do not have an actual build object because the bitbake process did not start. We add a page to display error details for build requests that did not start a build. Fixing errors and misspelling in build sections. Sorting by "timespent" is disabled for build-listing pages. [YOCTO #7165] [YOCTO #7156] [YOCTO #7196] [YOCTO #7188] (Bitbake rev: ee13bf45cecd6a0132d724b3206a6f4515669496) Signed-off-by: Alexandru DAMIAN Signed-off-by: Richard Purdie --- bitbake/lib/toaster/bldcontrol/models.py | 9 ++ .../toastergui/templates/buildrequestdetails.html | 67 ++++++++++++++ .../toastergui/templates/managed_builds.html | 18 ++-- .../toastergui/templates/managed_mrb_section.html | 42 ++++++--- .../toastergui/templates/projectbuilds.html | 20 ++-- bitbake/lib/toaster/toastergui/urls.py | 2 + bitbake/lib/toaster/toastergui/views.py | 103 +++++++++++++-------- 7 files changed, 193 insertions(+), 68 deletions(-) create mode 100644 bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html (limited to 'bitbake') diff --git a/bitbake/lib/toaster/bldcontrol/models.py b/bitbake/lib/toaster/bldcontrol/models.py index 2386d2345a..dc4afca2f7 100644 --- a/bitbake/lib/toaster/bldcontrol/models.py +++ b/bitbake/lib/toaster/bldcontrol/models.py @@ -113,6 +113,15 @@ class BuildRequest(models.Model): created = models.DateTimeField(auto_now_add = True) updated = models.DateTimeField(auto_now = True) + def get_duration(self): + return (self.updated - self.created).total_seconds() + + def get_sorted_target_list(self): + tgts = self.brtarget_set.order_by( 'target' ); + return( tgts ); + + def get_machine(self): + return self.brvariable_set.get(name="MACHINE").value # These tables specify the settings for running an actual build. # They MUST be kept in sync with the tables in orm.models.Project* diff --git a/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html b/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html new file mode 100644 index 0000000000..2a4571f42e --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/buildrequestdetails.html @@ -0,0 +1,67 @@ +{% extends "baseprojectpage.html" %} + +{% load static %} +{% load projecttags %} +{% load humanize %} + +{% block localbreadcrumb %} +
  • {{buildrequest.get_sorted_target_list.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} {{buildrequest.get_machine}} ({{buildrequest.updated|date:"d/m/y H:i"}})
  • +{% endblock %} + +{% block projectinfomain %} + + +
    + + + +
    + +
    +

    + Failed + on {{ buildrequest.updated|date:'d/m/y H:i' }} + with + + + + {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} + + Build time: {{buildrequest.get_duration|sectohms}} +

    +
    + +
    +
    + +
    +
    +
    + {% for error in buildrequest.brerror_set.all %} +
    + ERROR:
    {{error.errmsg}}
    +
    + {% endfor %} +
    +
    +
    + +
    +
    +
    +
    + + +{%endblock%} diff --git a/bitbake/lib/toaster/toastergui/templates/managed_builds.html b/bitbake/lib/toaster/toastergui/templates/managed_builds.html index 5944dc4747..183be760ae 100644 --- a/bitbake/lib/toaster/toastergui/templates/managed_builds.html +++ b/bitbake/lib/toaster/toastergui/templates/managed_builds.html @@ -35,10 +35,10 @@ - {% else %} + {% else %} {# We have builds to display #} {% include "basetable_top_buildprojects.html" %} - {% for br in objects %}{% if br.build %} {% with build=br.build %} {# if we have a build, just display it #} + {% for buildrequest in objects %}{% if buildrequest.build %} {% with build=buildrequest.build %} {# if we have a build, just display it #} {%if build.outcome == build.SUCCEEDED%}{%elif build.outcome == build.FAILED%}{%else%}{%endif%} {% for t in build.target_set.all %} {{t.target}}
    {% endfor %} @@ -61,7 +61,7 @@ {% if build.errors_no %} {{build.errors_no}} error{{build.errors_no|pluralize}} - {% if MANAGED and build.project %} + {% if MANAGED and build.project and build.buildartifact_set.count %} @@ -96,21 +96,21 @@ {% if buildrequest.state == buildrequest.REQ_FAILED %}{%else%}FIXME_build_request_state{%endif%} - 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} + 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} - {{br.machine}} + {{buildrequest.machine}} - {{br.created|date:"d/m/y H:i"}} + {{buildrequest.created|date:"d/m/y H:i"}} - {{br.updated|date:"d/m/y H:i"}} + {{buildrequest.updated|date:"d/m/y H:i"}} - {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}} + {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} @@ -120,7 +120,7 @@ {# we have no output here #} - {{br.project.name}} + {{buildrequest.project.name}} {%endif%} diff --git a/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html index da5a3f7f74..d2ffdcdc3d 100644 --- a/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html +++ b/bitbake/lib/toaster/toastergui/templates/managed_mrb_section.html @@ -26,7 +26,7 @@ {% endif %} 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} - + {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} {% endif %} @@ -71,24 +71,42 @@ {% else %} {# we use the project's page recent build design #} -
    -
    + +
    + {{buildrequest.project.name}} +
    + {% if buildrequest.state == buildrequest.REQ_FAILED %} -
    - 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} + +
    + {% if buildrequest.updated|format_build_date %} + {{ buildrequest.updated|date:'d/m/y H:i' }} + {% else %} + {{ buildrequest.updated|date:'H:i' }} + {% endif %} +
    +
    + {% if buildrequest.brerror_set.all.count %} + {{buildrequest.brerror_set.all.count}} error{{buildrequest.brerror_set.all.count|pluralize}} + {% endif %}
    -
    +
    {# there are no warnings for buildrequests #}
    -
    - {% for e in buildrequest.brerror_set.all|slice:":3" %} -
    -
    {{e.errmsg|whitespace_slice:":150"}}
    -
    - {% endfor %} +
    + + Build time: {{ buildrequest.get_duration|sectohms }} + + +
    + {% elif buildrequest.state == buildrequest.REQ_QUEUED %}
    diff --git a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html index 8f9172c6d5..2a8bd58f34 100644 --- a/bitbake/lib/toaster/toastergui/templates/projectbuilds.html +++ b/bitbake/lib/toaster/toastergui/templates/projectbuilds.html @@ -13,11 +13,11 @@ No builds found {% else %} - {% if request.GET.filter or request.GET.search %} - {{objects.paginator.count}} builds found - {% else %} + {% if request.GET.filter or request.GET.search %} + {{objects.paginator.count}} builds found + {% else %} Project builds ({{objects.paginator.count}}) - {% endif %} + {% endif %} {% endif %} @@ -89,23 +89,23 @@ - {% if buildrequest.state == buildrequest.REQ_FAILED %}{%else%}FIXME_build_request_state{%endif%} + {% if br.state == br.REQ_FAILED %}{%else%}FIXME_build_request_state{%endif%} - 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} + 1%}title="Targets: {%for target in br.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{br.brtarget_set.all.0.target}} {%if br.brtarget_set.all.count > 1%}(+ {{br.brtarget_set.all.count|add:"-1"}}){%endif%} - {{br.machine}} + {{br.machine}} - {{br.created|date:"d/m/y H:i"}} + {{br.created|date:"d/m/y H:i"}} - {{br.updated|date:"d/m/y H:i"}} + {{br.updated|date:"d/m/y H:i"}} - {{br.brerror_set.all.0.errmsg|whitespace_slice:":32"}} + {{br.brerror_set.all.count}} error{{br.brerror_set.all.count|pluralize}} diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index 8c3b5a85fd..1c83090f58 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py @@ -97,6 +97,8 @@ urlpatterns = patterns('toastergui.views', url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'), + # dashboard for failed build requests + url(r'^project/(?P\d+)/buildrequest/(?P\d+)$', 'buildrequestdetails', name='buildrequestdetails'), # default redirection url(r'^$', RedirectView.as_view( url= 'landing')), diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index e718ced570..6ccbf5452d 100755 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py @@ -22,7 +22,7 @@ import operator,re import HTMLParser -from django.db.models import Q, Sum +from django.db.models import Q, Sum, Count from django.db import IntegrityError from django.shortcuts import render, redirect from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable @@ -117,7 +117,8 @@ def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs): return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs) FIELD_SEPARATOR = ":" -VALUE_SEPARATOR = "!" +AND_VALUE_SEPARATOR = "!" +OR_VALUE_SEPARATOR = "|" DESCENDING = "-" def __get_q_for_val(name, value): @@ -126,20 +127,31 @@ def __get_q_for_val(name, value): if "AND" in value: return reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ])) if value.startswith("NOT"): - kwargs = { name : value.strip("NOT") } + value = value[3:] + if value == 'None': + value = None + kwargs = { name : value } return ~Q(**kwargs) else: + if value == 'None': + value = None kwargs = { name : value } return Q(**kwargs) def _get_filtering_query(filter_string): search_terms = filter_string.split(FIELD_SEPARATOR) - keys = search_terms[0].split(VALUE_SEPARATOR) - values = search_terms[1].split(VALUE_SEPARATOR) + and_keys = search_terms[0].split(AND_VALUE_SEPARATOR) + and_values = search_terms[1].split(AND_VALUE_SEPARATOR) + + and_query = [] + for kv in zip(and_keys, and_values): + or_keys = kv[0].split(OR_VALUE_SEPARATOR) + or_values = kv[1].split(OR_VALUE_SEPARATOR) + querydict = dict(zip(or_keys, or_values)) + and_query.append(reduce(operator.or_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict]))) - querydict = dict(zip(keys, values)) - return reduce(operator.and_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict])) + return reduce(operator.and_, [k for k in and_query]) def _get_toggle_order(request, orderkey, reverse = False): if reverse: @@ -169,13 +181,13 @@ def _validate_input(input, model): return None, invalid # Check we have an equal number of terms both sides of the colon - if len(input_list[0].split(VALUE_SEPARATOR)) != len(input_list[1].split(VALUE_SEPARATOR)): + if len(input_list[0].split(AND_VALUE_SEPARATOR)) != len(input_list[1].split(AND_VALUE_SEPARATOR)): invalid = "Not all arg names got values" return None, invalid + str(input_list) # Check we are looking for a valid field valid_fields = model._meta.get_all_field_names() - for field in input_list[0].split(VALUE_SEPARATOR): + for field in input_list[0].split(AND_VALUE_SEPARATOR): if not reduce(lambda x, y: x or y, map(lambda x: field.startswith(x), [ x for x in valid_fields ])): return None, (field, [ x for x in valid_fields ]) @@ -216,6 +228,7 @@ def _search_tuple(request, model): def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''): if filter_string: filter_query = _get_filtering_query(filter_string) +# raise Exception(filter_query) queryset = queryset.filter(filter_query) else: queryset = queryset.all() @@ -1780,12 +1793,13 @@ if toastermain.settings.MANAGED: # for that object type. copypasta for all needed table searches (filter_string, search_term, ordering_string) = _search_tuple(request, BuildRequest) # we don't display in-progress or deleted builds - queryset_all = buildrequests - queryset_with_search = _get_queryset(BuildRequest, queryset_all, None, search_term, ordering_string, '-updated') - queryset = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') + queryset_all = buildrequests.exclude(state = BuildRequest.REQ_DELETED) + queryset_all = queryset_all.annotate(Count('brerror')) + queryset_with_search = _get_queryset(BuildRequest, queryset_all, filter_string, search_term, ordering_string, '-updated') + # retrieve the objects that will be displayed in the table; builds a paginator and gets a page range to display - build_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1)) + build_info = _build_page_range(Paginator(queryset_with_search, pagesize), request.GET.get('page', 1)) # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds) # most recent build is like projects' most recent builds, but across all projects @@ -1842,8 +1856,8 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'outcome', 'label': 'Show:', 'options' : [ - ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_with_search.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression - ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_with_search.filter(state=str(BuildRequest.REQ_FAILED)).count()), + ('Successful builds', 'state:' + str(BuildRequest.REQ_COMPLETED), queryset_all.filter(state=str(BuildRequest.REQ_COMPLETED)).count()), # this is the field search expression + ('Failed builds', 'state:'+ str(BuildRequest.REQ_FAILED), queryset_all.filter(state=str(BuildRequest.REQ_FAILED)).count()), ] } }, @@ -1865,9 +1879,9 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'created', 'label': 'Show:', 'options' : [ - ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=timezone.now()).count()), - ("Yesterday's builds", 'created__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(hours=24))).count()), - ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), + ("Today's builds" , 'created__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(created__gte=timezone.now()).count()), + ("Yesterday's builds", 'created__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(hours=24))).count()), + ("This week's builds", 'created__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(created__gte=(timezone.now()-timedelta(days=7))).count()), ] } }, @@ -1879,9 +1893,9 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'updated', 'label': 'Show:', 'options' : [ - ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=timezone.now()).count()), - ("Yesterday's builds", 'updated__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(hours=24))).count()), - ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_with_search.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), + ("Today's builds", 'updated__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=timezone.now()).count()), + ("Yesterday's builds", 'updated__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(hours=24))).count()), + ("This week's builds", 'updated__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"), queryset_all.filter(updated__gte=(timezone.now()-timedelta(days=7))).count()), ] } }, @@ -1890,8 +1904,10 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'failed_tasks', 'label': 'Show:', 'options' : [ - ('Build with failed tasks', 'build__task_build__outcome:4', queryset_with_search.filter(build__task_build__outcome=4).count()), - ('Build without failed tasks', 'build__task_build__outcome:NOT4', queryset_with_search.filter(~Q(build__task_build__outcome=4)).count()), + ('Builds with failed tasks', 'build__task_build__outcome:%d' % Task.OUTCOME_FAILED, + queryset_all.filter(build__task_build__outcome=Task.OUTCOME_FAILED).count()), + ('Builds without failed tasks', 'build__task_build__outcome:%d' % Task.OUTCOME_FAILED, + queryset_all.filter(~Q(build__task_build__outcome=Task.OUTCOME_FAILED)).count()), ] } }, @@ -1903,8 +1919,10 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'errors_no', 'label': 'Show:', 'options' : [ - ('Build with errors', 'build__errors_no__gte:1', queryset_with_search.filter(build__errors_no__gte=1).count()), - ('Build without errors', 'build__errors_no:0', queryset_with_search.filter(build__errors_no=0).count()), + ('Builds with errors', 'build|build__errors_no__gt:None|0', + queryset_all.filter(Q(build=None) | Q(build__errors_no__gt=0)).count()), + ('Builds without errors', 'build__errors_no:0', + queryset_all.filter(build__errors_no=0).count()), ] } }, @@ -1916,8 +1934,8 @@ if toastermain.settings.MANAGED: 'filter' : {'class' : 'build__warnings_no', 'label': 'Show:', 'options' : [ - ('Build with warnings','build__warnings_no__gte:1', queryset_with_search.filter(build__warnings_no__gte=1).count()), - ('Build without warnings','build__warnings_no:0', queryset_with_search.filter(build__warnings_no=0).count()), + ('Builds with warnings','build__warnings_no__gte:1', queryset_all.filter(build__warnings_no__gte=1).count()), + ('Builds without warnings','build__warnings_no:0', queryset_all.filter(build__warnings_no=0).count()), ] } }, @@ -2016,7 +2034,7 @@ if toastermain.settings.MANAGED: context = { "project" : prj, - "completedbuilds": Build.objects.filter(project = prj).exclude(outcome = Build.IN_PROGRESS), + "completedbuilds": BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED), "prj" : {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}}, #"buildrequests" : prj.buildrequest_set.filter(state=BuildRequest.REQ_QUEUED), "builds" : _project_recent_build_list(prj), @@ -2061,7 +2079,7 @@ if toastermain.settings.MANAGED: try: if request.method != "POST": raise BadParameterException("invalid method") - request.session['project_id'] = pid + request.session['project_id'] = pid prj = Project.objects.get(id = pid) @@ -2167,11 +2185,11 @@ if toastermain.settings.MANAGED: try: prj = None if request.GET.has_key('project_id'): - prj = Project.objects.get(pk = request.GET['project_id']) + prj = Project.objects.get(pk = request.GET['project_id']) elif 'project_id' in request.session: prj = Project.objects.get(pk = request.session['project_id']) - else: - raise Exception("No valid project selected") + else: + raise Exception("No valid project selected") def _lv_to_dict(x): @@ -2819,10 +2837,10 @@ if toastermain.settings.MANAGED: vars_blacklist = { 'DL_DR','PARALLEL_MAKE','BB_NUMBER_THREADS','SSTATE_DIR', - 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', - 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR', - 'all_proxy','ftp_proxy','http_proxy ','https_proxy' - } + 'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT', + 'DL_DIR','PARALLEL_MAKE','SSTATE_DIR','SSTATE_DIR','SSTATE_MIRRORS','TMPDIR', + 'all_proxy','ftp_proxy','http_proxy ','https_proxy' + } vars_fstypes = { 'btrfs','cpio','cpio.gz','cpio.lz4','cpio.lzma','cpio.xz','cramfs', @@ -2874,7 +2892,7 @@ if toastermain.settings.MANAGED: def projectbuilds(request, pid): template = 'projectbuilds.html' - buildrequests = BuildRequest.objects.exclude(project_id = pid, state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) + buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED) try: context, pagesize, orderby = _build_list_helper(request, buildrequests) @@ -3012,6 +3030,14 @@ if toastermain.settings.MANAGED: } return render(request, template, context) + def buildrequestdetails(request, pid, brid): + template = "buildrequestdetails.html" + context = { + 'buildrequest' : BuildRequest.objects.get(pk = brid, project_id = pid) + } + return render(request, template, context) + + else: # these are pages that are NOT available in interactive mode def managedcontextprocessor(request): @@ -3256,3 +3282,6 @@ else: def xhr_updatelayer(request): raise Exception("page not available in interactive mode") + + def buildrequestdetails(request, pid, brid): + raise Exception("page not available in interactive mode") -- cgit v1.2.3-54-g00ecf