summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/toaster/orm/models.py10
-rw-r--r--lib/toaster/toastergui/static/css/default.css5
-rw-r--r--lib/toaster/toastergui/templates/basetable_bottom.html13
-rw-r--r--lib/toaster/toastergui/templates/basetable_top.html79
-rw-r--r--lib/toaster/toastergui/templates/build.html129
-rw-r--r--lib/toaster/toastergui/templates/configuration.html63
-rw-r--r--lib/toaster/toastergui/templates/configvars.html40
-rw-r--r--lib/toaster/toastergui/templates/filtersnippet.html19
-rw-r--r--lib/toaster/toastergui/templatetags/projecttags.py9
-rw-r--r--lib/toaster/toastergui/urls.py1
-rw-r--r--lib/toaster/toastergui/views.py412
11 files changed, 499 insertions, 281 deletions
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index b30e405c0..ff26c7d43 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -31,8 +31,8 @@ class Build(models.Model):
(IN_PROGRESS, 'In Progress'),
)
- search_allowed_fields = ['machine',
- 'cooker_log_path']
+ search_allowed_fields = ['machine', 'image_fstypes',
+ 'cooker_log_path', "target__target"]
machine = models.CharField(max_length=100)
image_fstypes = models.CharField(max_length=100)
@@ -102,6 +102,8 @@ class Task(models.Model):
(OUTCOME_NA, 'Not Available'),
)
+ search_allowed_fields = [ "recipe__name", "task_name" ]
+
build = models.ForeignKey(Build, related_name='task_build')
order = models.IntegerField(null=True)
task_executed = models.BooleanField(default=False) # True means Executed, False means Prebuilt
@@ -217,6 +219,8 @@ class Layer_Version(models.Model):
class Variable(models.Model):
+ search_allowed_fields = ['variable_name', 'variable_value',
+ 'variablehistory__file_name', "description"]
build = models.ForeignKey(Build, related_name='variable_build')
variable_name = models.CharField(max_length=100)
variable_value = models.TextField(blank=True)
@@ -225,7 +229,7 @@ class Variable(models.Model):
description = models.TextField(blank=True)
class VariableHistory(models.Model):
- variable = models.ForeignKey(Variable)
+ variable = models.ForeignKey(Variable, related_name='vhistory')
file_name = models.FilePathField(max_length=255)
line_number = models.IntegerField(null=True)
operation = models.CharField(max_length=16)
diff --git a/lib/toaster/toastergui/static/css/default.css b/lib/toaster/toastergui/static/css/default.css
index 844f6dcd5..53c50043b 100644
--- a/lib/toaster/toastergui/static/css/default.css
+++ b/lib/toaster/toastergui/static/css/default.css
@@ -171,4 +171,7 @@ dd p {line-height:20px;}
.tooltip { z-index: 2000 !important; } /* this makes tooltips work inside modal dialogs */
.tooltip code { background-color:transparent; color:#FFFFFF; font-weight:normal; border:none; font-size: 1em; }
.manual { margin-top:11px;}
-.heading-help { font-size:14px;} \ No newline at end of file
+.heading-help { font-size:14px;}
+
+
+.no-results { margin: 10px 0 0; }
diff --git a/lib/toaster/toastergui/templates/basetable_bottom.html b/lib/toaster/toastergui/templates/basetable_bottom.html
index 00703fe4c..3e4b0cc5a 100644
--- a/lib/toaster/toastergui/templates/basetable_bottom.html
+++ b/lib/toaster/toastergui/templates/basetable_bottom.html
@@ -1,3 +1,4 @@
+ </tbody>
</table>
<!-- Show pagination controls -->
@@ -8,15 +9,15 @@
<ul class="pagination" style="display: block-inline">
{%if objects.has_previous %}
- <li><a href="?page={{objects.previous_page_number}}&count={{request.GET.count}}">&laquo;</a></li>
+ <li><a href="javascript:reload_params({'page':{{objects.previous_page_number}}})">&laquo;</a></li>
{%else%}
<li class="disabled"><a href="#">&laquo;</a></li>
{%endif%}
{% for i in objects.page_range %}
- <li{%if i == objects.number %} class="active" {%endif%}><a href="?page={{i}}&count={{request.GET.count}}">{{i}}</a></li>
+ <li{%if i == objects.number %} class="active" {%endif%}><a href="javascript:reload_params({'page':{{i}}})">{{i}}</a></li>
{% endfor %}
{%if objects.has_next%}
- <li><a href="?page={{objects.next_page_number}}&count={{request.GET.count}}">&raquo;</a></li>
+ <li><a href="javascript:reload_params({'page':{{objects.next_page_number}}})">&raquo;</a></li>
{%else%}
<li class="disabled"><a href="#">&raquo;</a></li>
{%endif%}
@@ -58,3 +59,9 @@
});
});
</script>
+
+<!-- modal filter boxes -->
+ {% for tc in tablecols %}{% if tc.filter %}{% with f=tc.filter %}
+ {% include "filtersnippet.html" %}
+ {% endwith %}{% endif %} {% endfor %}
+<!-- end modals -->
diff --git a/lib/toaster/toastergui/templates/basetable_top.html b/lib/toaster/toastergui/templates/basetable_top.html
index b9277b4a3..34e0cd721 100644
--- a/lib/toaster/toastergui/templates/basetable_top.html
+++ b/lib/toaster/toastergui/templates/basetable_top.html
@@ -21,46 +21,53 @@
<!-- control header -->
<div class="navbar">
- <div class="navbar-inner">
- <form class="navbar-search input-append pull-left">
- <input class="input-xxlarge" type="text" placeholder="Search {{objectname}}" />
- <button class="btn" type="button">Search</button>
- </form>
- <div class="pull-right">
-
- {% if tablecols %}
- <div class="btn-group">
- <button class="btn dropdown-toggle" data-toggle="dropdown">
- Edit columns
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu">
-
- {% for i in tablecols %}
- <li>
- <label class="checkbox">
-<input type="checkbox" class="chbxtoggle" id="{{i.clclass}}" value="ct{{i.name}}" {% if i.clclass %}{% if not i.hidden %}checked="checked"{%endif%} onchange="showhideTableColumn($(this).attr('id'), $(this).is(':checked'))" {%else%} disabled{% endif %}/> {{i.name}}
- </label>
- </li>
- {% endfor %}
- </ul>
- </div>
- {% endif %}
-
- <div style="display:inline">
- <span class="divider-vertical"></span>
- <span class="help-inline" style="padding-top:5px;">Show rows:</span>
- <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
+ <div class="navbar-inner">
+ <form class="navbar-search input-append pull-left" >
+ <input class="input-xxlarge" name="search" type="text" placeholder="Search {{objectname}}" value="{{request.GET.search}}"/>
+ <input class="btn" type="submit" value="Search"/>
+ </form>
+ <div class="pull-right">
+{% if tablecols %}
+ <div class="btn-group">
+ <button class="btn dropdown-toggle" data-toggle="dropdown">Edit columns
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu">{% for i in tablecols %}
+ <li>
+ <label class="checkbox">
+ <input type="checkbox" class="chbxtoggle" {% if i.clclass %}id="{{i.clclass}}" value="ct{{i.name}}" {% if not i.hidden %}checked="checked"{%endif%} onchange="showhideTableColumn($(this).attr('id'), $(this).is(':checked'))" {%else%} checked disabled{% endif %}/> {{i.name}}
+ </label>
+ </li>{% endfor %}
+ </ul>
+ </div>
+{% endif %}
+ <div style="display:inline">
+ <span class="divider-vertical"></span>
+ <span class="help-inline" style="padding-top:5px;">Show rows:</span>
+ <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
{% with "2 5 10 25 50 100" as list%}
- {% for i in list.split %}<option{%if i == request.GET.count %} selected{%endif%}>{{i}}</option>
+{% for i in list.split %} <option{%if i == request.GET.count %} selected{%endif%}>{{i}}</option>
{% endfor %}
{% endwith %}
- </select>
- </div>
- </div>
- </div>
- </div>
+ </select>
+ </div>
+ </div>
+ </div> <!-- navbar-inner -->
+</div>
<!-- the actual rows of the table -->
<table class="table table-bordered table-hover tablesorter" id="otable">
+ <thead>
+ <!-- Table header row; generated from "tablecols" entry in the context dict -->
+ <tr>
+ {% for tc in tablecols %}<th class="{{tc.dclass}} {{tc.clclass}}">
+ {%if tc.qhelp%}<i class="icon-question-sign get-help" data-toggle="tooltip" title="{{tc.qhelp}}"></i>{%endif%}
+ <a href="javascript:reload_params({'orderby' : '{{tc.orderfield}}' })" style="font-weight:normal;">{{tc.name}}</a>
+ {%if tc.filter%}<div class="btn-group pull-right">
+ <a href="#filter_{{tc.filter.class}}" role="button" class="btn btn-mini{%if request.GET.filter in tc.filter.options.values%} btn-primary{%endif%}" data-toggle="modal"> <i class="icon-filter filtered"></i> </a>
+ </div>{%endif%}
+ </th>{% endfor %}
+ </tr>
+ </thead>
+ <tbody>
diff --git a/lib/toaster/toastergui/templates/build.html b/lib/toaster/toastergui/templates/build.html
index 43b491d55..eb7e03c95 100644
--- a/lib/toaster/toastergui/templates/build.html
+++ b/lib/toaster/toastergui/templates/build.html
@@ -7,70 +7,77 @@
{% block pagecontent %}
<div class="row-fluid">
-<div class="page-header" style="margin-top:40px;">
- <h1>
- Recent Builds
- </h1>
-</div>
-{% for build in mru %}
-<div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}">
- <div class="row-fluid">
- <div class="lead span5">
- {%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}
- <a href="{%url 'builddashboard' build.pk%}">
- <span data-toggle="tooltip" {%if build.target_set.all.count > 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%} {{build.machine}} ({{build.completed_on|naturaltime}})</span>
- </a>
- </div>
-{%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
- <div class="span2 lead">
-{% if build.errors_no %}
- <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}" class="error">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
-{% endif %}
- </div>
- <div class="span2 lead">
-{% if build.warnings_no %}
- <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>
-{% endif %}
- </div>
- <div class="lead pull-right">
- Build time: <a href="build-time.html">{{ build|timespent }}</a>
- </div>
-{%endif%}{%if build.outcome == build.IN_PROGRESS %}
- <div class="span4">
- <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete">
- <div style="width: {{build.completeper}}%;" class="bar"></div>
+ {%if mru.count > 0%}
+ <div class="page-header" style="margin-top:40px;">
+ <h1>
+ Recent Builds
+ </h1>
+ </div>
+ {% for build in mru %}
+ <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}">
+ <div class="row-fluid">
+ <div class="lead span5">
+ {%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}
+ <a href="{%url 'builddashboard' build.pk%}">
+ <span data-toggle="tooltip" {%if build.target_set.all.count > 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%} {{build.machine}} ({{build.completed_on|naturaltime}})</span>
+ </a>
+ </div>
+ {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
+ <div class="span2 lead">
+ {% if build.errors_no %}
+ <i class="icon-minus-sign red"></i> <a href="{%url 'builddashboard' build.pk%}" class="error">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>
+ {% endif %}
+ </div>
+ <div class="span2 lead">
+ {% if build.warnings_no %}
+ <i class="icon-warning-sign yellow"></i> <a href="{%url 'builddashboard' build.pk%}" class="warning">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>
+ {% endif %}
</div>
+ <div class="lead pull-right">
+ Build time: <a href="build-time.html">{{ build|timespent }}</a>
+ </div>
+ {%endif%}{%if build.outcome == build.IN_PROGRESS %}
+ <div class="span4">
+ <div class="progress" style="margin-top:5px;" data-toggle="tooltip" title="{{build.completeper}}% of tasks complete">
+ <div style="width: {{build.completeper}}%;" class="bar"></div>
+ </div>
+ </div>
+ <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div>
+ {%endif%}
</div>
- <div class="lead pull-right">ETA: in {{build.eta|naturaltime}}</div>
-{%endif%}
</div>
-</div>
-{% endfor %}
+ {% endfor %}{%endif%}
-
-<div class="page-header" style="margin-top:40px;">
- <h1>
- All builds
+ <div class="page-header" style="margin-top:40px;">
+ <h1>
+ {% if request.GET.filter or request.GET.search and objects.ocount > 0 %}
+ {{objects.ocount}} build{{objects.ocount|pluralize}} found
+ {%elif objects.ocount == 0%}
+ No builds
+ {%else%}
+ All builds
+ {%endif%}
</h1>
-</div>
+ </div>
-{% include "basetable_top.html" %}
+ {% if objects.ocount == 0 %}
+ <div class="row-fluid">
+ <div class="alert">
+ <form class="no-results">
+ <div class="input-append">
+ <input class="input-xxlarge" type="text" placeholder="{{request.GET.search}}" />
+ <input class="btn" type="submit" value="Search"/>
+ <button class="btn btn-link" onclick="javascript:reload_params({'search':'', 'filter':''})">Show all builds</button>
+ </div>
+ </form>
+ </div>
+ </div>
- <tr>
- <th class="outcome span2"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The outcome tells you if a build completed successfully or failed"></i> <a href="#" style="font-weight:normal;">Outcome</a> <div class="btn-group pull-right"> <a href="#outcome" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> </th>
- <th class="target"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="This is the build target(s): one or more recipes or image recipes"></i> <a href="#" style="font-weight:normal;">Target</a> </th>
- <th class="machine span3"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The machine is the hardware for which you are building"></i> <a href="#" style="font-weight:normal;">Machine</a> </th>
- <th class="started_on"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The date and time you started the build"></i> <a href="#" style="font-weight:normal;">Started on</a> <div class="btn-group pull-right"> <a href="#started-on" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> </th>
- <th class="completed_on"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The date and time the build finished"></i> <a href="#" class="sorted"> Completed on </a> <div class="btn-group pull-right"> <a href="#completed-on" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> <i class="icon-caret-down"></i> </th>
- <th class="failed_tasks"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How many tasks failed during the build"></i> <a href="#" style="font-weight:normal;">Failed tasks</a> <div class="btn-group pull-right"> <a href="#failed-tasks" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> <!--div id="filtered" class="btn-group pull-right" title="<p>Showing only builds with failed tasks</p><p><a class='btn btn-mini btn-primary' href='#'>Show all builds</a></p>"> <a class="btn btn-mini btn-primary"> <i class="icon-filter"></i> </a> </div--> </th>
- <th class="errors"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How many errors were encountered during the build (if any)"></i> <a href="#" style="font-weight:normal;">Errors</a> <div class="btn-group pull-right"> <a href="#errors" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> </th>
- <th class="warnings"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How many warnigns were encountered during the build (if any)"></i> <a href="#" style="font-weight:normal;">Warnings</a> <div class="btn-group pull-right"> <a href="#warnings" role="button" class="btn btn-mini" data-toggle="modal"> <i class="icon-filter"></i> </a> </div> <!--div id="filtered" class="btn-group pull-right" title="<p>Showing only builds without warnings</p><p><a class='btn btn-mini btn-primary' href='#'>Show all builds</a></p>"> <a class="btn btn-mini btn-primary"> <i class="icon-filter"></i> </a> </div--> </th>
- <th class="time"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="How long it took the build to finish"></i> <a href="#" style="font-weight:normal;">Time</a> </th>
- <th class="log span4"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The location in disk of the build main log file"></i> <a href="#" style="font-weight:normal;">Log</a> </th>
- <th class="output"> <i class="icon-question-sign get-help" data-toggle="tooltip" title="The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory"></i> <a href="#" style="font-weight:normal;">Output</a> </th>
- </tr>
+{% else %}
+{% include "basetable_top.html" %}
+ <!-- Table data rows; the order needs to match the order of "tablecols" definitions; and the <td class value needs to match the tablecols clclass value for show/hide buttons to work -->
{% for build in objects %}
<tr class="data">
<td class="outcome"><a href="{% url "builddashboard" build.id %}">{%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}</a></td>
@@ -78,11 +85,11 @@
<td class="machine"><a href="{% url "builddashboard" build.id %}">{{build.machine}}</a></td>
<td class="started_on"><a href="{% url "builddashboard" build.id %}">{{build.started_on}}</a></td>
<td class="completed_on"><a href="{% url "builddashboard" build.id %}">{{build.completed_on}}</a></td>
- <td class="failed_tasks"></td>
- <td class="errors">{% if build.errors_no %}<a class="error" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td>
- <td class="warnings">{% if build.warnings_no %}<a class="warning" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
+ <td class="failed_tasks">{% query build.task_build outcome=4 order__gt=0 as exectask%}{% if exectask.count == 1 %}{{exectask.0.recipe.name}}.{{exectask.0.task_name}}{% elif exectask.count > 1%}{{exectask.count}}{%endif%}</td>
+ <td class="errors_no">{% if build.errors_no %}<a class="errors_no" href="{% url "builddashboard" build.id %}#errors">{{build.errors_no}} error{{build.errors_no|pluralize}}</a>{%endif%}</td>
+ <td class="warnings_no">{% if build.warnings_no %}<a class="warnings_no" href="{% url "builddashboard" build.id %}#warnings">{{build.warnings_no}} warning{{build.warnings_no|pluralize}}</a>{%endif%}</td>
<td class="time"><a href="{% url "buildtime" build.id %}">{{build|timespent}}</a></td>
- <td class="log">{{build.log}}</td>
+ <td class="log">{{build.cooker_log_path}}</td>
<td class="output">{% if build.outcome == 0 %}{% for t in build.target_set.all %}{% if t.is_image %}<a href="{%url "builddashboard" build.id%}#images">{{build.image_fstypes}}</a>{% endif %}{% endfor %}{% endif %}</td>
</tr>
@@ -91,5 +98,7 @@
{% include "basetable_bottom.html" %}
-</div>
+{% endif %}
+</div><!-- end row-fluid-->
+
{% endblock %}
diff --git a/lib/toaster/toastergui/templates/configuration.html b/lib/toaster/toastergui/templates/configuration.html
index e390a95ff..467fbd02a 100644
--- a/lib/toaster/toastergui/templates/configuration.html
+++ b/lib/toaster/toastergui/templates/configuration.html
@@ -4,25 +4,54 @@
{% endblock %}
{% block buildinfomain %}
+<!-- page title -->
+<div class="row-fluid span10">
+ <div class="page-header">
+ <h1>Configuration</h1>
+ </div>
+</div>
-{% include "basetable_top.html" %}
+<!-- configuration table -->
+<div class="row-fluid pull-right span10" id="navTab">
+<ul class="nav nav-pills">
+ <li class="active"><a href="#">Summary</a></li>
+ <li class=""><a href="{% url 'configvars' build.id %}">BitBake variables</a></li>
+</ul>
- <tr>
- <th>Name</th>
- <th>Description</th>
- <th>Definition history</th>
- <th>Value</th>
- </tr>
+ <!-- summary -->
+ <div id="summary" class="tab-pane active">
+ <h3>Build configuration</h3>
+ <dl class="dl-horizontal">
+ <dt>BitBake version</dt><dd>1.19.1</dd>
+ <dt>Build system</dt><dd>x86_64-linux</dd>
+ <dt>Host distribution</dt><dd>Ubuntu-12.04</dd>
+ <dt>Target system</dt><dd>i586-poky-linux</dd>
+ <dt><i class="icon-question-sign get-help" data-toggle="tooltip" title="Specifies the target device for which the image is built"></i> Machine</dt><dd>atom-pc</dd>
+ <dt><i class="icon-question-sign get-help" data-toggle="tooltip" title="The short name of the distribution"></i> Distro</dt><dd>poky</dd>
+ <dt>Distro version</dt><dd>1.4+snapshot-20130718</dd>
+ <dt>Tune features</dt><dd>m32 i586</dd>
+ <dt>Target(s)</dt><dd>core-image-sato</dd>
+ </dl>
+ <h3>Layers</h3>
+ <div class="span9" style="margin-left:0px;">
+ <table class="table table-bordered table-hover">
+ <thead>
+ <tr>
+ <th>Layer</th>
+ <th>Layer branch</th>
+ <th>Layer commit</th>
+ <th>Layer directory</th>
+ </tr>
+ </thead>
+ <tbody>{% for lv in build.layer_version_build.all %}
+ <tr>
+ <td>{{lv.layer.name}}<a href="{{lv.layer.layer_index_url}}" target="_blank">&nbsp;<i class="icon-share get-info"></i></a></td><td>{{lv.branch}}</td><td class="layer_commit"><a data-content="{{lv.commit}}" title="" href="#" class="btn" data-original-title="">{{lv.commit|slice:":8"}}...</a></td><td>{{lv.layer.local_path}}</td>
+ </tr>{% endfor %}
+ </tbody>
+ </table>
+ </div>
+ </div>
- {% for variable in objects %}
-
- <tr class="data">
- <td>{{variable.variable_name}}</td>
- <td>{% if variable.description %}{{variable.description}}{% endif %}</td>
- <td>{% for vh in variable.variablehistory_set.all %}{{vh.operation}} in {{vh.file_name}}:{{vh.line_number}}<br/>{%endfor%}</td>
- <td>{{variable.variable_value}}</td>
- {% endfor %}
-
-{% include "basetable_bottom.html" %}
+</div>
{% endblock %}
diff --git a/lib/toaster/toastergui/templates/configvars.html b/lib/toaster/toastergui/templates/configvars.html
new file mode 100644
index 000000000..8ce04b883
--- /dev/null
+++ b/lib/toaster/toastergui/templates/configvars.html
@@ -0,0 +1,40 @@
+{% extends "basebuildpage.html" %}
+{% block localbreadcrumb %}
+<li>Configuration</li>
+{% endblock %}
+
+{% block buildinfomain %}
+<!-- page title -->
+<div class="row-fluid span10">
+ <div class="page-header">
+ <h1>Configuration</h1>
+ </div>
+</div>
+
+<!-- configuration table -->
+<div class="row-fluid pull-right span10" id="navTab">
+<ul class="nav nav-pills">
+ <li class=""><a href="{% url 'configuration' build.id %}">Summary</a></li>
+ <li class="active"><a href="#" >BitBake variables</a></li>
+</ul>
+
+
+ <!-- variables -->
+ <div id="variables" class="tab-pane">
+{% include "basetable_top.html" %}
+
+{% for variable in objects %}
+ <tr class="data">
+ <td class="variable">{{variable.variable_name}}</td>
+ <td class="variable_value">{{variable.variable_value}}</td>
+ <td class="file">{% for vh in variable.variablehistory_set.all %}{{vh.operation}} in {{vh.file_name}}:{{vh.line_number}}<br/>{%endfor%}</td>
+ <td class="description">{% if variable.description %}{{variable.description}}{% endif %}</td>
+ </tr>
+{% endfor %}
+
+{% include "basetable_bottom.html" %}
+
+ </div> <!-- endvariables -->
+
+</div>
+{% endblock %}
diff --git a/lib/toaster/toastergui/templates/filtersnippet.html b/lib/toaster/toastergui/templates/filtersnippet.html
new file mode 100644
index 000000000..26ff67563
--- /dev/null
+++ b/lib/toaster/toastergui/templates/filtersnippet.html
@@ -0,0 +1,19 @@
+
+ <!-- '{{f.class}}' filter -->
+ <form id="filter_{{f.class}}" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
+ <input type="hidden" name="search" value="{{request.GET.search}}"/>
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+ <h3>Filter builds by {{tc.name}}</h3>
+ </div>
+ <div class="modal-body">
+ <label>{{f.label}}</label>
+ <select name="filter">
+ <option value="">No Filter</option>{% for key, value in f.options.items %}
+ <option {%if request.GET.filter == value %}selected="" {%endif%}value="{{value}}">{{key}}</option>{% endfor %}
+ </select>
+ </div>
+ <div class="modal-footer">
+ <button type="submit" class="btn btn-primary disabled">Apply</button>
+ </div>
+ </form>
diff --git a/lib/toaster/toastergui/templatetags/projecttags.py b/lib/toaster/toastergui/templatetags/projecttags.py
index 145502675..15a1757b3 100644
--- a/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/lib/toaster/toastergui/templatetags/projecttags.py
@@ -16,8 +16,9 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from datetime import datetime
+from datetime import datetime, timedelta
from django import template
+from django.utils import timezone
register = template.Library()
@@ -42,8 +43,14 @@ def query(qs, **kwargs):
@register.filter
def divide(value, arg):
+ if int(arg) == 0:
+ return -1
return int(value) / int(arg)
@register.filter
def multiply(value, arg):
return int(value) * int(arg)
+
+@register.assignment_tag
+def datecompute(delta, start = timezone.now()):
+ return start + timedelta(delta)
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index f531eb013..585578316 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -39,6 +39,7 @@ urlpatterns = patterns('toastergui.views',
url(r'^build/(?P<build_id>\d+)/target/(?P<target_id>\d+)/packages$', 'tpackage', name='targetpackages'),
url(r'^build/(?P<build_id>\d+)/configuration$', 'configuration', name='configuration'),
+ url(r'^build/(?P<build_id>\d+)/configvars$', 'configvars', name='configvars'),
url(r'^build/(?P<build_id>\d+)/buildtime$', 'buildtime', name='buildtime'),
url(r'^build/(?P<build_id>\d+)/cpuusage$', 'cpuusage', name='cpuusage'),
url(r'^build/(?P<build_id>\d+)/diskio$', 'diskio', name='diskio'),
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 7d4d710f8..09da9c2a2 100644
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -25,7 +25,10 @@ from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File
from orm.models import Target_Installed_Package
from django.views.decorators.cache import cache_control
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-
+from django.http import HttpResponseBadRequest
+from django.utils import timezone
+from datetime import timedelta
+from django.utils import formats
def _build_page_range(paginator, index = 1):
try:
@@ -72,6 +75,109 @@ def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs)
+FIELD_SEPARATOR = ":"
+VALUE_SEPARATOR = ";"
+DESCENDING = "-"
+
+def __get_q_for_val(name, value):
+ if "OR" in value:
+ return reduce(operator.or_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("OR") ]))
+ 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") }
+ return ~Q(**kwargs)
+ else:
+ 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)
+
+ querydict = dict(zip(keys, values))
+ return reduce(lambda x, y: x & y, map(lambda x: __get_q_for_val(k, querydict[k]),[k for k in querydict]))
+
+def _get_toggle_order(request, orderkey):
+ return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
+
+# we check that the input comes in a valid form that we can recognize
+def _validate_input(input, model):
+
+ invalid = None
+
+ if input:
+ input_list = input.split(FIELD_SEPARATOR)
+
+ # Check we have only one colon
+ if len(input_list) != 2:
+ invalid = "We have an invalid number of separators"
+ 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)):
+ 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):
+ 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 ])
+
+ return input, invalid
+
+# uses search_allowed_fields in orm/models.py to create a search query
+# for these fields with the supplied input text
+def _get_search_results(search_term, queryset, model):
+ search_objects = []
+ for st in search_term.split(" "):
+ q_map = map(lambda x: Q(**{x+'__icontains': st}),
+ model.search_allowed_fields)
+
+ search_objects.append(reduce(operator.or_, q_map))
+ search_object = reduce(operator.and_, search_objects)
+ queryset = queryset.filter(search_object)
+
+ return queryset
+
+
+# function to extract the search/filter/ordering parameters from the request
+# it uses the request and the model to validate input for the filter and orderby values
+def _search_tuple(request, model):
+ ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
+ if invalid:
+ raise BaseException("Invalid ordering " + str(invalid))
+
+ filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
+ if invalid:
+ raise BaseException("Invalid filter " + str(invalid))
+
+ search_term = request.GET.get('search', '')
+ return (filter_string, search_term, ordering_string)
+
+
+# returns a lazy-evaluated queryset for a filter/search/order combination
+def _get_queryset(model, filter_string, search_term, ordering_string):
+ if filter_string:
+ filter_query = _get_filtering_query(filter_string)
+ queryset = model.objects.filter(filter_query)
+ else:
+ queryset = model.objects.all()
+
+ if search_term:
+ queryset = _get_search_results(search_term, queryset, model)
+
+ if ordering_string and queryset:
+ column, order = ordering_string.split(':')
+ if order.lower() == DESCENDING:
+ queryset = queryset.order_by('-' + column)
+ else:
+ queryset = queryset.order_by(column)
+
+ return queryset
# shows the "all builds" page
def builds(request):
@@ -84,16 +190,24 @@ def builds(request):
if retval:
return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
- # retrieve the objects that will be displayed in the table
- build_info = _build_page_range(Paginator(Build.objects.exclude(outcome = Build.IN_PROGRESS).order_by("-id"), request.GET.get('count', 10)),request.GET.get('page', 1))
+ # boilerplate code that takes a request for an object type and returns a queryset
+ # for that object type. copypasta for all needed table searches
+ (filter_string, search_term, ordering_string) = _search_tuple(request, Build)
+ queryset = _get_queryset(Build, filter_string, search_term, ordering_string)
+
+ # 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.exclude(outcome = Build.IN_PROGRESS), request.GET.get('count', 10)),request.GET.get('page', 1))
- # build view-specific information; this is rendered specifically in the builds page
- build_mru = Build.objects.order_by("-started_on")[:3]
+ # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
+ build_mru = Build.objects.filter(completed_on__gte=(timezone.now()-timedelta(hours=24))).order_by("-started_on")[:3]
for b in [ x for x in build_mru if x.outcome == Build.IN_PROGRESS ]:
tf = Task.objects.filter(build = b)
b.completeper = tf.exclude(order__isnull=True).count()*100/tf.count()
- from django.utils import timezone
- b.eta = timezone.now() + ((timezone.now() - b.started_on)*100/b.completeper)
+ b.eta = timezone.now()
+ if b.completeper > 0:
+ b.eta += ((timezone.now() - b.started_on)*100/b.completeper)
+ else:
+ b.eta = 0
# send the data to the template
context = {
@@ -101,19 +215,78 @@ def builds(request):
'mru' : build_mru,
# TODO: common objects for all table views, adapt as needed
'objects' : build_info,
+ # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
'tablecols' : [
- {'name': 'Target ', 'clclass': 'target',},
- {'name': 'Machine ', 'clclass': 'machine'},
- {'name': 'Completed on ', 'clclass': 'completed_on'},
- {'name': 'Failed tasks ', 'clclass': 'failed_tasks'},
- {'name': 'Errors ', 'clclass': 'errors_no'},
- {'name': 'Warnings', 'clclass': 'warnings_no'},
- {'name': 'Output ', 'clclass': 'output'},
- {'name': 'Started on ', 'clclass': 'started_on', 'hidden' : 1},
- {'name': 'Time ', 'clclass': 'time', 'hidden' : 1},
- {'name': 'Output', 'clclass': 'output'},
- {'name': 'Log', 'clclass': 'log', 'hidden': 1},
- ]}
+ {'name': 'Outcome ', # column with a single filter
+ 'qhelp' : "The outcome tells you if a build completed successfully or failed", # the help button content
+ 'dclass' : "span2", # indication about column width; comes from the design
+ 'orderfield': _get_toggle_order(request, "outcome"), # adds ordering by the field value; default ascending unless clicked from ascending into descending
+ # filter field will set a filter on that column with the specs in the filter description
+ # the class field in the filter has no relation with clclass; the control different aspects of the UI
+ # still, it is recommended for the values to be identical for easy tracking in the generated HTML
+ 'filter' : {'class' : 'outcome', 'label': 'Show only', 'options' : {
+ 'Successful builds': 'outcome:' + str(Build.SUCCEEDED), # this is the field search expression
+ 'Failed builds': 'outcome:'+ str(Build.FAILED),
+ }
+ }
+ },
+ {'name': 'Target ', # default column, disabled box, with just the name in the list
+ 'qhelp': "This is the build target(s): one or more recipes or image recipes",
+ 'orderfield': _get_toggle_order(request, "target__target"),
+ },
+ {'name': 'Machine ',
+ 'qhelp': "The machine is the hardware for which you are building",
+ 'dclass': 'span3'}, # a slightly wider column
+ {'name': 'Started on ', 'clclass': 'started_on', 'hidden' : 1, # this is an unchecked box, which hides the column
+ 'qhelp': "The date and time you started the build",
+ 'filter' : {'class' : 'started_on', 'label': 'Show only builds started', 'options' : {
+ 'Today' : 'started_on__gte:'+timezone.now().strftime("%Y-%m-%d"),
+ 'Yesterday' : 'started_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"),
+ 'Within one week' : 'started_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"),
+ }}
+ },
+ {'name': 'Completed on ',
+ 'qhelp': "The date and time the build finished",
+ 'orderfield': _get_toggle_order(request, "completed_on"),
+ 'filter' : {'class' : 'completed_on', 'label': 'Show only builds completed', 'options' : {
+ 'Today' : 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"),
+ 'Yesterday' : 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"),
+ 'Within one week' : 'completed_on__gte:'+(timezone.now()-timedelta(days=7)).strftime("%Y-%m-%d"),
+ }}
+ },
+ {'name': 'Failed tasks ', 'clclass': 'failed_tasks', # specifing a clclass will enable the checkbox
+ 'qhelp': "How many tasks failed during the build",
+ 'filter' : {'class' : 'failed_tasks', 'label': 'Show only ', 'options' : {
+ 'Builds with failed tasks' : 'task_build__outcome:4',
+ 'Builds without failed tasks' : 'task_build__outcome:NOT4',
+ }}
+ },
+ {'name': 'Errors ', 'clclass': 'errors_no',
+ 'qhelp': "How many errors were encountered during the build (if any)",
+ 'orderfield': _get_toggle_order(request, "errors_no"),
+ 'filter' : {'class' : 'errors_no', 'label': 'Show only ', 'options' : {
+ 'Builds with errors' : 'errors_no__gte:1',
+ 'Builds without errors' : 'errors_no:0',
+ }}
+ },
+ {'name': 'Warnings', 'clclass': 'warnings_no',
+ 'qhelp': "How many warnigns were encountered during the build (if any)",
+ 'orderfield': _get_toggle_order(request, "warnings_no"),
+ 'filter' : {'class' : 'warnings_no', 'label': 'Show only ', 'options' : {
+ 'Builds with warnings' : 'warnings_no__gte:1',
+ 'Builds without warnings' : 'warnings_no:0',
+ }}
+ },
+ {'name': 'Time ', 'clclass': 'time', 'hidden' : 1,
+ 'qhelp': "How long it took the build to finish",},
+ {'name': 'Log',
+ 'dclass': "span4",
+ 'qhelp': "The location in disk of the build main log file",
+ 'clclass': 'log', 'hidden': 1},
+ {'name': 'Output', 'clclass': 'output',
+ 'qhelp': "The root file system types produced by the build. You can find them in your <code>/build/tmp/deploy/images/</code> directory"},
+ ]
+ }
return render(request, template, context)
@@ -191,8 +364,10 @@ def tasks(request, build_id):
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( 'tasks', request.GET, mandatory_parameters, build_id = build_id)
+ (filter_string, search_term, ordering_string) = _search_tuple(request, Task)
+ queryset = _get_queryset(Task, filter_string, search_term, ordering_string)
- tasks = _build_page_range(Paginator(Task.objects.filter(build=build_id, order__gt=0), request.GET.get('count', 100)),request.GET.get('page', 1))
+ tasks = _build_page_range(Paginator(queryset.filter(build=build_id, order__gt=0), request.GET.get('count', 100)),request.GET.get('page', 1))
for t in tasks:
if t.outcome == Task.OUTCOME_COVERED:
@@ -208,8 +383,10 @@ def recipes(request, build_id):
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( 'recipes', request.GET, mandatory_parameters, build_id = build_id)
+ (filter_string, search_term, ordering_string) = _search_tuple(request, Recipe)
+ queryset = _get_queryset(Recipe, filter_string, search_term, ordering_string)
- recipes = _build_page_range(Paginator(Recipe.objects.filter(layer_version__id__in=Layer_Version.objects.filter(build=build_id)), request.GET.get('count', 100)),request.GET.get('page', 1))
+ recipes = _build_page_range(Paginator(queryset.filter(layer_version__id__in=Layer_Version.objects.filter(build=build_id)), request.GET.get('count', 100)),request.GET.get('page', 1))
context = {'build': Build.objects.filter(pk=build_id)[0], 'objects': recipes, }
@@ -218,15 +395,63 @@ def recipes(request, build_id):
def configuration(request, build_id):
template = 'configuration.html'
+ context = {'build': Build.objects.filter(pk=build_id)[0]}
+ return render(request, template, context)
+
+
+def configvars(request, build_id):
+ template = 'configvars.html'
mandatory_parameters = { 'count': 100, 'page' : 1};
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
- return _redirect_parameters( 'configuration', request.GET, mandatory_parameters, build_id = build_id)
+ return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
+
+ (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
+ queryset = _get_queryset(Variable, filter_string, search_term, ordering_string)
+
+ variables = _build_page_range(Paginator(queryset.filter(build=build_id), request.GET.get('count', 50)), request.GET.get('page', 1))
+
+ context = {
+ 'build': Build.objects.filter(pk=build_id)[0],
+ 'objects' : variables,
+ # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
+ 'tablecols' : [
+ {'name': 'Variable ',
+ 'qhelp': "Base variable expanded name",
+ 'clclass' : 'variable',
+ 'dclass' : "span3",
+ 'orderfield': _get_toggle_order(request, "variable_name"),
+ },
+ {'name': 'Value ',
+ 'qhelp': "The value assigned to the variable",
+ 'clclass': 'variable_value',
+ 'dclass': "span4",
+ 'orderfield': _get_toggle_order(request, "variable_value"),
+ },
+ {'name': 'Configuration file(s) ',
+ 'qhelp': "The configuration file(s) that touched the variable value",
+ 'clclass': 'file',
+ 'dclass': "span6",
+ 'orderfield': _get_toggle_order(request, "variable_vhistory__file_name"),
+ 'filter' : { 'class': 'file', 'label' : 'Show only', 'options' : {
+ }
+ }
+ },
+ {'name': 'Description ',
+ 'qhelp': "A brief explanation of a variable",
+ 'clclass': 'description',
+ 'dclass': "span5",
+ 'orderfield': _get_toggle_order(request, "description"),
+ 'filter' : { 'class' : 'description', 'label' : 'No', 'options' : {
+ }
+ },
+ }
+ ]
+ }
- variables = _build_page_range(Paginator(Variable.objects.filter(build=build_id), 50), request.GET.get('page', 1))
- context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : variables}
return render(request, template, context)
+
def buildtime(request, build_id):
template = "buildtime.html"
if Build.objects.filter(pk=build_id).count() == 0 :
@@ -263,8 +488,10 @@ def bpackage(request, build_id):
retval = _verify_parameters( request.GET, mandatory_parameters )
if retval:
return _redirect_parameters( 'packages', request.GET, mandatory_parameters, build_id = build_id)
+ (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
+ queryset = _get_queryset(Package, filter_string, search_term, ordering_string)
- packages = _build_page_range(Paginator(Package.objects.filter(build = build_id), request.GET.get('count', 100)),request.GET.get('page', 1))
+ packages = _build_page_range(Paginator(queryset.filter(build = build_id), request.GET.get('count', 100)),request.GET.get('page', 1))
context = {'build': Build.objects.filter(pk=build_id)[0], 'objects' : packages}
return render(request, template, context)
@@ -305,139 +532,4 @@ def layer_versions_recipes(request, layerversion_id):
return render(request, template, context)
-#### API
-
-import json
-from django.core import serializers
-from django.http import HttpResponse, HttpResponseBadRequest
-
-
-def model_explorer(request, model_name):
-
- DESCENDING = 'desc'
- response_data = {}
- model_mapping = {
- 'build': Build,
- 'target': Target,
- 'task': Task,
- 'task_dependency': Task_Dependency,
- 'package': Package,
- 'layer': Layer,
- 'layerversion': Layer_Version,
- 'recipe': Recipe,
- 'recipe_dependency': Recipe_Dependency,
- 'package': Package,
- 'package_dependency': Package_Dependency,
- 'build_file': Package_File,
- 'variable': Variable,
- 'logmessage': LogMessage,
- }
-
- if model_name not in model_mapping.keys():
- return HttpResponseBadRequest()
-
- model = model_mapping[model_name]
-
- try:
- limit = int(request.GET.get('limit', 0))
- except ValueError:
- limit = 0
-
- try:
- offset = int(request.GET.get('offset', 0))
- except ValueError:
- offset = 0
-
- ordering_string, invalid = _validate_input(request.GET.get('orderby', ''),
- model)
- if invalid:
- return HttpResponseBadRequest()
-
- filter_string, invalid = _validate_input(request.GET.get('filter', ''),
- model)
- if invalid:
- return HttpResponseBadRequest()
-
- search_term = request.GET.get('search', '')
-
- if filter_string:
- filter_terms = _get_filtering_terms(filter_string)
- try:
- queryset = model.objects.filter(**filter_terms)
- except ValueError:
- queryset = []
- else:
- queryset = model.objects.all()
- if search_term:
- queryset = _get_search_results(search_term, queryset, model)
-
- if ordering_string and queryset:
- column, order = ordering_string.split(':')
- if order.lower() == DESCENDING:
- queryset = queryset.order_by('-' + column)
- else:
- queryset = queryset.order_by(column)
-
- if offset and limit:
- queryset = queryset[offset:(offset+limit)]
- elif offset:
- queryset = queryset[offset:]
- elif limit:
- queryset = queryset[:limit]
-
- if queryset:
- response_data['count'] = queryset.count()
- else:
- response_data['count'] = 0
- response_data['list'] = serializers.serialize('json', queryset)
-# response_data = serializers.serialize('json', queryset)
-
- return HttpResponse(json.dumps(response_data),
- content_type='application/json')
-
-def _get_filtering_terms(filter_string):
-
- search_terms = filter_string.split(":")
- keys = search_terms[0].split(',')
- values = search_terms[1].split(',')
-
- return dict(zip(keys, values))
-
-def _validate_input(input, model):
-
- invalid = 0
-
- if input:
- input_list = input.split(":")
-
- # Check we have only one colon
- if len(input_list) != 2:
- invalid = 1
- return None, invalid
-
- # Check we have an equal number of terms both sides of the colon
- if len(input_list[0].split(',')) != len(input_list[1].split(',')):
- invalid = 1
- return None, invalid
-
- # Check we are looking for a valid field
- valid_fields = model._meta.get_all_field_names()
- for field in input_list[0].split(','):
- if field not in valid_fields:
- invalid = 1
- return None, invalid
-
- return input, invalid
-
-def _get_search_results(search_term, queryset, model):
- search_objects = []
- for st in search_term.split(" "):
- q_map = map(lambda x: Q(**{x+'__icontains': st}),
- model.search_allowed_fields)
-
- search_objects.append(reduce(operator.or_, q_map))
- search_object = reduce(operator.and_, search_objects)
- queryset = queryset.filter(search_object)
-
- return queryset