All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 01/19] toaster: move project data typeahead to the REST API
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 02/19] convert all project-based view to JSON APIs Alex DAMIAN
                   ` (17 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch enables JSON requests on the project REST endpoint,
and replaces the universal queries "xhr_datatypeahead" with the
`project` type to the REST project endpoint.

The patch adds a decorator that takes a context returned by a view
and either renders the template specified as the decorator argument,
or converts the context to JSON.

Normal "search", "filter" and "order" options for view work as normal
on the JSON API format. To enable the JSON return, set the "format"
GET parameter to "json".

In order to demonstrate the functionality, the "New build" button
is switched from using the xhr_datatypeahead to the project
REST API with JSON formatting. Additionally, the XHR APIs that
perform actions with the project id passed as parameter are removed,
and the needed URLs are populated from the project JSON API returns after
the project has been selected.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/static/js/base.js           | 51 ++++++++-----
 lib/toaster/toastergui/static/js/importlayer.js    |  6 +-
 lib/toaster/toastergui/static/js/layerdetails.js   |  2 +-
 lib/toaster/toastergui/static/js/libtoaster.js     | 12 +--
 lib/toaster/toastergui/templates/base.html         | 24 +++---
 .../toastergui/templates/basetable_bottom.html     |  2 +-
 .../templates/detail_pagination_bottom.html        |  2 +-
 lib/toaster/toastergui/templatetags/projecttags.py |  4 +-
 lib/toaster/toastergui/urls.py                     |  3 -
 lib/toaster/toastergui/views.py                    | 89 ++++++++++++++--------
 10 files changed, 119 insertions(+), 76 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js
index ccc23e0..9424b6c 100644
--- a/lib/toaster/toastergui/static/js/base.js
+++ b/lib/toaster/toastergui/static/js/base.js
@@ -15,20 +15,21 @@ function basePageInit (ctx) {
 
   /* Hide the change project icon when there is only one project */
   if (ctx.numProjects == 1){
-     $('#project .icon-pencil').hide(); 
+     $('#project .icon-pencil').hide();
   }
 
   newBuildButton.show().removeAttr("disabled");
 
+
   _checkProjectBuildable()
   _setupNewBuildButton();
 
 
   function _checkProjectBuildable(){
-    if (currentProjectId == undefined)
+    if (libtoaster.ctx.projectId == undefined)
       return;
 
-    libtoaster.getProjectInfo(ctx.projectInfoUrl, currentProjectId,
+    libtoaster.getProjectInfo(ctx.projectInfoUrl, libtoaster.ctx.projectId,
       function(data){
         if (data.machine.name == undefined || data.layers.length == 0) {
           /* we can't build anything with out a machine and some layers */
@@ -53,18 +54,13 @@ function basePageInit (ctx) {
     /* If we don't have a current project then present the set project
      * form.
      */
-    if (currentProjectId == undefined) {
+    if (libtoaster.ctx.projectId == undefined) {
       $('#change-project-form').show();
       $('#project .icon-pencil').hide();
     }
 
-    libtoaster.makeTypeahead(newBuildTargetInput, { type : "targets", project_id: currentProjectId }, function(item){
-        /* successfully selected a target */
-        selectedTarget = item;
-    });
 
-
-    libtoaster.makeTypeahead(newBuildProjectInput, { type : "projects" }, function(item){
+    libtoaster.makeTypeahead(newBuildProjectInput, libtoaster.ctx.projectsUrl, { format : "json" }, function(item){
         /* successfully selected a project */
         newBuildProjectSaveBtn.removeAttr("disabled");
         selectedProject = item;
@@ -93,20 +89,40 @@ function basePageInit (ctx) {
       if (!selectedTarget)
         selectedTarget = { name: newBuildTargetInput.val() };
       /* fire and forget */
-      libtoaster.startABuild(ctx.projectBuildUrl, currentProjectId, selectedTarget.name, null, null);
-      window.location.replace(ctx.projectBasePageUrl+currentProjectId);
+      libtoaster.startABuild(ctx.projectBuildUrl, libtoaster.ctx.projectId, selectedTarget.name, null, null);
+      window.location.replace(libtoaster.ctx.projectPageUrl);
     });
 
     newBuildProjectSaveBtn.click(function() {
-      currentProjectId = selectedProject.id
+      libtoaster.ctx.projectId = selectedProject.pk
       /* Update the typeahead project_id paramater */
       _checkProjectBuildable();
-      newBuildTargetInput.data('typeahead').options.xhrParams.project_id = currentProjectId;
-      newBuildTargetInput.val("");
 
-      $("#new-build-button #project a").text(selectedProject.name).attr('href', ctx.projectBasePageUrl+currentProjectId);
-      $("#new-build-button .alert a").attr('href', ctx.projectBasePageUrl+currentProjectId);
+      /* we set the effective context of the page to the currently selected project */
+      /* TBD: do we override even if we already have a context project ?? */
+      /* TODO: replace global library context with references to the "selected" project */
+      libtoaster.ctx.projectPageUrl = selectedProject.projectPageUrl;
+      libtoaster.ctx.xhrProjectEditUrl = selectedProject.xhrProjectEditUrl;
+      libtoaster.ctx.projectName = selectedProject.name;
+      libtoaster.ctx.projectId = selectedProject.id;
+
+      ctx.projectBuildUrl = selectedProject.projectBuildUrl;
+
+      /* we can create a target typeahead only after we have a project selected */
+      newBuildTargetInput.prop("disabled", false);
+      newBuildTargetBuildBtn.prop("disabled", false);
+
+      libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.xhrProjectDataTypeaheadUrl, { type : "targets" }, function(item){
+        /* successfully selected a target */
+        selectedTarget = item;
+	    });
+
+      newBuildTargetInput.val("");
 
+      /* set up new form aspect */
+      $("#new-build-button #project a").text(selectedProject.name).attr('href', libtoaster.ctx.projectPageUrl);
+      $("#new-build-button .alert a").attr('href', libtoaster.ctx.projectPageUrl);
+      $("#project .icon-pencil").show();
 
       $("#change-project-form").slideUp({ 'complete' : function() {
           $("#new-build-button #project").show();
@@ -116,6 +132,7 @@ function basePageInit (ctx) {
     $('#new-build-button #project .icon-pencil').click(function() {
       newBuildProjectSaveBtn.attr("disabled", "disabled");
       newBuildProjectInput.val($("#new-build-button #project a").text());
+      $("#cancel-change-project").show();
       $(this).parent().hide();
       $("#change-project-form").slideDown();
     });
diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index 875cc34..beb2ede 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -18,7 +18,7 @@ function importLayerPageInit (ctx) {
 
   $("#new-project-button").hide();
 
-  libtoaster.makeTypeahead(layerDepInput, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" }, function(item){
+  libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" }, function(item){
     currentLayerDepSelection = item;
 
     layerDepBtn.removeAttr("disabled");
@@ -28,7 +28,7 @@ function importLayerPageInit (ctx) {
   /* We automatically add "openembedded-core" layer for convenience as a
    * dependency as pretty much all layers depend on this one
    */
-  $.getJSON(libtoaster.ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" , value: "openembedded-core" }, function(layer) {
+  $.getJSON(libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" , value: "openembedded-core" }, function(layer) {
     if (layer.list.length == 1) {
       currentLayerDepSelection = layer.list[0];
       layerDepBtn.click();
@@ -211,7 +211,7 @@ function importLayerPageInit (ctx) {
       var name = $(this).val();
 
       /* Check if the layer name exists */
-      $.getJSON(libtoaster.ctx.xhrDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" , value: name }, function(layer) {
+      $.getJSON(libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" , value: name }, function(layer) {
       if (layer.list.length > 0) {
         for (var i in layer.list){
           if (layer.list[i].name == name) {
diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
index 3746ea2..8e14b8f 100644
--- a/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -9,7 +9,7 @@ function layerDetailsPageInit (ctx) {
   var addRmLayerBtn = $("#add-remove-layer-btn");
 
   /* setup the dependencies typeahead */
-  libtoaster.makeTypeahead(layerDepInput, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" }, function(item){
+  libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" }, function(item){
     currentLayerDepSelection = item;
 
     layerDepBtn.removeAttr("disabled");
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 99e1f03..72fb0a93 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -10,16 +10,16 @@ var libtoaster = (function (){
    * xhrUrl: the url to get the JSON from expects JSON in the form:
    *  { "list": [ { "name": "test", "detail" : "a test thing"  }, .... ] }
    * xhrParams: the data/parameters to pass to the getJSON url e.g.
-   *  { 'type' : 'projects' } the text typed will be passed as 'value'.
+   *  { 'type' : 'projects' } the text typed will be passed as 'search'.
    *  selectedCB: function to call once an item has been selected one
    *  arg of the item.
    */
-  function _makeTypeahead (jQElement, xhrParams, selectedCB) {
+  function _makeTypeahead (jQElement, xhrUrl, xhrParams, selectedCB) {
 
     jQElement.typeahead({
         source: function(query, process){
-          xhrParams.value = query;
-          $.getJSON(libtoaster.ctx.xhrDataTypeaheadUrl, this.options.xhrParams, function(data){
+          xhrParams.search = query;
+          $.getJSON(xhrUrl, this.options.xhrParams, function(data){
             if (data.error !== "ok") {
               console.log("Error getting data from server "+data.error);
               return;
@@ -41,7 +41,7 @@ var libtoaster = (function (){
           return $('<span></span>').text(item.name).get(0);
         },
         sorter: function (items) { return items; },
-        xhrUrl: libtoaster.ctx.xhrDataTypeaheadUrl,
+        xhrUrl: xhrUrl,
         xhrParams: xhrParams,
     });
 
@@ -172,7 +172,7 @@ var libtoaster = (function (){
 
   function _getLayerDepsForProject(projectId, layerId, onSuccess, onFail){
     /* Check for dependencies not in the current project */
-    $.getJSON(libtoaster.ctx.xhrDataTypeaheadUrl,
+    $.getJSON(libtoaster.ctx.xhrProjectDataTypeaheadUrl,
       { type: 'layerdeps', 'value': layerId , project_id: projectId },
       function(data) {
         if (data.error != "ok") {
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index 230dee4..e10dc11 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -31,11 +31,19 @@
     projectId : {{project.id|default:'undefined'}},
     jsUrl : "{% static 'js/' %}",
     htmlUrl : "{% static 'html/' %}",
+    projectsUrl : "{% url 'all-projects' %}",
     {% if project.id %}
-    xhrDataTypeaheadUrl : "{% url 'xhr_datatypeahead' project.id %}",
-    xhrProjectEditUrl : "{% url 'xhr_projectedit' project.id %}",
-    projectPageUrl : "{% url 'project' project.id %}",
-    projectName : "{{project.name}}",
+      xhrProjectDataTypeaheadUrl : "{% url 'xhr_datatypeahead' project.id %}",
+      xhrProjectEditUrl : "{% url 'xhr_projectedit' project.id %}",
+      projectPageUrl : "{% url 'project' project.id %}",
+      projectName : "{{project.name}}",
+      projectId : {{project.id}},
+    {% else %}
+      xhrProjectDataTypeaheadUrl : undefined,
+      xhrProjectEditUrl : undefined,
+      projectPageUrl : undefined,
+      projectName : undefined,
+      projectId : undefined,
     {% endif %}
   };
 </script>
@@ -45,8 +53,6 @@
   $(document).ready(function () {
     /* Vars needed for base.js */
     var ctx = {};
-    ctx.projectBuildUrl = "{% url 'xhr_build' %}";
-    ctx.projectBasePageUrl = "{% url 'base_project' %}";
     ctx.projectInfoUrl = "{% url 'xhr_projectinfo' %}";
     ctx.numProjects = {{projects|length}};
     ctx.currentUrl = "{{request.path|escapejs}}";
@@ -99,7 +105,7 @@
                     <div class="input-append">
                       <input type="text" class="input-medium" id="project-name-input" placeholder="Type a project name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead"/>
                         <button id="save-project-button" class="btn" type="button">Save</button>
-                        <a href="#" id="cancel-change-project" class="btn btn-link">Cancel</a>
+                        <a href="#" id="cancel-change-project" class="btn btn-link" style="display: none">Cancel</a>
                       </div>
                       <p><a id="view-all-projects" href="{% url 'all-projects' %}">View all projects</a></p>
                     </form>
@@ -111,9 +117,9 @@
                   <li id="targets-form">
                     <h6>Recipe(s):</h6>
                     <form>
-                      <input type="text" class="input-xlarge" id="build-target-input" placeholder="Type a recipe name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" />
+                      <input type="text" class="input-xlarge" id="build-target-input" placeholder="Type a recipe name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" disabled/>
                       <div>
-                        <a class="btn btn-primary" id="build-button" data-project-id="{{project.id}}">Build</a>
+                        <button class="btn btn-primary" id="build-button" data-project-id="{{project.id}}" disabled>Build</button>
                       </div>
                     </form>
                   </li>
diff --git a/lib/toaster/toastergui/templates/basetable_bottom.html b/lib/toaster/toastergui/templates/basetable_bottom.html
index 4c28cae..ce023f5 100644
--- a/lib/toaster/toastergui/templates/basetable_bottom.html
+++ b/lib/toaster/toastergui/templates/basetable_bottom.html
@@ -57,7 +57,7 @@
         }
     }
 
-    // load cookie for number of entries to be displayed on page
+    // load data for number of entries to be displayed on page
     if ({{request.GET.count}} != "") {
       pagesize = {{request.GET.count}};
     }
diff --git a/lib/toaster/toastergui/templates/detail_pagination_bottom.html b/lib/toaster/toastergui/templates/detail_pagination_bottom.html
index 434facb..f40c21d 100644
--- a/lib/toaster/toastergui/templates/detail_pagination_bottom.html
+++ b/lib/toaster/toastergui/templates/detail_pagination_bottom.html
@@ -38,7 +38,7 @@
 <!-- Update page display settings -->
 <script>
  $(document).ready(function() {
-    // load cookie for number of entries to be displayed on page
+    // load data for number of entries to be displayed on page
     if ({{request.GET.count}} != "") {
       pagesize = {{request.GET.count}};
     }
diff --git a/lib/toaster/toastergui/templatetags/projecttags.py b/lib/toaster/toastergui/templatetags/projecttags.py
index 8028ae0..e79a4e5 100644
--- a/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/lib/toaster/toastergui/templatetags/projecttags.py
@@ -51,11 +51,11 @@ def get_tasks(queryset):
 
 
 @register.filter(name = "json")
-def json(value):
+def json(value, default = None):
     # JSON spec says that "\/" is functionally identical to "/" to allow for HTML-tag embedding in JSON strings
     # unfortunately, I can't find any option in the json module to turn on forward-slash escaping, so we do
     # it manually here
-    return mark_safe(JsonLib.dumps(value, ensure_ascii=False).replace('</', '<\\/'))
+    return mark_safe(JsonLib.dumps(value, default = default, ensure_ascii=False).replace('</', '<\\/'))
 
 @register.assignment_tag
 def query(qs, **kwargs):
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 7a1132f..4e328da 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -77,8 +77,6 @@ urlpatterns = patterns('toastergui.views',
 
         url(r'^projects/$', 'projects', name='all-projects'),
 
-        url(r'^project/$', lambda x: HttpResponseBadRequest(), name='base_project'),
-
         url(r'^project/(?P<pid>\d+)/$', 'project', name='project'),
         url(r'^project/(?P<pid>\d+)/configuration$', 'projectconf', name='projectconf'),
         url(r'^project/(?P<pid>\d+)/builds/$', 'projectbuilds', name='projectbuilds'),
@@ -110,7 +108,6 @@ urlpatterns = patterns('toastergui.views',
             name="all-layers"),
 
 
-        url(r'^xhr_build/$', 'xhr_build', name='xhr_build'),
         url(r'^xhr_projectbuild/(?P<pid>\d+)$', 'xhr_projectbuild', name='xhr_projectbuild'),
         url(r'^xhr_projectinfo/$', 'xhr_projectinfo', name='xhr_projectinfo'),
         url(r'^xhr_projectedit/(?P<pid>\d+)$', 'xhr_projectedit', name='xhr_projectedit'),
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 0e248a7..d4a9b4c 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -129,7 +129,19 @@ def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
         if not i in params:
             params[i] = urllib.unquote(str(mandatory_parameters[i]))
 
-    return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs)
+    return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, *args, **kwargs)
+
+class RedirectException(Exception):
+    def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
+        super(RedirectException, self).__init__()
+        self.view = view
+        self.g = g
+        self.mandatory_parameters = mandatory_parameters
+        self.oargs  = args
+        self.okwargs = kwargs
+
+    def get_redirect_response(self):
+        return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, self.okwargs)
 
 FIELD_SEPARATOR = ":"
 AND_VALUE_SEPARATOR = "!"
@@ -2200,14 +2212,6 @@ if toastermain.settings.MANAGED:
         response['Pragma'] = "no-cache"
         return response
 
-    # This is a wrapper for xhr_projectbuild which allows for a project id
-    # which only becomes known client side.
-    def xhr_build(request):
-        if request.POST.has_key("project_id"):
-            pid = request.POST['project_id']
-            return xhr_projectbuild(request, pid)
-        else:
-            raise BadParameterException("invalid project id")
 
     def xhr_projectbuild(request, pid):
         try:
@@ -2333,7 +2337,7 @@ if toastermain.settings.MANAGED:
             # returns layers for current project release that are not in the project set, matching the name
             if request.GET['type'] == "layers":
                 # all layers for the current project
-                queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('value',''))
+                queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('search',''))
 
                 # but not layers with equivalent layers already in project
                 if not request.GET.has_key('include_added'):
@@ -2348,7 +2352,7 @@ if toastermain.settings.MANAGED:
             # returns layer dependencies for a layer, excluding current project layers
             if request.GET['type'] == "layerdeps":
                 queryset = prj.compatible_layerversions().exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()]).filter(
-                    layer__name__in = [ x.depends_on.layer.name for x in LayerVersionDependency.objects.filter(layer_version_id = request.GET['value'])])
+                    layer__name__in = [ x.depends_on.layer.name for x in LayerVersionDependency.objects.filter(layer_version_id = request.GET['search'])])
 
                 final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset])
 
@@ -2361,7 +2365,7 @@ if toastermain.settings.MANAGED:
 
                 retval = []
                 for i in prj.projectlayer_set.all():
-                    lv = prj.compatible_layerversions(release = Release.objects.get(pk=request.GET['value'])).filter(layer__name = i.layercommit.layer.name)
+                    lv = prj.compatible_layerversions(release = Release.objects.get(pk=request.GET['search'])).filter(layer__name = i.layercommit.layer.name)
                     # there is no layer_version with the new release id, and the same name
                     if lv.count() < 1:
                         retval.append(i)
@@ -2374,10 +2378,10 @@ if toastermain.settings.MANAGED:
             # returns layer versions that provide the named targets
             if request.GET['type'] == "layers4target":
                 # we return data only if the recipe can't be provided by the current project layer set
-                if reduce(lambda x, y: x + y, [x.recipe_layer_version.filter(name=request.GET['value']).count() for x in prj.projectlayer_equivalent_set()], 0):
+                if reduce(lambda x, y: x + y, [x.recipe_layer_version.filter(name=request.GET['search']).count() for x in prj.projectlayer_equivalent_set()], 0):
                     final_list = []
                 else:
-                    queryset_all = prj.compatible_layerversions().filter(recipe_layer_version__name = request.GET['value'])
+                    queryset_all = prj.compatible_layerversions().filter(recipe_layer_version__name = request.GET['search'])
 
                     # exclude layers in the project
                     queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])
@@ -2389,7 +2393,7 @@ if toastermain.settings.MANAGED:
 
             # returns targets provided by current project layers
             if request.GET['type'] == "targets":
-                search_token = request.GET.get('value','')
+                search_token = request.GET.get('search','')
                 queryset_all = Recipe.objects.filter(layer_version__layer__name__in = [x.layercommit.layer.name for x in prj.projectlayer_set.all().select_related("layercommit__layer")]).filter(Q(name__icontains=search_token) | Q(layer_version__layer__name__icontains=search_token))
 
 #                layer_equivalent_set = []
@@ -2420,7 +2424,7 @@ if toastermain.settings.MANAGED:
                 if 'project_id' in request.session:
                     queryset_all = queryset_all.filter(layer_version__in =  prj.projectlayer_equivalent_set()).order_by("name")
 
-                search_token = request.GET.get('value','')
+                search_token = request.GET.get('search','')
                 queryset_all = queryset_all.filter(Q(name__icontains=search_token) | Q(description__icontains=search_token))
 
                 return HttpResponse(jsonfilter({ "error":"ok",
@@ -2432,15 +2436,6 @@ if toastermain.settings.MANAGED:
                         )
                     }), content_type = "application/json")
 
-            # returns all projects
-            if request.GET['type'] == "projects":
-                queryset_all = Project.objects.all()
-                ret = { "error": "ok",
-                       "list": map (lambda x: {"id":x.pk, "name": x.name},
-                                    queryset_all.filter(name__icontains=request.GET.get('value',''))[:8])}
-
-                return HttpResponse(jsonfilter(ret), content_type = "application/json")
-
             raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied"))
         except Exception as e:
             return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
@@ -2866,14 +2861,38 @@ if toastermain.settings.MANAGED:
         return build_mru
 
 
-    def projects(request):
-        template="projects.html"
+    def template_renderer(template):
+        def func_wrapper(view):
+            def returned_wrapper(request, *args, **kwargs):
+                try:
+                    context = view(request, *args, **kwargs)
+                except RedirectException as e:
+                    return e.get_redirect_response()
+
+                if request.GET.get('format', None) == 'json':
+                    # objects is a special keyword - it's a Page, but we need the actual objects here
+                    # in XHR, the objects come in the "list" property
+                    if "objects" in context:
+                        context["list"] = context["objects"].object_list
+                        del context["objects"]
+
+                    # we're about to return; to keep up with the XHR API, we set the error to OK
+                    context["error"] = "ok"
+
+                    return HttpResponse(jsonfilter(context, default=lambda obj: obj.isoformat() if isinstance(obj, datetime) else obj.__dict__ ),
+                                content_type = "application/json; charset=utf-8")
+                else:
+                    return render(request, template, context)
+            return returned_wrapper
+        return func_wrapper
 
+    @template_renderer("projects.html")
+    def projects(request):
         (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-')
         mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
         retval = _verify_parameters( request.GET, mandatory_parameters )
         if retval:
-            return _redirect_parameters( 'all-projects', request.GET, mandatory_parameters)
+            raise RedirectException( 'all-projects', request.GET, mandatory_parameters )
 
         queryset_all = Project.objects.all()
 
@@ -2886,6 +2905,14 @@ if toastermain.settings.MANAGED:
         # retrieve the objects that will be displayed in the table; projects a paginator and gets a page range to display
         project_info = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
 
+        # add fields needed in JSON dumps for API call support
+        for p in project_info.object_list:
+            p.id = p.pk
+            p.xhrProjectDataTypeaheadUrl = reverse('xhr_datatypeahead', args=(p.id,))
+            p.projectPageUrl = reverse('project', args=(p.id,))
+            p.xhrProjectEditUrl = reverse('xhr_projectedit', args=(p.id,))
+            p.projectBuildUrl = reverse('xhr_projectbuild', args=(p.id,))
+
         # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
         build_mru = _managed_get_latest_builds()
 
@@ -2965,9 +2992,8 @@ if toastermain.settings.MANAGED:
                     ]
             }
 
-        response = render(request, template, context)
         _set_parameters_values(pagesize, orderby, request)
-        return response
+        return context
 
     def buildrequestdetails(request, pid, brid):
         template = "buildrequestdetails.html"
@@ -3185,9 +3211,6 @@ else:
     def xhr_projectbuild(request, pid):
         return render(request, 'landing_not_managed.html')
 
-    def xhr_build(request):
-        return render(request, 'landing_not_managed.html')
-
     def xhr_projectinfo(request):
         return render(request, 'landing_not_managed.html')
 
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 02/19] convert all project-based view to JSON APIs
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 01/19] toaster: move project data typeahead to the REST API Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 03/19] toaster: toastertables REST refactoring Alex DAMIAN
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch converts all-project views that are REST-style URLs
to support JSON encoding if the "format=json" parameter is supplied.

The formatting code is enhanced to prevent following Django foreign
keys, to convert enum-values from int to string, and support for
timedelta.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/views.py | 164 ++++++++++++++++++++++++----------------
 1 file changed, 99 insertions(+), 65 deletions(-)

diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index d4a9b4c..f2626f8 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -87,6 +87,55 @@ def _project_recent_build_list(prj):
             list(prj.buildrequest_set.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3]))
 
 
+def _template_renderer(template):
+    def func_wrapper(view):
+        def returned_wrapper(request, *args, **kwargs):
+            try:
+                context = view(request, *args, **kwargs)
+            except RedirectException as e:
+                return e.get_redirect_response()
+
+            if request.GET.get('format', None) == 'json':
+                # objects is a special keyword - it's a Page, but we need the actual objects here
+                # in XHR, the objects come in the "list" property
+                if "objects" in context:
+                    context["list"] = context["objects"].object_list
+                    del context["objects"]
+
+                # we're about to return; to keep up with the XHR API, we set the error to OK
+                context["error"] = "ok"
+                def _objtojson(obj):
+                    from django.db.models.query import QuerySet
+                    from django.db.models import Model, IntegerField
+                    if isinstance(obj, datetime):
+                        return obj.isoformat()
+                    elif isinstance(obj, timedelta):
+                        return obj.total_seconds()
+                    elif isinstance(obj, QuerySet) or isinstance(obj, set):
+                        return list(obj)
+                    elif hasattr( obj, '__dict__'):
+                        d = obj.__dict__
+                        nd = dict(d)
+                        for di in d:
+                            if di.startswith("_"):
+                                del nd[di]
+                            elif isinstance(d[di], Model):
+                                nd[di] = d[di].pk
+                            elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
+                                nd[di] = getattr(obj, "get_%s_display" % di)()
+                        return nd
+                    else:
+                        raise TypeError("Unserializable object %s of type %s" % ( obj, type(obj)))
+
+                return HttpResponse(jsonfilter(context, default=_objtojson ),
+                            content_type = "application/json; charset=utf-8")
+            else:
+                return render(request, template, context)
+        return returned_wrapper
+    return func_wrapper
+
+
+
 def _build_page_range(paginator, index = 1):
     try:
         page = paginator.page(index)
@@ -129,7 +178,7 @@ def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
         if not i in params:
             params[i] = urllib.unquote(str(mandatory_parameters[i]))
 
-    return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, *args, **kwargs)
+    return redirect(url + "?%s" % urllib.urlencode(params), permanent = False, **kwargs)
 
 class RedirectException(Exception):
     def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
@@ -141,7 +190,7 @@ class RedirectException(Exception):
         self.okwargs = kwargs
 
     def get_redirect_response(self):
-        return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, self.okwargs)
+        return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
 
 FIELD_SEPARATOR = ":"
 AND_VALUE_SEPARATOR = "!"
@@ -1839,8 +1888,8 @@ if toastermain.settings.MANAGED:
 
 
     # shows the "all builds" page for managed mode; it displays build requests (at least started!) instead of actual builds
+    @_template_renderer("managed_builds.html")
     def builds(request):
-        template = 'managed_builds.html'
         # define here what parameters the view needs in the GET portion in order to
         # be able to display something.  'count' and 'page' are mandatory for all views
         # that use paginators.
@@ -1850,12 +1899,10 @@ if toastermain.settings.MANAGED:
         try:
             context, pagesize, orderby = _build_list_helper(request, buildrequests, True)
         except InvalidRequestException as e:
-            return _redirect_parameters( builds, request.GET, e.response)
+            raise RedirectException( builds, request.GET, e.response)
 
-        response = render(request, template, context)
         _set_parameters_values(pagesize, orderby, request)
-        return response
-
+        return context
 
 
     # helper function, to be used on "all builds" and "project builds" pages
@@ -2151,8 +2198,8 @@ if toastermain.settings.MANAGED:
 
 
     # Shows the edit project page
+    @_template_renderer('project.html')
     def project(request, pid):
-        template = "project.html"
         try:
             prj = Project.objects.get(id = pid)
         except Project.DoesNotExist:
@@ -2207,10 +2254,7 @@ if toastermain.settings.MANAGED:
         except ProjectVariable.DoesNotExist:
             context["distro"] = "-- not set yet"
 
-        response = render(request, template, context)
-        response['Cache-Control'] = "no-cache, must-revalidate, no-store"
-        response['Pragma'] = "no-cache"
-        return response
+        return context
 
 
     def xhr_projectbuild(request, pid):
@@ -2680,8 +2724,8 @@ if toastermain.settings.MANAGED:
 
         return(vars_managed,sorted(vars_fstypes),vars_blacklist)
 
+    @_template_renderer("projectconf.html")
     def projectconf(request, pid):
-        template = "projectconf.html"
 
         try:
             prj = Project.objects.get(id = pid)
@@ -2730,21 +2774,20 @@ if toastermain.settings.MANAGED:
         except ProjectVariable.DoesNotExist:
             pass
 
-        return render(request, template, context)
+        return context
 
+    @_template_renderer('projectbuilds.html')
     def projectbuilds(request, pid):
-        template = 'projectbuilds.html'
         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, False)
         except InvalidRequestException as e:
-            return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid)
+            raise RedirectException('projectbuilds', request.GET, e.response, pid = pid)
 
-        response = render(request, template, context)
         _set_parameters_values(pagesize, orderby, request)
 
-        return response
+        return context
 
 
     def _file_name_for_artifact(b, artifact_type, artifact_id):
@@ -2861,32 +2904,8 @@ if toastermain.settings.MANAGED:
         return build_mru
 
 
-    def template_renderer(template):
-        def func_wrapper(view):
-            def returned_wrapper(request, *args, **kwargs):
-                try:
-                    context = view(request, *args, **kwargs)
-                except RedirectException as e:
-                    return e.get_redirect_response()
-
-                if request.GET.get('format', None) == 'json':
-                    # objects is a special keyword - it's a Page, but we need the actual objects here
-                    # in XHR, the objects come in the "list" property
-                    if "objects" in context:
-                        context["list"] = context["objects"].object_list
-                        del context["objects"]
-
-                    # we're about to return; to keep up with the XHR API, we set the error to OK
-                    context["error"] = "ok"
-
-                    return HttpResponse(jsonfilter(context, default=lambda obj: obj.isoformat() if isinstance(obj, datetime) else obj.__dict__ ),
-                                content_type = "application/json; charset=utf-8")
-                else:
-                    return render(request, template, context)
-            return returned_wrapper
-        return func_wrapper
 
-    @template_renderer("projects.html")
+    @_template_renderer("projects.html")
     def projects(request):
         (pagesize, orderby) = _get_parameters_values(request, 10, 'updated:-')
         mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
@@ -2995,18 +3014,19 @@ if toastermain.settings.MANAGED:
         _set_parameters_values(pagesize, orderby, request)
         return context
 
+    @_template_renderer("buildrequestdetails.html")
     def buildrequestdetails(request, pid, brid):
-        template = "buildrequestdetails.html"
         context = {
             'buildrequest' : BuildRequest.objects.get(pk = brid, project_id = pid)
         }
-        return render(request, template, context)
+        return context
 
 
 else:
     # shows the "all builds" page for interactive mode; this is the old code, simply moved
+
+    @_template_renderer('build.html')
     def builds(request):
-        template = 'build.html'
         # define here what parameters the view needs in the GET portion in order to
         # be able to display something.  'count' and 'page' are mandatory for all views
         # that use paginators.
@@ -3014,7 +3034,7 @@ else:
         mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
         retval = _verify_parameters( request.GET, mandatory_parameters )
         if retval:
-            return _redirect_parameters( 'all-builds', request.GET, mandatory_parameters)
+            raise RedirectException( 'all-builds', request.GET, mandatory_parameters)
 
         # boilerplate code that takes a request for an object type and returns a queryset
         # for that object type. copypasta for all needed table searches
@@ -3194,55 +3214,69 @@ else:
 
         # merge daterange values
         context.update(context_date)
-
-        response = render(request, template, context)
         _set_parameters_values(pagesize, orderby, request)
-        return response
+
+        return context
 
 
 
 
+    @_template_renderer('landing_not_managed.html')
     def newproject(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def project(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_projectbuild(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_projectinfo(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_projectedit(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_datatypeahead(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_configvaredit(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def importlayer(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def projectconf(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def projectbuilds(request, pid):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def build_artifact(request, build_id, artifact_type, artifact_id):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def projects(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_importlayer(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def xhr_updatelayer(request):
-        return render(request, 'landing_not_managed.html')
+        return {}
 
+    @_template_renderer('landing_not_managed.html')
     def buildrequestdetails(request, pid, brid):
-        return render(request, 'landing_not_managed.html')
+        return {}
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 03/19] toaster: toastertables REST refactoring
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 01/19] toaster: move project data typeahead to the REST API Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 02/19] convert all project-based view to JSON APIs Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 04/19] toastergui: use defaults on xhr_datatypeahead parameters Alex DAMIAN
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch refactors the ToasterTables to bring them in line
with REST principles -

- all table pages now support the "format=json" GET parameter
that returns the data in JSON format
- the tables themselves

This cleans up the URL namespace by aleviating the need to
have two URLS for each table (one for the template and one for
the data loading), and fixes minor things in the ToasterTable
implementation.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/static/js/table.js          |  5 +-
 lib/toaster/toastergui/tables.py                   | 84 ++++++++++++++--------
 lib/toaster/toastergui/templates/layerdetails.html |  2 +-
 .../toastergui/templates/toastertable-simple.html  |  2 +-
 lib/toaster/toastergui/templates/toastertable.html |  2 +-
 lib/toaster/toastergui/urls.py                     | 30 +++++---
 lib/toaster/toastergui/widgets.py                  | 47 +++++-------
 7 files changed, 102 insertions(+), 70 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/table.js b/lib/toaster/toastergui/static/js/table.js
index 2e35e38..7588a4a 100644
--- a/lib/toaster/toastergui/static/js/table.js
+++ b/lib/toaster/toastergui/static/js/table.js
@@ -383,12 +383,13 @@ function tableInit(ctx){
      */
     var params = {
       'name' : filterName,
-      'search': tableParams.search
+      'search': tableParams.search,
+      'cmd': 'filterinfo',
     };
 
     $.ajax({
         type: "GET",
-        url: ctx.url + 'filterinfo',
+        url: ctx.url,
         data: params,
         headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
         success: function (filterData) {
diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index 9a93ff9..b75e565 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -19,7 +19,7 @@
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-from widgets import ToasterTable
+from toastergui.widgets import ToasterTable
 from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
 from django.db.models import Q, Max
 from django.conf.urls import url
@@ -29,9 +29,19 @@ class LayersTable(ToasterTable):
     """Table of layers in Toaster"""
 
     def __init__(self, *args, **kwargs):
-        ToasterTable.__init__(self)
+        super(LayersTable, self).__init__(*args, **kwargs)
         self.default_orderby = "layer__name"
 
+    def get_context_data(self, **kwargs):
+        context = super(LayersTable, self).get_context_data(**kwargs)
+
+        context['project'] = Project.objects.get(pk=kwargs['pid'])
+
+        context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
+
+        return context
+
+
     def setup_queryset(self, *args, **kwargs):
         prj = Project.objects.get(pk = kwargs['pid'])
         compatible_layers = prj.compatible_layerversions()
@@ -132,14 +142,31 @@ class LayersTable(ToasterTable):
                         static_data_name="add-del-layers",
                         static_data_template='{% include "layer_btn.html" %}')
 
+class LayerDetails(TemplateView):
+    def get_context_data(self, **kwargs):
+        context = super(LayerDetails, self).get_context_data(**kwargs)
+
+        context['project'] = Project.objects.get(pk=kwargs['pid'])
+        context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
+        context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
+
+        return context
+
 class MachinesTable(ToasterTable):
     """Table of Machines in Toaster"""
 
     def __init__(self, *args, **kwargs):
-        ToasterTable.__init__(self)
+        super(MachinesTable, self).__init__(*args, **kwargs)
         self.empty_state = "No machines maybe you need to do a build?"
         self.default_orderby = "name"
 
+    def get_context_data(self, **kwargs):
+        context = super(MachinesTable, self).get_context_data(**kwargs)
+        context['project'] = Project.objects.get(pk=kwargs['pid'])
+        context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
+        return context
+
+
     def setup_queryset(self, *args, **kwargs):
         prj = Project.objects.get(pk = kwargs['pid'])
         compatible_layers = prj.compatible_layerversions()
@@ -191,7 +218,13 @@ class LayerMachinesTable(MachinesTable):
     """ Smaller version of the Machines table for use in layer details """
 
     def __init__(self, *args, **kwargs):
-        MachinesTable.__init__(self)
+        super(LayerMachinesTable, self).__init__(*args, **kwargs)
+
+    def get_context_data(self, **kwargs):
+        context = super(LayerMachinesTable, self).get_context_data(**kwargs)
+        context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
+        return context
+
 
     def setup_queryset(self, *args, **kwargs):
         MachinesTable.setup_queryset(self, *args, **kwargs)
@@ -219,10 +252,20 @@ class RecipesTable(ToasterTable):
     """Table of Recipes in Toaster"""
 
     def __init__(self, *args, **kwargs):
-        ToasterTable.__init__(self)
+        super(RecipesTable, self).__init__(*args, **kwargs)
         self.empty_state = "Toaster has no recipe information. To generate recipe information you can configure a layer source then run a build."
         self.default_orderby = "name"
 
+    def get_context_data(self, **kwargs):
+        context = super(RecipesTable, self).get_context_data(**kwargs)
+
+        context['project'] = Project.objects.get(pk=kwargs['pid'])
+
+        context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
+
+        return context
+
+
     def setup_queryset(self, *args, **kwargs):
         prj = Project.objects.get(pk = kwargs['pid'])
 
@@ -293,10 +336,16 @@ class RecipesTable(ToasterTable):
                         static_data_template='{% include "recipe_btn.html" %}')
 
 class LayerRecipesTable(RecipesTable):
-    """ Smaller version of the Machines table for use in layer details """
+    """ Smaller version of the Recipes table for use in layer details """
 
     def __init__(self, *args, **kwargs):
-        RecipesTable.__init__(self)
+        super(LayerRecipesTable, self).__init__(*args, **kwargs)
+
+    def get_context_data(self, **kwargs):
+        context = super(LayerRecipesTable, self).get_context_data(**kwargs)
+        context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
+        return context
+
 
     def setup_queryset(self, *args, **kwargs):
         RecipesTable.setup_queryset(self, *args, **kwargs)
@@ -320,24 +369,3 @@ class LayerRecipesTable(RecipesTable):
         self.add_column(title="Build recipe",
                         static_data_name="add-del-layers",
                         static_data_template=build_recipe_template)
-
-
-
-# This needs to be staticaly defined here as django reads the url patterns
-# on start up
-urlpatterns = (
-    url(r'^machines/(?P<cmd>\w+)*', MachinesTable.as_view(),
-        name=MachinesTable.__name__.lower()),
-    url(r'^layers/(?P<cmd>\w+)*', LayersTable.as_view(),
-        name=LayersTable.__name__.lower()),
-    url(r'^recipes/(?P<cmd>\w+)*', RecipesTable.as_view(),
-        name=RecipesTable.__name__.lower()),
-
-    # layer details tables
-    url(r'^layer/(?P<layerid>\d+)/recipes/(?P<cmd>\w+)*',
-        LayerRecipesTable.as_view(),
-        name=LayerRecipesTable.__name__.lower()),
-    url(r'^layer/(?P<layerid>\d+)/machines/(?P<cmd>\w+)*',
-        LayerMachinesTable.as_view(),
-        name=LayerMachinesTable.__name__.lower()),
-)
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index 259a59e..c27d259 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -33,7 +33,7 @@
 
   $(document).ready(function (){
     var ctx = {
-      projectBuildUrl : "{% url 'xhr_build' %}",
+      projectBuildUrl : "{% url 'xhr_projectbuild' project.id %}",
       layerDetailsUrl : "{% url 'base_layerdetails' project.id %}",
       xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}",
       layerVersion : {
diff --git a/lib/toaster/toastergui/templates/toastertable-simple.html b/lib/toaster/toastergui/templates/toastertable-simple.html
index 23a7526..ea7b38e 100644
--- a/lib/toaster/toastergui/templates/toastertable-simple.html
+++ b/lib/toaster/toastergui/templates/toastertable-simple.html
@@ -10,7 +10,7 @@
 
     var ctx = {
       tableName : "{{table_name}}",
-      url : "{{ xhr_table_url }}",
+      url : "{{ xhr_table_url }}?format=json",
       title : "{{title}}",
       projectLayers : {{projectlayers|json}},
     };
diff --git a/lib/toaster/toastergui/templates/toastertable.html b/lib/toaster/toastergui/templates/toastertable.html
index 5c79ab4..c7c7a84 100644
--- a/lib/toaster/toastergui/templates/toastertable.html
+++ b/lib/toaster/toastergui/templates/toastertable.html
@@ -10,7 +10,7 @@
 
     var ctx = {
       tableName : "{{table_name}}",
-      url : "{{ xhr_table_url }}",
+      url : "{{ xhr_table_url }}?format=json",
       title : "{{title}}",
       projectLayers : {{projectlayers|json}},
     };
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 4e328da..e10e0bb 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -20,8 +20,7 @@ from django.conf.urls import patterns, include, url
 from django.views.generic import RedirectView, TemplateView
 
 from django.http import HttpResponseBadRequest
-import tables
-from widgets import ToasterTemplateView
+from toastergui import tables
 
 urlpatterns = patterns('toastergui.views',
         # landing page
@@ -81,32 +80,46 @@ urlpatterns = patterns('toastergui.views',
         url(r'^project/(?P<pid>\d+)/configuration$', 'projectconf', name='projectconf'),
         url(r'^project/(?P<pid>\d+)/builds/$', 'projectbuilds', name='projectbuilds'),
 
-        url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)$',
-            ToasterTemplateView.as_view(template_name='layerdetails.html'),
-            name='layerdetails'),
         url(r'^project/(?P<pid>\d+)/layer/$', lambda x,pid: HttpResponseBadRequest(), name='base_layerdetails'),
 
         # the import layer is a project-specific functionality;
         url(r'^project/(?P<pid>\d+)/importlayer$', 'importlayer', name='importlayer'),
 
+
+        # the table pages that have been converted to ToasterTable widget
         url(r'^project/(?P<pid>\d+)/machines/$',
-            ToasterTemplateView.as_view(template_name="generic-toastertable-page.html"),
+            tables.MachinesTable.as_view(template_name="generic-toastertable-page.html"),
             { 'table_name': tables.MachinesTable.__name__.lower(),
               'title' : 'All compatible machines' },
             name="all-machines"),
 
         url(r'^project/(?P<pid>\d+)/recipes/$',
-            ToasterTemplateView.as_view(template_name="generic-toastertable-page.html"),
+            tables.RecipesTable.as_view(template_name="generic-toastertable-page.html"),
             { 'table_name': tables.RecipesTable.__name__.lower(),
               'title' : 'All compatible recipes' },
             name="all-targets"),
 
         url(r'^project/(?P<pid>\d+)/layers/$',
-            ToasterTemplateView.as_view(template_name="generic-toastertable-page.html"),
+            tables.LayersTable.as_view(template_name="generic-toastertable-page.html"),
             { 'table_name': tables.LayersTable.__name__.lower(),
               'title' : 'All compatible layers' },
             name="all-layers"),
 
+        url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)$',
+            tables.LayerDetails.as_view(template_name='layerdetails.html'),
+            name='layerdetails'),
+
+        url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)/recipes/$',
+            tables.LayerRecipesTable.as_view(template_name="generic-toastertable-page.html"),
+            { 'table_name': tables.LayerRecipesTable.__name__.lower(),
+              'title' : 'All recipes in layer' },
+             name=tables.LayerRecipesTable.__name__.lower()),
+
+        url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)/machines/$',
+            tables.LayerMachinesTable.as_view(template_name="generic-toastertable-page.html"),
+            { 'table_name': tables.LayerMachinesTable.__name__.lower(),
+              'title' : 'All machines in layer' },
+            name=tables.LayerMachinesTable.__name__.lower()),
 
         url(r'^xhr_projectbuild/(?P<pid>\d+)$', 'xhr_projectbuild', name='xhr_projectbuild'),
         url(r'^xhr_projectinfo/$', 'xhr_projectinfo', name='xhr_projectinfo'),
@@ -116,7 +129,6 @@ urlpatterns = patterns('toastergui.views',
         url(r'^xhr_datatypeahead/(?P<pid>\d+)$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
         url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
         url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'),
-        url(r'^xhr_tables/project/(?P<pid>\d+)/', include('toastergui.tables')),
 
         # dashboard for failed build requests
         url(r'^project/(?P<pid>\d+)/buildrequest/(?P<brid>\d+)$', 'buildrequestdetails', name='buildrequestdetails'),
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 8cf6e1b..4347a3f 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -32,29 +32,17 @@ from django.core.serializers.json import DjangoJSONEncoder
 from django.core.exceptions import FieldError
 from django.conf.urls import url, patterns
 
-import urls
 import types
 import json
 import collections
 import operator
 
 
-class ToasterTemplateView(TemplateView):
-    def get_context_data(self, **kwargs):
-      context = super(ToasterTemplateView, self).get_context_data(**kwargs)
-      if 'pid' in kwargs:
-          context['project'] = Project.objects.get(pk=kwargs['pid'])
-
-          context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
-
-      if 'layerid' in kwargs:
-          context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
-
-      return context
-
-
-class ToasterTable(View):
-    def __init__(self):
+class ToasterTable(TemplateView):
+    def __init__(self, *args, **kwargs):
+        super(ToasterTable, self).__init__()
+        if 'template_name' in kwargs:
+            self.template_name = kwargs['template_name']
         self.title = None
         self.queryset = None
         self.columns = []
@@ -66,20 +54,23 @@ class ToasterTable(View):
         self.default_orderby = ""
 
     def get(self, request, *args, **kwargs):
-        self.setup_queryset(*args, **kwargs)
+        if request.GET.get('format', None) == 'json':
 
-        # Put the project id into the context for the static_data_template
-        if 'pid' in kwargs:
-            self.static_context_extra['pid'] = kwargs['pid']
+            self.setup_queryset(*args, **kwargs)
+            # Put the project id into the context for the static_data_template
+            if 'pid' in kwargs:
+                self.static_context_extra['pid'] = kwargs['pid']
 
-        cmd = kwargs['cmd']
-        if cmd and 'filterinfo' in cmd:
-            data = self.get_filter_info(request)
-        else:
-            # If no cmd is specified we give you the table data
-            data = self.get_data(request, **kwargs)
+            cmd = request.GET.get('cmd', None)
+            if cmd and 'filterinfo' in cmd:
+                data = self.get_filter_info(request)
+            else:
+                # If no cmd is specified we give you the table data
+                data = self.get_data(request, **kwargs)
+
+            return HttpResponse(data, content_type="application/json")
 
-        return HttpResponse(data, content_type="application/json")
+        return super(ToasterTable, self).get(request, *args, **kwargs)
 
     def get_filter_info(self, request):
         data = None
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 04/19] toastergui: use defaults on xhr_datatypeahead parameters
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (2 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 03/19] toaster: toastertables REST refactoring Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 05/19] toastergui: remove xhr_projectedit and xhr_projectinfo URLs Alex DAMIAN
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch prevents crashes when xhr_datatypeahead is called
without proper parameters.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/views.py | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index f2626f8..678e356 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -2379,7 +2379,7 @@ if toastermain.settings.MANAGED:
 
 
             # returns layers for current project release that are not in the project set, matching the name
-            if request.GET['type'] == "layers":
+            if request.GET.get('type', None) == "layers":
                 # all layers for the current project
                 queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('search',''))
 
@@ -2394,9 +2394,9 @@ if toastermain.settings.MANAGED:
 
 
             # returns layer dependencies for a layer, excluding current project layers
-            if request.GET['type'] == "layerdeps":
+            if request.GET.get('type', None) == "layerdeps":
                 queryset = prj.compatible_layerversions().exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()]).filter(
-                    layer__name__in = [ x.depends_on.layer.name for x in LayerVersionDependency.objects.filter(layer_version_id = request.GET['search'])])
+                    layer__name__in = [ x.depends_on.layer.name for x in LayerVersionDependency.objects.filter(layer_version_id = request.GET.get('search', None))])
 
                 final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset])
 
@@ -2405,11 +2405,11 @@ if toastermain.settings.MANAGED:
 
 
             # returns layer versions that would be deleted on the new release__pk
-            if request.GET['type'] == "versionlayers":
+            if request.GET.get('type', None) == "versionlayers":
 
                 retval = []
                 for i in prj.projectlayer_set.all():
-                    lv = prj.compatible_layerversions(release = Release.objects.get(pk=request.GET['search'])).filter(layer__name = i.layercommit.layer.name)
+                    lv = prj.compatible_layerversions(release = Release.objects.get(pk=request.GET.get('search', None))).filter(layer__name = i.layercommit.layer.name)
                     # there is no layer_version with the new release id, and the same name
                     if lv.count() < 1:
                         retval.append(i)
@@ -2420,12 +2420,12 @@ if toastermain.settings.MANAGED:
 
 
             # returns layer versions that provide the named targets
-            if request.GET['type'] == "layers4target":
+            if request.GET.get('type', None) == "layers4target":
                 # we return data only if the recipe can't be provided by the current project layer set
-                if reduce(lambda x, y: x + y, [x.recipe_layer_version.filter(name=request.GET['search']).count() for x in prj.projectlayer_equivalent_set()], 0):
+                if reduce(lambda x, y: x + y, [x.recipe_layer_version.filter(name=request.GET.get('search', None)).count() for x in prj.projectlayer_equivalent_set()], 0):
                     final_list = []
                 else:
-                    queryset_all = prj.compatible_layerversions().filter(recipe_layer_version__name = request.GET['search'])
+                    queryset_all = prj.compatible_layerversions().filter(recipe_layer_version__name = request.GET.get('search', None))
 
                     # exclude layers in the project
                     queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])
@@ -2436,7 +2436,7 @@ if toastermain.settings.MANAGED:
                 return HttpResponse(jsonfilter( { "error":"ok",  "list" : map( _lv_to_dict, final_list) }), content_type = "application/json")
 
             # returns targets provided by current project layers
-            if request.GET['type'] == "targets":
+            if request.GET.get('type', None) == "targets":
                 search_token = request.GET.get('search','')
                 queryset_all = Recipe.objects.filter(layer_version__layer__name__in = [x.layercommit.layer.name for x in prj.projectlayer_set.all().select_related("layercommit__layer")]).filter(Q(name__icontains=search_token) | Q(layer_version__layer__name__icontains=search_token))
 
@@ -2463,7 +2463,7 @@ if toastermain.settings.MANAGED:
                     }), content_type = "application/json")
 
             # returns machines provided by the current project layers
-            if request.GET['type'] == "machines":
+            if request.GET.get('type', None) == "machines":
                 queryset_all = Machine.objects.all()
                 if 'project_id' in request.session:
                     queryset_all = queryset_all.filter(layer_version__in =  prj.projectlayer_equivalent_set()).order_by("name")
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 05/19] toastergui: remove xhr_projectedit and xhr_projectinfo URLs
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (3 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 04/19] toastergui: use defaults on xhr_datatypeahead parameters Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 06/19] toaster: toaster table add raw data Alex DAMIAN
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch removes the xhr_projectedit and xhr_projectinfo URLs
in favour of REST calls to the Project page.

The project page takes now the POST requests to modify project
settings. All usages of removed URLs are now changed to point to the
project page, using the json format.

The interface call specs have not modified.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/contrib/tts/urllist.py             |   3 -
 lib/toaster/toastergui/static/js/base.js       |   3 +-
 lib/toaster/toastergui/static/js/libtoaster.js |   8 +-
 lib/toaster/toastergui/static/js/projectapp.js |  73 +++++++++++++---
 lib/toaster/toastergui/templates/base.html     |   3 -
 lib/toaster/toastergui/templates/project.html  |   2 +-
 lib/toaster/toastergui/tests.py                |   1 -
 lib/toaster/toastergui/urls.py                 |   2 -
 lib/toaster/toastergui/views.py                | 110 +++++++++----------------
 9 files changed, 108 insertions(+), 97 deletions(-)

diff --git a/lib/toaster/contrib/tts/urllist.py b/lib/toaster/contrib/tts/urllist.py
index a7d6d6e..433ac9f 100644
--- a/lib/toaster/contrib/tts/urllist.py
+++ b/lib/toaster/contrib/tts/urllist.py
@@ -40,10 +40,7 @@ URLS = [
 'toastergui/project/1/importlayer',
 'toastergui/project/1/targets/',
 'toastergui/project/1/machines/',
-'toastergui/xhr_build/',
 'toastergui/xhr_projectbuild/1/',
-'toastergui/xhr_projectinfo/',
-'toastergui/xhr_projectedit/1',
 'toastergui/xhr_configvaredit/1',
 'toastergui/xhr_datatypeahead/1',
 'toastergui/xhr_importlayer/',
diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js
index 9424b6c..9c8d01e 100644
--- a/lib/toaster/toastergui/static/js/base.js
+++ b/lib/toaster/toastergui/static/js/base.js
@@ -29,7 +29,7 @@ function basePageInit (ctx) {
     if (libtoaster.ctx.projectId == undefined)
       return;
 
-    libtoaster.getProjectInfo(ctx.projectInfoUrl, libtoaster.ctx.projectId,
+    libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
       function(data){
         if (data.machine.name == undefined || data.layers.length == 0) {
           /* we can't build anything with out a machine and some layers */
@@ -102,7 +102,6 @@ function basePageInit (ctx) {
       /* TBD: do we override even if we already have a context project ?? */
       /* TODO: replace global library context with references to the "selected" project */
       libtoaster.ctx.projectPageUrl = selectedProject.projectPageUrl;
-      libtoaster.ctx.xhrProjectEditUrl = selectedProject.xhrProjectEditUrl;
       libtoaster.ctx.projectName = selectedProject.name;
       libtoaster.ctx.projectId = selectedProject.id;
 
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 72fb0a93..8791029 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -120,11 +120,11 @@ var libtoaster = (function (){
   }
 
   /* Get a project's configuration info */
-  function _getProjectInfo(url, projectId, onsuccess, onfail){
+  function _getProjectInfo(url, onsuccess, onfail){
     $.ajax({
-        type: "POST",
+        type: "GET",
+        data : { format: "json" },
         url: url,
-        data: { project_id : projectId },
         headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
         success: function (_data) {
           if (_data.error !== "ok") {
@@ -150,7 +150,7 @@ var libtoaster = (function (){
   function _editCurrentProject(data, onSuccess, onFail){
     $.ajax({
         type: "POST",
-        url: libtoaster.ctx.xhrProjectEditUrl,
+        url: libtoaster.ctx.projectPageUrl + "?format=json",
         data: data,
         headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
         success: function (data) {
diff --git a/lib/toaster/toastergui/static/js/projectapp.js b/lib/toaster/toastergui/static/js/projectapp.js
index a3309c7..36c942f 100644
--- a/lib/toaster/toastergui/static/js/projectapp.js
+++ b/lib/toaster/toastergui/static/js/projectapp.js
@@ -156,6 +156,62 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
      * Retrieves text suggestions for text-edit drop down autocomplete boxes
      */
 
+    $scope.getLayersAutocompleteSuggestions = function(currentValue) {
+        var deffered = $q.defer();
+
+        $http({method:"GET", url: $scope.urls.layers, params : { search: currentValue, format: "json" }})
+            .success(function (_data) {
+                if (_data.error != "ok") {
+                    console.warn("error on data", _data.error);
+                    deffered.reject(_data.error);
+                }
+                deffered.resolve(_data.rows);
+            });
+
+        return deffered.promise;
+    }
+
+    $scope.filterProjectLayerIds = function () {
+        return $scope.layers.map(function (e) { return e.id; });
+    }
+
+    $scope.getMachinesAutocompleteSuggestions = function(currentValue) {
+        var deffered = $q.defer();
+
+        $http({method:"GET", url: $scope.urls.machines, params : { search: currentValue, format: "json" }})
+            .success(function (_data) {
+                if (_data.error != "ok") {
+                    console.warn("error on data", _data.error);
+                    deffered.reject(_data.error);
+                }
+                deffered.resolve(_data.rows);
+            });
+
+        return deffered.promise;
+    }
+
+    $scope.getRecipesAutocompleteSuggestions = function(currentValue) {
+        var deffered = $q.defer();
+
+        $http({method:"GET", url: $scope.urls.targets, params : { search: currentValue, format: "json" }})
+            .success(function (_data) {
+                if (_data.error != "ok") {
+                    console.warn("error on data", _data.error);
+                    deffered.reject(_data.error);
+                }
+                deffered.resolve(_data.rows);
+            });
+        return deffered.promise;
+    }
+
+    $scope.values = function() {
+        var deffered = $q.defer();
+
+        deffered.resolve(["mama", "tata"]);
+
+        return deffered.promise;
+    };
+
     $scope.getAutocompleteSuggestions = function(type, currentValue) {
         var deffered = $q.defer();
 
@@ -421,7 +477,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
 
 
     $scope.onLayerSelect = function (item) {
-        $scope.layerAddId = item.id;
+        $scope.layerToAdd = item;
     };
 
     $scope.machineSelect = function (machineName) {
@@ -443,14 +499,9 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
     };
 
 
-    $scope.layerAddById = function (id) {
-        $scope.layerAddId = id;
-        $scope.layerAdd();
-    };
-
     $scope.layerAdd = function() {
 
-        $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "layerdeps", value: $scope.layerAddId }})
+        $http({method:"GET", url: $scope.layerToAdd.layerdict.layerdetailurl, params : {}})
         .success(function (_data) {
              if (_data.error != "ok") {
                  console.warn(_data.error);
@@ -493,7 +544,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
                      });
 
                      modalInstance.result.then(function (selectedArray) {
-                         selectedArray.push($scope.layerAddId);
+                         selectedArray.push($scope.layerToAdd.layerversion.id);
                          console.warn("TRC6: selected", selectedArray);
 
                          $scope._makeXHRCall({
@@ -512,7 +563,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
                          $scope._makeXHRCall({
                              method: "POST", url: $scope.urls.xhr_edit,
                              data: {
-                                 layerAdd:  $scope.layerAddId,
+                                 layerAdd:  $scope.layerToAdd.layerversion.id,
                              }
                          }).then(function () {
                              $scope.layerAddName = undefined;
@@ -768,10 +819,8 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
 
 
         _cmdExecuteWithParam("/layeradd=", function (layer) {
-            angular.forEach(layer.split(","), function (l) {
-                $scope.layerAddId = l;
+                $scope.layerToAdd = layer;
                 $scope.layerAdd();
-            });
         });
     };
 
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index e10dc11..5d51bc3 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -34,13 +34,11 @@
     projectsUrl : "{% url 'all-projects' %}",
     {% if project.id %}
       xhrProjectDataTypeaheadUrl : "{% url 'xhr_datatypeahead' project.id %}",
-      xhrProjectEditUrl : "{% url 'xhr_projectedit' project.id %}",
       projectPageUrl : "{% url 'project' project.id %}",
       projectName : "{{project.name}}",
       projectId : {{project.id}},
     {% else %}
       xhrProjectDataTypeaheadUrl : undefined,
-      xhrProjectEditUrl : undefined,
       projectPageUrl : undefined,
       projectName : undefined,
       projectId : undefined,
@@ -53,7 +51,6 @@
   $(document).ready(function () {
     /* Vars needed for base.js */
     var ctx = {};
-    ctx.projectInfoUrl = "{% url 'xhr_projectinfo' %}";
     ctx.numProjects = {{projects|length}};
     ctx.currentUrl = "{{request.path|escapejs}}";
 
diff --git a/lib/toaster/toastergui/templates/project.html b/lib/toaster/toastergui/templates/project.html
index 933da4f..e598631 100644
--- a/lib/toaster/toastergui/templates/project.html
+++ b/lib/toaster/toastergui/templates/project.html
@@ -430,7 +430,7 @@ angular.element(document).ready(function() {
   scope = angular.element("#main").scope();
   scope.urls = {};
   scope.urls.xhr_build = "{% url 'xhr_projectbuild' project.id %}";
-  scope.urls.xhr_edit = "{% url 'xhr_projectedit' project.id %}";
+  scope.urls.xhr_edit = "{% url 'project' project.id %}?format=json";
   scope.urls.xhr_datatypeahead = "{% url 'xhr_datatypeahead' project.id %}";
   scope.urls.layers = "{% url 'all-layers' project.id %}";
   scope.urls.targets = "{% url 'all-targets' project.id %}";
diff --git a/lib/toaster/toastergui/tests.py b/lib/toaster/toastergui/tests.py
index 0f10020..c92c5fe 100644
--- a/lib/toaster/toastergui/tests.py
+++ b/lib/toaster/toastergui/tests.py
@@ -40,7 +40,6 @@ class AllProjectsViewTestCase(ProvisionedProjectTestCase):
         self.assertTrue("id" in data["list"][0])
         self.assertTrue("xhrProjectDataTypeaheadUrl" in data["list"][0])
         self.assertTrue("projectPageUrl" in data["list"][0])
-        self.assertTrue("xhrProjectEditUrl" in data["list"][0])
         self.assertTrue("projectBuildUrl" in data["list"][0])
 
 class ProvisionedLayersProjectTestCase(ProvisionedProjectTestCase):
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index e10e0bb..d527be0 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -122,8 +122,6 @@ urlpatterns = patterns('toastergui.views',
             name=tables.LayerMachinesTable.__name__.lower()),
 
         url(r'^xhr_projectbuild/(?P<pid>\d+)$', 'xhr_projectbuild', name='xhr_projectbuild'),
-        url(r'^xhr_projectinfo/$', 'xhr_projectinfo', name='xhr_projectinfo'),
-        url(r'^xhr_projectedit/(?P<pid>\d+)$', 'xhr_projectedit', name='xhr_projectedit'),
         url(r'^xhr_configvaredit/(?P<pid>\d+)$', 'xhr_configvaredit', name='xhr_configvaredit'),
 
         url(r'^xhr_datatypeahead/(?P<pid>\d+)$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 678e356..91c4fa2 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -2210,6 +2210,45 @@ if toastermain.settings.MANAGED:
         except User.DoesNotExist:
             puser = None
 
+        # execute POST requests
+        if request.method == "POST":
+            # add layers
+            if 'layerAdd' in request.POST:
+                for lc in Layer_Version.objects.filter(pk__in=request.POST['layerAdd'].split(",")):
+                    ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
+
+            # remove layers
+            if 'layerDel' in request.POST:
+                for t in request.POST['layerDel'].strip().split(" "):
+                    pt = ProjectLayer.objects.filter(project = prj, layercommit_id = int(t)).delete()
+
+            if 'projectName' in request.POST:
+                prj.name = request.POST['projectName']
+                prj.save();
+
+            if 'projectVersion' in request.POST:
+                prj.release = Release.objects.get(pk = request.POST['projectVersion'])
+                # we need to change the bitbake version
+                prj.bitbake_version = prj.release.bitbake_version
+                prj.save()
+                # we need to change the layers
+                for i in prj.projectlayer_set.all():
+                    # find and add a similarly-named layer on the new branch
+                    try:
+                        lv = prj.compatible_layerversions(layer_name = i.layercommit.layer.name)[0]
+                        ProjectLayer.objects.get_or_create(project = prj, layercommit = lv)
+                    except IndexError:
+                        pass
+                    finally:
+                        # get rid of the old entry
+                        i.delete()
+
+            if 'machineName' in request.POST:
+                machinevar = prj.projectvariable_set.get(name="MACHINE")
+                machinevar.value=request.POST['machineName']
+                machinevar.save()
+
+
         # we use implicit knowledge of the current user's project to filter layer information, e.g.
         pid = prj.id
 
@@ -2241,10 +2280,12 @@ if toastermain.settings.MANAGED:
                         "branch" : { "name" : x.layercommit.get_vcs_reference(), "layersource" : x.layercommit.up_branch.layer_source.name if x.layercommit.up_branch != None else None}},
                     prj.projectlayer_set.all().order_by("id")),
             "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
+            "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()),
             "freqtargets": freqtargets[:5],
             "releases": map(lambda x: {"id": x.pk, "name": x.name, "description":x.description}, Release.objects.all()),
             "project_html": 1,
         }
+
         try:
             context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value}
         except ProjectVariable.DoesNotExist:
@@ -2300,66 +2341,6 @@ if toastermain.settings.MANAGED:
         except Exception as e:
             return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
 
-    # This is a wraper for xhr_projectedit which allows for a project id
-    # which only becomes known client side
-    def xhr_projectinfo(request):
-        if request.POST.has_key("project_id") == False:
-            raise BadParameterException("invalid project id")
-
-        return xhr_projectedit(request, request.POST['project_id'])
-
-    def xhr_projectedit(request, pid):
-        try:
-            prj = Project.objects.get(id = pid)
-            # add layers
-            if 'layerAdd' in request.POST:
-                for lc in Layer_Version.objects.filter(pk__in=request.POST['layerAdd'].split(",")):
-                    ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
-
-            # remove layers
-            if 'layerDel' in request.POST:
-                for t in request.POST['layerDel'].strip().split(" "):
-                    pt = ProjectLayer.objects.filter(project = prj, layercommit_id = int(t)).delete()
-
-            if 'projectName' in request.POST:
-                prj.name = request.POST['projectName']
-                prj.save();
-
-            if 'projectVersion' in request.POST:
-                prj.release = Release.objects.get(pk = request.POST['projectVersion'])
-                # we need to change the bitbake version
-                prj.bitbake_version = prj.release.bitbake_version
-                prj.save()
-                # we need to change the layers
-                for i in prj.projectlayer_set.all():
-                    # find and add a similarly-named layer on the new branch
-                    try:
-                        lv = prj.compatible_layerversions(layer_name = i.layercommit.layer.name)[0]
-                        ProjectLayer.objects.get_or_create(project = prj, layercommit = lv)
-                    except IndexError:
-                        pass
-                    finally:
-                        # get rid of the old entry
-                        i.delete()
-
-            if 'machineName' in request.POST:
-                machinevar = prj.projectvariable_set.get(name="MACHINE")
-                machinevar.value=request.POST['machineName']
-                machinevar.save()
-
-            # return all project settings
-            return HttpResponse(jsonfilter( {
-                "error": "ok",
-                "layers" :  map(lambda x: {"id": x.layercommit.pk, "orderid" : x.pk, "name" : x.layercommit.layer.name, "giturl" : x.layercommit.layer.vcs_url, "url": x.layercommit.layer.layer_index_url, "layerdetailurl": reverse("layerdetails", args=(prj.id, x.layercommit.pk,)), "branch" : { "name" : x.layercommit.get_vcs_reference(), "layersource" : x.layercommit.up_branch.layer_source.name}}, prj.projectlayer_set.all().select_related("layer").order_by("id")),
-                "builds" : _project_recent_build_list(prj),
-                "variables": map(lambda x: (x.name, x.value), prj.projectvariable_set.all()),
-                "machine": {"name": prj.projectvariable_set.get(name="MACHINE").value},
-                "prj": {"name": prj.name, "release": { "id": prj.release.pk, "name": prj.release.name, "desc": prj.release.description}},
-                }), content_type = "application/json")
-
-        except Exception as e:
-            return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
-
 
     from django.views.decorators.csrf import csrf_exempt
     @csrf_exempt
@@ -2929,7 +2910,6 @@ if toastermain.settings.MANAGED:
             p.id = p.pk
             p.xhrProjectDataTypeaheadUrl = reverse('xhr_datatypeahead', args=(p.id,))
             p.projectPageUrl = reverse('project', args=(p.id,))
-            p.xhrProjectEditUrl = reverse('xhr_projectedit', args=(p.id,))
             p.projectBuildUrl = reverse('xhr_projectbuild', args=(p.id,))
 
         # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
@@ -3234,14 +3214,6 @@ else:
         return {}
 
     @_template_renderer('landing_not_managed.html')
-    def xhr_projectinfo(request):
-        return {}
-
-    @_template_renderer('landing_not_managed.html')
-    def xhr_projectedit(request, pid):
-        return {}
-
-    @_template_renderer('landing_not_managed.html')
     def xhr_datatypeahead(request):
         return {}
 
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 06/19] toaster: toaster table add raw data
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (4 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 05/19] toastergui: remove xhr_projectedit and xhr_projectinfo URLs Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 07/19] toaster: ToasterTables add computational fields Alex DAMIAN
                   ` (12 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We add in a JSON response both the raw data and the rendered
version for display. The rendered fields start with "static:"
to mark a different "namespace".

The toaster.js is updated to always display the "static:" version
of a field, if it exists (and ignore the raw data unless the
static rendering is missing).

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/static/js/table.js | 14 +++++++++++++-
 lib/toaster/toastergui/views.py           | 31 ++++++++++++++++++++++++++++++-
 lib/toaster/toastergui/widgets.py         | 16 +++++++++++-----
 3 files changed, 54 insertions(+), 7 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/table.js b/lib/toaster/toastergui/static/js/table.js
index 7588a4a..80e9ec2 100644
--- a/lib/toaster/toastergui/static/js/table.js
+++ b/lib/toaster/toastergui/static/js/table.js
@@ -113,8 +113,20 @@ function tableInit(ctx){
     for (var i in tableData.rows){
       var row = $("<tr></tr>");
       for (var key_j in tableData.rows[i]){
+        /* if we have a static: version of a key, prefer the static: version for rendering */
+        var orig_key_j = key_j;
+
+        if (key_j.indexOf("static:") === 0) {
+          if (key_j.substr("static:".length) in tableData.rows[i]) {
+            continue;
+          }
+          orig_key_j = key_j.substr("static:".length)
+        } else if (("static:" + key_j) in tableData.rows[i]) {
+          key_j = "static:" + key_j;
+        }
+
         var td = $("<td></td>");
-        td.prop("class", key_j);
+        td.prop("class", orig_key_j);
         if (tableData.rows[i][key_j]){
           td.html(tableData.rows[i][key_j]);
         }
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 91c4fa2..280159a 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -87,6 +87,35 @@ def _project_recent_build_list(prj):
             list(prj.buildrequest_set.filter(state__in=[BuildRequest.REQ_COMPLETED, BuildRequest.REQ_FAILED]).order_by("-pk")[:3]))
 
 
+
+def objtojson(obj):
+    from django.db.models.query import QuerySet
+    from django.db.models import Model, IntegerField
+    from django.db.models.fields.related import ForeignKey
+
+    if isinstance(obj, datetime):
+        return obj.isoformat()
+    elif isinstance(obj, timedelta):
+        return obj.total_seconds()
+    elif isinstance(obj, QuerySet) or isinstance(obj, set):
+        return list(obj)
+    elif type(obj).__name__ == "RelatedManager":
+        return [x.pk for x in obj.all()]
+    elif hasattr( obj, '__dict__') and isinstance(obj, Model):
+        d = obj.__dict__
+        nd = dict(d)
+        for di in d.keys():
+            if di.startswith("_"):
+                del nd[di]
+            elif isinstance(d[di], Model):
+                nd[di] = d[di].pk
+            elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
+                nd[di] = getattr(obj, "get_%s_display" % di)()
+        return nd
+    else:
+        raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
+
+
 def _template_renderer(template):
     def func_wrapper(view):
         def returned_wrapper(request, *args, **kwargs):
@@ -127,7 +156,7 @@ def _template_renderer(template):
                     else:
                         raise TypeError("Unserializable object %s of type %s" % ( obj, type(obj)))
 
-                return HttpResponse(jsonfilter(context, default=_objtojson ),
+                return HttpResponse(jsonfilter(context, default=objtojson ),
                             content_type = "application/json; charset=utf-8")
             else:
                 return render(request, template, context)
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 4347a3f..82b7514 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -37,6 +37,7 @@ import json
 import collections
 import operator
 
+from toastergui.views import objtojson
 
 class ToasterTable(TemplateView):
     def __init__(self, *args, **kwargs):
@@ -275,19 +276,25 @@ class ToasterTable(TemplateView):
 
                 for col in self.columns:
                     field = col['field_name']
+                    if not field:
+                        field = col['static_data_name']
+                    if not field:
+                        raise Exception("Must supply a field_name or static_data_name for column %s.%s" % (self.__class__.__name__,col))
                     # Check if we need to process some static data
                     if "static_data_name" in col and col['static_data_name']:
-                        required_data[col['static_data_name']] = self.render_static_data(col['static_data_template'], row)
+                        required_data["static:%s" % col['static_data_name']] = self.render_static_data(col['static_data_template'], row)
 
                         # Overwrite the field_name with static_data_name
                         # so that this can be used as the html class name
 
                         col['field_name'] = col['static_data_name']
-                    else:
+
+                    if True:        # we add the raw model data at all times
                         model_data = row
                         # Traverse to any foriegn key in the object hierachy
                         for subfield in field.split("__"):
-                            model_data = getattr(model_data, subfield)
+                            if hasattr(model_data, subfield):
+                                model_data = getattr(model_data, subfield)
                         # The field could be a function on the model so check
                         # If it is then call it
                         if isinstance(model_data, types.MethodType):
@@ -299,8 +306,7 @@ class ToasterTable(TemplateView):
 
         except FieldError:
             print "Error: Requested field does not exist"
-
-        data = json.dumps(data, indent=2, cls=DjangoJSONEncoder)
+        data = json.dumps(data, indent=2, default=objtojson)
         cache.set(cache_name, data, 60*30)
 
         return data
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 07/19] toaster: ToasterTables add computational fields
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (5 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 06/19] toaster: toaster table add raw data Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 08/19] toaster: toastertables raise errors Alex DAMIAN
                   ` (11 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch adds the ability to pass a function to be computed
for generating a field value in setting up a column in
ToasterTables.

Also adding "displayable" property that can be turned False for
columns that are present in JSON data but are not part of the UI.

Add the "id" column by default for all rows.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/static/js/table.js | 13 +++++++++++++
 lib/toaster/toastergui/tables.py          |  8 ++++++--
 lib/toaster/toastergui/views.py           |  3 +++
 lib/toaster/toastergui/widgets.py         | 19 ++++++++++++++++---
 4 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/table.js b/lib/toaster/toastergui/static/js/table.js
index 80e9ec2..45c6184 100644
--- a/lib/toaster/toastergui/static/js/table.js
+++ b/lib/toaster/toastergui/static/js/table.js
@@ -110,9 +110,13 @@ function tableInit(ctx){
     setupTableChrome(tableData);
 
     /* Add table data rows */
+    var column_index;
     for (var i in tableData.rows){
+      /* only display if the column is display-able */
       var row = $("<tr></tr>");
+      column_index = -1;
       for (var key_j in tableData.rows[i]){
+
         /* if we have a static: version of a key, prefer the static: version for rendering */
         var orig_key_j = key_j;
 
@@ -125,6 +129,12 @@ function tableInit(ctx){
           key_j = "static:" + key_j;
         }
 
+        /* we skip over un-displayable column entries */
+        column_index += 1;
+        if (! tableData.columns[column_index].displayable) {
+          continue;
+        }
+
         var td = $("<td></td>");
         td.prop("class", orig_key_j);
         if (tableData.rows[i][key_j]){
@@ -206,6 +216,9 @@ function tableInit(ctx){
     /* Add table header and column toggle menu */
     for (var i in tableData.columns){
       var col = tableData.columns[i];
+      if (col.displayable === false) {
+        continue;
+      }
       var header = $("<th></th>");
       header.prop("class", col.field_name);
 
diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index b75e565..e540b91 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -79,7 +79,7 @@ class LayersTable(ToasterTable):
         self.add_column(title="Git repository URL",
                         help_text="The Git repository for the layer source code",
                         hidden=True,
-                        static_data_name="git_url",
+                        static_data_name="layer__vcs_url",
                         static_data_template=git_url_template)
 
         git_dir_template = '''
@@ -328,13 +328,17 @@ class RecipesTable(ToasterTable):
         self.add_column(title="Revision",
                         field_name="layer_version__get_vcs_reference")
 
-
         self.add_column(title="Build",
                         help_text="Add or delete recipes to and from your project",
                         hideable=False,
                         static_data_name="add-del-layers",
                         static_data_template='{% include "recipe_btn.html" %}')
 
+        self.add_column(title="Project compatible Layer ID",
+                        displayable = False,
+                        field_name = "projectcompatible_layer",
+                        computation = lambda x: (x.layer_version.get_equivalents_wpriority(Project.objects.get(pk=kwargs['pid']))[0]))
+
 class LayerRecipesTable(RecipesTable):
     """ Smaller version of the Recipes table for use in layer details """
 
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 280159a..c25c512 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -112,6 +112,9 @@ def objtojson(obj):
             elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
                 nd[di] = getattr(obj, "get_%s_display" % di)()
         return nd
+    elif isinstance( obj, type(lambda x:x)):
+        import inspect
+        return inspect.getsourcelines(obj)[0]
     else:
         raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
 
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 82b7514..407a0fb 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -54,6 +54,13 @@ class ToasterTable(TemplateView):
         self.empty_state = "Sorry - no data found"
         self.default_orderby = ""
 
+        # add the "id" column, undisplayable, by default
+        self.add_column(title="Id",
+                        displayable=False,
+                        orderable=True,
+                        field_name="id")
+
+
     def get(self, request, *args, **kwargs):
         if request.GET.get('format', None) == 'json':
 
@@ -142,6 +149,7 @@ class ToasterTable(TemplateView):
     def add_column(self, title="", help_text="",
                    orderable=False, hideable=True, hidden=False,
                    field_name="", filter_name=None, static_data_name=None,
+                   displayable=True, computation=None,
                    static_data_template=None):
         """Add a column to the table.
 
@@ -168,6 +176,8 @@ class ToasterTable(TemplateView):
                              'filter_name' : filter_name,
                              'static_data_name': static_data_name,
                              'static_data_template': static_data_template,
+                             'displayable': displayable,
+                             'computation': computation,
                             })
 
     def render_static_data(self, template, row):
@@ -289,8 +299,11 @@ class ToasterTable(TemplateView):
 
                         col['field_name'] = col['static_data_name']
 
-                    if True:        # we add the raw model data at all times
-                        model_data = row
+                    # compute the computation on the raw data if needed
+                    model_data = row
+                    if col['computation']:
+                        model_data = col['computation'](row)
+                    else:
                         # Traverse to any foriegn key in the object hierachy
                         for subfield in field.split("__"):
                             if hasattr(model_data, subfield):
@@ -300,7 +313,7 @@ class ToasterTable(TemplateView):
                         if isinstance(model_data, types.MethodType):
                           model_data = model_data()
 
-                        required_data[field] = model_data
+                    required_data[col['field_name']] = model_data
 
                 data['rows'].append(required_data)
 
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 08/19] toaster: toastertables raise errors
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (6 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 07/19] toaster: ToasterTables add computational fields Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 09/19] toaster: add class template view for single-object pages Alex DAMIAN
                   ` (10 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Modifies the widget code to raise the Exceptions to the user
instead of printing then to stdout - making the programming
errors much more visible.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/widgets.py | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 407a0fb..3d3c1d1 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -207,7 +207,8 @@ class ToasterTable(TemplateView):
         try:
             self.filter_actions[filter_action]()
         except KeyError:
-            print "Filter and Filter action pair not found"
+            # pass it to the user - programming error here
+            raise
 
     def apply_orderby(self, orderby):
         # Note that django will execute this when we try to retrieve the data
@@ -217,8 +218,7 @@ class ToasterTable(TemplateView):
         """Creates a query based on the model's search_allowed_fields"""
 
         if not hasattr(self.queryset.model, 'search_allowed_fields'):
-            print "Err Search fields aren't defined in the model"
-            return
+            raise Exception("Err Search fields aren't defined in the model")
 
         search_queries = []
         for st in search_term.split(" "):
@@ -228,9 +228,10 @@ class ToasterTable(TemplateView):
             search_queries.append(reduce(operator.or_, q_map))
 
         search_queries = reduce(operator.and_, search_queries)
-        print "applied the search to the queryset"
+
         self.queryset = self.queryset.filter(search_queries)
 
+
     def get_data(self, request, **kwargs):
         """Returns the data for the page requested with the specified
         parameters applied"""
@@ -318,7 +319,8 @@ class ToasterTable(TemplateView):
                 data['rows'].append(required_data)
 
         except FieldError:
-            print "Error: Requested field does not exist"
+            # pass  it to the user - programming-error here
+            raise
         data = json.dumps(data, indent=2, default=objtojson)
         cache.set(cache_name, data, 60*30)
 
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 09/19] toaster: add class template view for single-object pages
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (7 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 08/19] toaster: toastertables raise errors Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 10/19] toaster: eliminate duplicate querysets Alex DAMIAN
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

Adding ToasterTemplateView as prototype for all class-based
views that display single objects; equivalent to ToasterTable
for object lists.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/tables.py  | 12 ++++++++++--
 lib/toaster/toastergui/widgets.py | 26 ++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index e540b91..e03aa76 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -19,7 +19,7 @@
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-from toastergui.widgets import ToasterTable
+from toastergui.widgets import ToasterTable, ToasterTemplateView
 from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
 from django.db.models import Q, Max
 from django.conf.urls import url
@@ -142,16 +142,24 @@ class LayersTable(ToasterTable):
                         static_data_name="add-del-layers",
                         static_data_template='{% include "layer_btn.html" %}')
 
-class LayerDetails(TemplateView):
+
+
+class LayerDetails(ToasterTemplateView):
     def get_context_data(self, **kwargs):
         context = super(LayerDetails, self).get_context_data(**kwargs)
+        from toastergui.views import _lv_to_dict
 
         context['project'] = Project.objects.get(pk=kwargs['pid'])
         context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
+        context['layerdict'] = _lv_to_dict(context['project'], context['layerversion'])
+        context['layerdeps'] = {"list": [x.depends_on.get_equivalents_wpriority(context['project'])[0] for x in context['layerversion'].dependencies.all()]}
         context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
 
+        self.context_entries = ['project', 'layerversion', 'projectlayers', 'layerdict', 'layerdeps']
+
         return context
 
+
 class MachinesTable(ToasterTable):
     """Table of Machines in Toaster"""
 
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 3d3c1d1..8bc3d7f 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -325,3 +325,29 @@ class ToasterTable(TemplateView):
         cache.set(cache_name, data, 60*30)
 
         return data
+
+
+class ToasterTemplateView(TemplateView):
+    # renders a instance in a template, or returns the context as json
+    # the class-equivalent of the _template_renderer decorator for views
+
+
+    def get(self, *args, **kwargs):
+        if self.request.GET.get('format', None) == 'json':
+            from django.core.urlresolvers import reverse
+            from django.shortcuts import HttpResponse
+            from views import objtojson
+            from toastergui.templatetags.projecttags import json as jsonfilter
+
+            context = self.get_context_data(**kwargs)
+
+            for x in context.keys():
+                if x not in self.context_entries:
+                    del context[x]
+
+            context["error"] = "ok"
+
+            return HttpResponse(jsonfilter(context,  default=objtojson ),
+                            content_type = "application/json; charset=utf-8")
+
+        return super(ToasterTemplateView, self).get(*args, **kwargs)
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 10/19] toaster: eliminate duplicate querysets
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (8 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 09/19] toaster: add class template view for single-object pages Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 11/19] toastergui: remove xhr_datatypeahead and xhr_XXXbuild Alex DAMIAN
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

The Layer_Version.get_equivalents_wpriority performs the same
function as Project.compatible_layerversions, but in memory and
with worse performance.

Replace the code in get_equivalents_wpriority with a call to
the project compatible_layerversions, which also returns a queryset
instead of a list (can be used to further enhance queries)

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/orm/models.py | 41 +++--------------------------------------
 1 file changed, 3 insertions(+), 38 deletions(-)

diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index 198c6f5..8e73ee1 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -20,7 +20,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 from django.db import models
-from django.db.models import F, Q
+from django.db.models import F, Q, Avg
 from django.utils import timezone
 
 
@@ -180,7 +180,7 @@ class Project(models.Model):
             queryset = queryset.filter(layer__name = layer_name)
 
         # order by layer version priority
-        queryset = queryset.filter(layer_source__releaselayersourcepriority__release = release).order_by("-layer_source__releaselayersourcepriority__priority")
+        queryset = queryset.filter(Q(layer_source=None) | Q(layer_source__releaselayersourcepriority__release = release)).select_related('layer_source', 'layer', 'up_branch').annotate(prio=Avg("layer_source__releaselayersourcepriority__priority")).order_by("-prio")
 
         return queryset
 
@@ -1062,42 +1062,7 @@ class Layer_Version(models.Model):
         return self._handle_url_path(self.layer.vcs_web_tree_base_url, '')
 
     def get_equivalents_wpriority(self, project):
-        """ Returns an ordered layerversion list that satisfies a LayerVersionDependency using the layer name and the current Project Releases' LayerSource priority """
-
-        # layers created for this project, or coming from a build inthe project
-        query = Q(project = project) | Q(build__project = project)
-        if self.up_branch is not None:
-            # the same up_branch name
-            query |= Q(up_branch__name=self.up_branch.name)
-        else:
-            # or we have a layer in the project that's similar to mine (See the layer.name constraint below)
-            query |= Q(projectlayer__project=project)
-
-        candidate_layer_versions = list(Layer_Version.objects.filter(layer__name = self.layer.name).filter(query).select_related('layer_source', 'layer', 'up_branch').order_by("-id"))
-
-        # optimization - if we have only one, we don't need no stinking sort
-        if len(candidate_layer_versions) == 1:
-            return candidate_layer_versions
-
-#        raise Exception(candidate_layer_versions)
-
-        release_priorities = {}
-
-        for ls_id, prio in map(lambda x: (x.layer_source_id, x.priority), project.release.releaselayersourcepriority_set.all().order_by("-priority")):
-            release_priorities[ls_id] = prio
-
-        def _get_ls_priority(ls):
-            # if there is no layer source, we have minus infinite priority, as we don't want this layer selected
-            if ls == None:
-                return -10000
-            try:
-                return release_priorities[ls.id]
-            except IndexError:
-                raise Exception("Unknown %d %s" % (ls.id, release_priorities))
-
-        return sorted( candidate_layer_versions ,
-                key = lambda x: _get_ls_priority(x.layer_source),
-                reverse = True)
+        return project.compatible_layerversions(layer_name = self.layer.name)
 
     def get_vcs_reference(self):
         if self.commit is not None and len(self.commit) > 0:
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 11/19] toastergui: remove xhr_datatypeahead and xhr_XXXbuild
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (9 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 10/19] toaster: eliminate duplicate querysets Alex DAMIAN
@ 2015-06-10 14:38 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 12/19] toastergui: remove xhr_datatypeahaed layerdeps call Alex DAMIAN
                   ` (7 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:38 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

We remove the endpoints for XHR on the toastergui application.

The endpoints are now replaced with calls to the respective
REST endpoints (i.e. projectlayers, projecttargets, projectmachines).

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/contrib/tts/urllist.py                 |   2 -
 lib/toaster/toastergui/static/js/base.js           |   6 +-
 lib/toaster/toastergui/static/js/importlayer.js    |   6 +-
 lib/toaster/toastergui/static/js/layerdetails.js   |   4 +-
 lib/toaster/toastergui/static/js/libtoaster.js     |   8 +-
 lib/toaster/toastergui/templates/base.html         |   2 -
 lib/toaster/toastergui/templates/layerdetails.html |   4 +-
 .../toastergui/templates/managed_mrb_section.html  |   2 +-
 lib/toaster/toastergui/templates/project.html      |  52 ++++--
 lib/toaster/toastergui/templates/runagain.html     |   2 +-
 lib/toaster/toastergui/templatetags/projecttags.py |   2 +-
 lib/toaster/toastergui/tests.py                    |   6 +-
 lib/toaster/toastergui/urls.py                     |   8 +-
 lib/toaster/toastergui/views.py                    | 208 ++++++---------------
 lib/toaster/toastergui/widgets.py                  |   6 +-
 lib/toaster/toastermain/settings.py                |   2 +-
 16 files changed, 119 insertions(+), 201 deletions(-)

diff --git a/lib/toaster/contrib/tts/urllist.py b/lib/toaster/contrib/tts/urllist.py
index 433ac9f..0226334 100644
--- a/lib/toaster/contrib/tts/urllist.py
+++ b/lib/toaster/contrib/tts/urllist.py
@@ -40,9 +40,7 @@ URLS = [
 'toastergui/project/1/importlayer',
 'toastergui/project/1/targets/',
 'toastergui/project/1/machines/',
-'toastergui/xhr_projectbuild/1/',
 'toastergui/xhr_configvaredit/1',
-'toastergui/xhr_datatypeahead/1',
 'toastergui/xhr_importlayer/',
 'toastergui/xhr_updatelayer/',
 'toastergui/project/1/buildrequest/1',
diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js
index 9c8d01e..747442c 100644
--- a/lib/toaster/toastergui/static/js/base.js
+++ b/lib/toaster/toastergui/static/js/base.js
@@ -89,7 +89,7 @@ function basePageInit (ctx) {
       if (!selectedTarget)
         selectedTarget = { name: newBuildTargetInput.val() };
       /* fire and forget */
-      libtoaster.startABuild(ctx.projectBuildUrl, libtoaster.ctx.projectId, selectedTarget.name, null, null);
+      libtoaster.startABuild(ctx.projectBuildsUrl, libtoaster.ctx.projectId, selectedTarget.name, null, null);
       window.location.replace(libtoaster.ctx.projectPageUrl);
     });
 
@@ -105,13 +105,13 @@ function basePageInit (ctx) {
       libtoaster.ctx.projectName = selectedProject.name;
       libtoaster.ctx.projectId = selectedProject.id;
 
-      ctx.projectBuildUrl = selectedProject.projectBuildUrl;
+      ctx.projectBuildsUrl = selectedProject.projectBuildsUrl;
 
       /* we can create a target typeahead only after we have a project selected */
       newBuildTargetInput.prop("disabled", false);
       newBuildTargetBuildBtn.prop("disabled", false);
 
-      libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.xhrProjectDataTypeaheadUrl, { type : "targets" }, function(item){
+      libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function(item){
         /* successfully selected a target */
         selectedTarget = item;
 	    });
diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index beb2ede..e1fc5c5 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -18,7 +18,7 @@ function importLayerPageInit (ctx) {
 
   $("#new-project-button").hide();
 
-  libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" }, function(item){
+  libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.projectLayersUrl, { include_added: "true" }, function(item){
     currentLayerDepSelection = item;
 
     layerDepBtn.removeAttr("disabled");
@@ -28,7 +28,7 @@ function importLayerPageInit (ctx) {
   /* We automatically add "openembedded-core" layer for convenience as a
    * dependency as pretty much all layers depend on this one
    */
-  $.getJSON(libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" , value: "openembedded-core" }, function(layer) {
+  $.getJSON(libtoaster.ctx.projectLayersUrl, { include_added: "true" , search: "openembedded-core" }, function(layer) {
     if (layer.list.length == 1) {
       currentLayerDepSelection = layer.list[0];
       layerDepBtn.click();
@@ -211,7 +211,7 @@ function importLayerPageInit (ctx) {
       var name = $(this).val();
 
       /* Check if the layer name exists */
-      $.getJSON(libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" , value: name }, function(layer) {
+      $.getJSON(libtoaster.ctx.projectLayersUrl, { include_added: "true" , search: name }, function(layer) {
       if (layer.list.length > 0) {
         for (var i in layer.list){
           if (layer.list[i].name == name) {
diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
index 8e14b8f..ab78182 100644
--- a/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -9,7 +9,7 @@ function layerDetailsPageInit (ctx) {
   var addRmLayerBtn = $("#add-remove-layer-btn");
 
   /* setup the dependencies typeahead */
-  libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.xhrProjectDataTypeaheadUrl, { type : "layers", project_id: libtoaster.ctx.projectId, include_added: "true" }, function(item){
+  libtoaster.makeTypeahead(layerDepInput, libtoaster.ctx.projectLayersUrl, { include_added: "true" }, function(item){
     currentLayerDepSelection = item;
 
     layerDepBtn.removeAttr("disabled");
@@ -170,7 +170,7 @@ function layerDetailsPageInit (ctx) {
     $(".build-target-btn").click(function(){
       /* fire a build */
       var target = $(this).data('target-name');
-      libtoaster.startABuild(ctx.projectBuildUrl, libtoaster.ctx.projectId, target, null, null);
+      libtoaster.startABuild(ctx.projectBuildsUrl, libtoaster.ctx.projectId, target, null, null);
       window.location.replace(libtoaster.ctx.projectPageUrl);
     });
   });
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 8791029..2a9a790 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -93,7 +93,7 @@ var libtoaster = (function (){
   }
 
   /* cancelABuild:
-   * url: xhr_projectbuild
+   * url: projectbuilds
    * builds_ids: space separated list of build request ids
    * onsuccess: callback for successful execution
    * onfail: callback for failed execution
@@ -172,15 +172,15 @@ var libtoaster = (function (){
 
   function _getLayerDepsForProject(projectId, layerId, onSuccess, onFail){
     /* Check for dependencies not in the current project */
-    $.getJSON(libtoaster.ctx.xhrProjectDataTypeaheadUrl,
-      { type: 'layerdeps', 'value': layerId , project_id: projectId },
+    $.getJSON(libtoaster.ctx.projectLayersUrl,
+      { format: 'json', search: layerId },
       function(data) {
         if (data.error != "ok") {
           console.log(data.error);
           if (onFail !== undefined)
             onFail(data);
         } else {
-          onSuccess(data);
+          onSuccess(data.layerdeps);
         }
       }, function() {
         console.log("E: Failed to make request");
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index 5d51bc3..7fee26e 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -33,12 +33,10 @@
     htmlUrl : "{% static 'html/' %}",
     projectsUrl : "{% url 'all-projects' %}",
     {% if project.id %}
-      xhrProjectDataTypeaheadUrl : "{% url 'xhr_datatypeahead' project.id %}",
       projectPageUrl : "{% url 'project' project.id %}",
       projectName : "{{project.name}}",
       projectId : {{project.id}},
     {% else %}
-      xhrProjectDataTypeaheadUrl : undefined,
       projectPageUrl : undefined,
       projectName : undefined,
       projectId : undefined,
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index c27d259..ef1795c 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -3,7 +3,7 @@
 {% load humanize %}
 {% load static %}
 {% block localbreadcrumb %}
-<li><a href="{% url 'all-layers' project.id %}">All compatible layers</a></li>
+<li><a href="{% url 'projectlayers' project.id %}">All compatible layers</a></li>
 <li>
   {{layerversion.layer.name}} ({{layerversion.get_vcs_reference|truncatechars:13}})
 </li>
@@ -33,7 +33,7 @@
 
   $(document).ready(function (){
     var ctx = {
-      projectBuildUrl : "{% url 'xhr_projectbuild' project.id %}",
+      projectBuildsUrl : "{% url 'projectbuilds' project.id %}",
       layerDetailsUrl : "{% url 'base_layerdetails' project.id %}",
       xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}",
       layerVersion : {
diff --git a/lib/toaster/toastergui/templates/managed_mrb_section.html b/lib/toaster/toastergui/templates/managed_mrb_section.html
index c93c2af..47e64ea 100644
--- a/lib/toaster/toastergui/templates/managed_mrb_section.html
+++ b/lib/toaster/toastergui/templates/managed_mrb_section.html
@@ -129,7 +129,7 @@
             <div class="span4 lead">Build queued
               <i title="This build will start as soon as a build server is available" class="icon-question-sign get-help get-help-blue heading-help" data-toggle="tooltip"></i>
             </div>
-            <button class="btn btn-info pull-right cancel-build-btn" data-build-id="{{buildrequest.id}}" data-request-url="{% url 'xhr_projectbuild' buildrequest.project.id %}" >Cancel</button>
+            <button class="btn btn-info pull-right cancel-build-btn" data-build-id="{{buildrequest.id}}" data-request-url="{% url 'projectbuilds' buildrequest.project.id %}" >Cancel</button>
 
          {% elif buildrequest.state == buildrequest.REQ_CREATED %}
 
diff --git a/lib/toaster/toastergui/templates/project.html b/lib/toaster/toastergui/templates/project.html
index e598631..63fbc40 100644
--- a/lib/toaster/toastergui/templates/project.html
+++ b/lib/toaster/toastergui/templates/project.html
@@ -30,7 +30,7 @@ vim: expandtab tabstop=2
 
 {%else%}
 
-<div id="main" role="main" data-ng-app="project" data-ng-controller="prjCtrl" class="top-padded">
+<div id="main" role="main" data-ng-app="project" data-ng-controller="prjCtrl" class="top-padded" data-ng-cloak>
 
   <!-- project name -->
   <div class="page-header">
@@ -46,10 +46,30 @@ vim: expandtab tabstop=2
 
   <!-- custom templates for ng -->
 
-  <script type="text/ng-template" id="suggestion_details">
-    <a> {[match.model.name]} {[match.model.detail]} </a>
+  <style>
+  .missing-layer {
+    color: lightgrey;
+  }
+  </style>
+  <script type="text/ng-template" id="recipes_suggestion_details">
+    <a> {[match.model.name]}
+      <span data-ng-class="{'missing-layer':($parent.$parent.$parent.$parent.filterProjectLayerIds().indexOf(match.model.projectcompatible_layer.id) == -1)}">
+          [{[match.model.layer_version__layer__name]}]
+      </span>
+    </a>
   </script>
 
+  <script type="text/ng-template" id="machines_suggestion_details">
+    <a>  {[match.model.name]} <span class="{'missing-layer':(filterProjectLayerIds().indexOf(match.model.layer_version_compatible_id) == -1)}">[{[match.model.layer_version__layer__name]}]</span> </a>
+  </script>
+
+  <script type="text/ng-template" id="layers_suggestion_details">
+    <a>  {[match.model['layer__name']]} ( {[match.model.layer__vcs_url]} ) </a>
+  </script>
+
+
+
+
   <!-- modal dialogs -->
   <script type="text/ng-template" id="dependencies_modal">
     <div class="modal-header">
@@ -104,14 +124,14 @@ vim: expandtab tabstop=2
   <div class="well">
     <form class="build-form" data-ng-submit="buildNamedTarget()">
       <div class="input-append controls">
-        <input type="text" class="huge input-xxlarge" placeholder="Type the recipe(s) you want to build" autocomplete="off" data-ng-model="targetName" data-typeahead="e.name for e in getAutocompleteSuggestions('targets', $viewValue)|filter:$viewValue" data-typeahead-template-url="suggestion_details" data-ng-disabled="!layers.length"/>
+        <input type="text" class="huge input-xxlarge" placeholder="Type the recipe(s) you want to build" autocomplete="off" data-ng-model="targetName" data-typeahead="a.name for a in getRecipesAutocompleteSuggestions($viewValue)" data-typeahead-template-url="recipes_suggestion_details" data-ng-disabled="!layers.length"/>
         <button type="submit" class="btn btn-large btn-primary" data-ng-disabled="!targetName.length">
         Build
         </button>
       </div>
       <i class="icon-question-sign get-help get-help-blue" title="Type the name of one or more recipes you want to build, separated by a space. You can also specify a task by appending a semicolon and a task name to a recipe name, like so: <code>core-image-minimal:do_build</code>"></i>
       <p>
-        <a href="{% url 'all-targets' project.id %}">View all compatible recipes</a>
+        <a href="{% url 'projecttargets' project.id %}">View all compatible recipes</a>
         <i class="icon-question-sign get-help get-help-blue heading-help" title="View all the recipes you can build with the release selected for this project, which is {[project.release.desc]}"></i>
         {% if completedbuilds.count %}
           | <a href="{% url 'projectbuilds' project.id %}">View all project builds ({{completedbuilds.count}})</a>
@@ -278,7 +298,7 @@ vim: expandtab tabstop=2
         <p>
         You can:
           <ul>
-            <li> <a href="{% url 'all-layers' project.id %}">View all compatible layers available in Toaster</a>
+            <li> <a href="{% url 'projectlayers' project.id %}">View all compatible layers available in Toaster</a>
             <li> <a href="{% url 'importlayer' project.id %}">Import a layer</a>
             <li> <a href="https://www.yoctoproject.org/docs/1.6.1/dev-manual/dev-manual.html#understanding-and-creating-layers" target="_blank">Read about layers in the manual</a>
           </ul>
@@ -287,13 +307,13 @@ vim: expandtab tabstop=2
       </div>
       <form data-ng-submit="layerAdd()">
         <div class="input-append">
-          <input type="text" class="input-xlarge" id="layer" autocomplete="off" placeholder="Type a layer name" data-minLength="1" data-ng-model="layerAddName" data-typeahead="e.name for e in getAutocompleteSuggestions('layers', $viewValue)|filter:$viewValue" data-typeahead-template-url="suggestion_details" data-typeahead-on-select="onLayerSelect($item, $model, $label)" data-typeahead-editable="false" data-ng-class="{ 'has-error': layerAddName.$invalid }" />
+          <input type="text" class="input-xlarge" id="layer" autocomplete="off" placeholder="Type a layer name" data-minLength="1" data-ng-model="layerAddName" data-typeahead="e for e in getLayersAutocompleteSuggestions($viewValue)" data-typeahead-template-url="layers_suggestion_details" data-typeahead-on-select="onLayerSelect($item, $model, $label)" data-typeahead-editable="false" data-ng-class="{ 'has-error': layerAddName.$invalid }" />
           <input type="submit" id="add-layer" class="btn" value="Add" data-ng-disabled="!layerAddName.length"/>
         </div>
         {% csrf_token %}
       </form>
       <p>
-        <a href="{% url 'all-layers' project.id %}">View all compatible layers</a>
+        <a href="{% url 'projectlayers' project.id %}">View all compatible layers</a>
         <i class="icon-question-sign get-help" title="View all the layers you can build with the release selected for this project, which is {[project.release.desc]}"></i>
         |
         <a href="{% url 'importlayer' project.id %}">Import layer</a></p>
@@ -314,14 +334,14 @@ vim: expandtab tabstop=2
       </h3>
         <form data-ng-submit="buildNamedTarget()">
           <div class="input-append">
-            <input type="text" class="input-xlarge" placeholder="Type the recipe(s) you want to build" autocomplete="off" data-minLength="1"  data-ng-model="targetName1" data-typeahead="e.name for e in getAutocompleteSuggestions('targets', $viewValue)|filter:$viewValue" data-typeahead-template-url="suggestion_details" data-ng-disabled="!layers.length">
+            <input type="text" class="input-xlarge" placeholder="Type the recipe(s) you want to build" autocomplete="off" data-minLength="1"  data-ng-model="targetName1" data-typeahead="a.name for a in getRecipesAutocompleteSuggestions($viewValue)" data-typeahead-template-url="recipes_suggestion_details" data-ng-disabled="!layers.length">
             <button type="submit" class="btn btn-primary" data-ng-disabled="!targetName1.length">
               Build </button>
           </div>
           {% csrf_token %}
         </form>
         <p>
-          <a href="{% url 'all-targets' project.id %}">View all compatible recipes</a>
+          <a href="{% url 'projecttargets' project.id %}">View all compatible recipes</a>
           <i class="icon-question-sign get-help" title="View all the recipes you can build with the release selected for this project, which is {[project.release.desc]}"></i>
         </p>
         <div data-ng-if="frequenttargets.length">
@@ -355,13 +375,13 @@ vim: expandtab tabstop=2
               You cannot really compare the builds for the new machine with the previous ones.
         </div>
         <form data-ng-submit="editProjectSettings('#select-machine')" class="input-append">
-              <input type="text" id="machine" autocomplete="off" data-ng-model="machineName" value="{[machine.name]}" data-typeahead="m.name for m in getAutocompleteSuggestions('machines', $viewValue)" data-typeahead-template-url="suggestion_details" />
+              <input type="text" id="machine" autocomplete="off" data-ng-model="machineName" value="{[machine.name]}" data-typeahead="m.name for m in getMachinesAutocompleteSuggestions($viewValue)" data-typeahead-template-url="machines_suggestion_details" />
               <input type="submit" id="apply-change-machine" class="btn" data-ng-disabled="machineName == machine.name || machineName.length == 0" value="Save"/>
               <input type="reset" id="cancel-machine" class="btn btn-link" data-ng-click="toggle('#select-machine')" value="Cancel"/>
               {% csrf_token %}
         </form>
         <p>
-          <a href="{% url 'all-machines' project.id %}" class="link">View all compatible machines</a>
+          <a href="{% url 'projectmachines' project.id %}" class="link">View all compatible machines</a>
         <i class="icon-question-sign get-help" title="View all the machines you can set with the release selected for this project, which is {[project.release.desc]}"></i>
       </p>
       </div>
@@ -429,11 +449,11 @@ vim: expandtab tabstop=2
 angular.element(document).ready(function() {
   scope = angular.element("#main").scope();
   scope.urls = {};
-  scope.urls.xhr_build = "{% url 'xhr_projectbuild' project.id %}";
+  scope.urls.xhr_build = "{% url 'projectbuilds' project.id %}";
   scope.urls.xhr_edit = "{% url 'project' project.id %}?format=json";
-  scope.urls.xhr_datatypeahead = "{% url 'xhr_datatypeahead' project.id %}";
-  scope.urls.layers = "{% url 'all-layers' project.id %}";
-  scope.urls.targets = "{% url 'all-targets' project.id %}";
+  scope.urls.layers = "{% url 'projectlayers' project.id %}";
+  scope.urls.targets = "{% url 'projecttargets' project.id %}";
+  scope.urls.machines = "{% url 'projectmachines' project.id %}";
   scope.urls.importlayer = "{% url 'importlayer' project.id %}";
   scope.urls.layer = "{% url 'base_layerdetails' project.id %}";
   scope.project = {{prj|json}};
diff --git a/lib/toaster/toastergui/templates/runagain.html b/lib/toaster/toastergui/templates/runagain.html
index a78c0dd..b4ba5fb 100644
--- a/lib/toaster/toastergui/templates/runagain.html
+++ b/lib/toaster/toastergui/templates/runagain.html
@@ -1,6 +1,6 @@
 {% load projecttags %}
 onclick='scheduleBuild(
-    {% url 'xhr_projectbuild' buildrequest.project.id as bpi %}{{bpi|json}},
+    {% url 'projectbuilds' buildrequest.project.id as bpi %}{{bpi|json}},
     {{buildrequest.project.name|json}},
     {% url 'project' buildrequest.project.id as bpurl %}{{bpurl|json}},
     {{buildrequest.brtarget_set.all|get_tasks|json}})
diff --git a/lib/toaster/toastergui/templatetags/projecttags.py b/lib/toaster/toastergui/templatetags/projecttags.py
index e79a4e5..75f2261 100644
--- a/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/lib/toaster/toastergui/templatetags/projecttags.py
@@ -55,7 +55,7 @@ def json(value, default = None):
     # JSON spec says that "\/" is functionally identical to "/" to allow for HTML-tag embedding in JSON strings
     # unfortunately, I can't find any option in the json module to turn on forward-slash escaping, so we do
     # it manually here
-    return mark_safe(JsonLib.dumps(value, default = default, ensure_ascii=False).replace('</', '<\\/'))
+    return mark_safe(JsonLib.dumps(value, indent=2, default = default, ensure_ascii=False).replace('</', '<\\/'))
 
 @register.assignment_tag
 def query(qs, **kwargs):
diff --git a/lib/toaster/toastergui/tests.py b/lib/toaster/toastergui/tests.py
index c92c5fe..77e80fe 100644
--- a/lib/toaster/toastergui/tests.py
+++ b/lib/toaster/toastergui/tests.py
@@ -38,9 +38,9 @@ class AllProjectsViewTestCase(ProvisionedProjectTestCase):
 
         self.assertTrue(AllProjectsViewTestCase.TEST_PROJECT_NAME in map(lambda x: x["name"], data["list"]))
         self.assertTrue("id" in data["list"][0])
-        self.assertTrue("xhrProjectDataTypeaheadUrl" in data["list"][0])
+        self.assertTrue("projectLayersUrl" in data["list"][0])
         self.assertTrue("projectPageUrl" in data["list"][0])
-        self.assertTrue("projectBuildUrl" in data["list"][0])
+        self.assertTrue("projectBuildsUrl" in data["list"][0])
 
 class ProvisionedLayersProjectTestCase(ProvisionedProjectTestCase):
     LAYER_NAME = "base-layer"
@@ -59,7 +59,7 @@ class XHRDataTypeAheadTestCase(ProvisionedLayersProjectTestCase):
         self.assertTrue(self.lv in self.project.compatible_layerversions())
 
     def test_xhr_datatypeahead_layer(self):
-        response = self.client.get(reverse('xhr_datatypeahead', args=(self.project.id,)), {"type": "layers"})
+        response = self.client.get(reverse('xhr_datatypeahead', args=(self.project.id,)), {"type": "layerdeps"})
         self.assertEqual(response.status_code, 200)
         self.assertTrue(response['Content-Type'].startswith('application/json'))
 
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index d527be0..f1b74cd 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -91,19 +91,19 @@ urlpatterns = patterns('toastergui.views',
             tables.MachinesTable.as_view(template_name="generic-toastertable-page.html"),
             { 'table_name': tables.MachinesTable.__name__.lower(),
               'title' : 'All compatible machines' },
-            name="all-machines"),
+            name="projectmachines"),
 
         url(r'^project/(?P<pid>\d+)/recipes/$',
             tables.RecipesTable.as_view(template_name="generic-toastertable-page.html"),
             { 'table_name': tables.RecipesTable.__name__.lower(),
               'title' : 'All compatible recipes' },
-            name="all-targets"),
+            name="projecttargets"),
 
         url(r'^project/(?P<pid>\d+)/layers/$',
             tables.LayersTable.as_view(template_name="generic-toastertable-page.html"),
             { 'table_name': tables.LayersTable.__name__.lower(),
               'title' : 'All compatible layers' },
-            name="all-layers"),
+            name="projectlayers"),
 
         url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)$',
             tables.LayerDetails.as_view(template_name='layerdetails.html'),
@@ -121,10 +121,8 @@ urlpatterns = patterns('toastergui.views',
               'title' : 'All machines in layer' },
             name=tables.LayerMachinesTable.__name__.lower()),
 
-        url(r'^xhr_projectbuild/(?P<pid>\d+)$', 'xhr_projectbuild', name='xhr_projectbuild'),
         url(r'^xhr_configvaredit/(?P<pid>\d+)$', 'xhr_configvaredit', name='xhr_configvaredit'),
 
-        url(r'^xhr_datatypeahead/(?P<pid>\d+)$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
         url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
         url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'),
 
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index c25c512..5221f1f 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -136,28 +136,6 @@ def _template_renderer(template):
 
                 # we're about to return; to keep up with the XHR API, we set the error to OK
                 context["error"] = "ok"
-                def _objtojson(obj):
-                    from django.db.models.query import QuerySet
-                    from django.db.models import Model, IntegerField
-                    if isinstance(obj, datetime):
-                        return obj.isoformat()
-                    elif isinstance(obj, timedelta):
-                        return obj.total_seconds()
-                    elif isinstance(obj, QuerySet) or isinstance(obj, set):
-                        return list(obj)
-                    elif hasattr( obj, '__dict__'):
-                        d = obj.__dict__
-                        nd = dict(d)
-                        for di in d:
-                            if di.startswith("_"):
-                                del nd[di]
-                            elif isinstance(d[di], Model):
-                                nd[di] = d[di].pk
-                            elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
-                                nd[di] = getattr(obj, "get_%s_display" % di)()
-                        return nd
-                    else:
-                        raise TypeError("Unserializable object %s of type %s" % ( obj, type(obj)))
 
                 return HttpResponse(jsonfilter(context, default=objtojson ),
                             content_type = "application/json; charset=utf-8")
@@ -167,6 +145,21 @@ def _template_renderer(template):
     return func_wrapper
 
 
+def _lv_to_dict(prj, x = None):
+    if x is None:
+        def wrapper(x):
+            return _lv_to_dict(prj, x)
+        return wrapper
+
+    return {"id": x.pk,
+            "name": x.layer.name,
+            "tooltip": x.layer.vcs_url+" | "+x.get_vcs_reference(),
+            "detail": "(" + x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.get_vcs_reference()+")"),
+            "giturl": x.layer.vcs_url,
+            "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
+            "revision" : x.get_vcs_reference(),
+           }
+
 
 def _build_page_range(paginator, index = 1):
     try:
@@ -335,7 +328,6 @@ 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()
@@ -2330,82 +2322,12 @@ if toastermain.settings.MANAGED:
         return context
 
 
-    def xhr_projectbuild(request, pid):
-        try:
-            if request.method != "POST":
-                raise BadParameterException("invalid method")
-            pid = pid
-            prj = Project.objects.get(id = pid)
-
-
-            if 'buildCancel' in request.POST:
-                for i in request.POST['buildCancel'].strip().split(" "):
-                    try:
-                        br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED)
-                        br.state = BuildRequest.REQ_DELETED
-                        br.save()
-                    except BuildRequest.DoesNotExist:
-                        pass
-
-            if 'buildDelete' in request.POST:
-                for i in request.POST['buildDelete'].strip().split(" "):
-                    try:
-                        br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_DELETED).delete()
-                    except BuildRequest.DoesNotExist:
-                        pass
-
-            if 'targets' in request.POST:
-                ProjectTarget.objects.filter(project = prj).delete()
-                s = str(request.POST['targets'])
-                for t in s.translate(None, ";%|\"").split(" "):
-                    if ":" in t:
-                        target, task = t.split(":")
-                    else:
-                        target = t
-                        task = ""
-                    ProjectTarget.objects.create(project = prj, target = target, task = task)
-
-                br = prj.schedule_build()
-
-            return HttpResponse(jsonfilter({"error":"ok",
-                "builds" : _project_recent_build_list(prj),
-            }), content_type = "application/json")
-        except Exception as e:
-            return HttpResponse(jsonfilter({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
-
-
     from django.views.decorators.csrf import csrf_exempt
     @csrf_exempt
     def xhr_datatypeahead(request, pid):
         try:
             prj = Project.objects.get(pk = pid)
 
-            def _lv_to_dict(x):
-                return {"id": x.pk,
-                        "name": x.layer.name,
-                        "tooltip": x.layer.vcs_url+" | "+x.get_vcs_reference(),
-                        "detail": "(" + x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.get_vcs_reference()+")"),
-                        "giturl": x.layer.vcs_url,
-                        "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
-                        "revision" : x.get_vcs_reference(),
-                       }
-
-
-            # returns layers for current project release that are not in the project set, matching the name
-            if request.GET.get('type', None) == "layers":
-                # all layers for the current project
-                queryset_all = prj.compatible_layerversions().filter(layer__name__icontains=request.GET.get('search',''))
-
-                # but not layers with equivalent layers already in project
-                if not request.GET.has_key('include_added'):
-                    queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])[:8]
-
-                # and show only the selected layers for this project
-                final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all])
-
-                return HttpResponse(jsonfilter( { "error":"ok",  "list" : map( _lv_to_dict, sorted(final_list, key = lambda x: x.layer.name)) }), content_type = "application/json")
-
-
             # returns layer dependencies for a layer, excluding current project layers
             if request.GET.get('type', None) == "layerdeps":
                 queryset = prj.compatible_layerversions().exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()]).filter(
@@ -2413,8 +2335,7 @@ if toastermain.settings.MANAGED:
 
                 final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset])
 
-                return HttpResponse(jsonfilter( { "error":"ok", "list" : map( _lv_to_dict, sorted(final_list, key = lambda x: x.layer.name)) }), content_type = "application/json")
-
+                return HttpResponse(jsonfilter( { "error":"ok", "list" : map( _lv_to_dict(prj), sorted(final_list, key = lambda x: x.layer.name)) }), content_type = "application/json")
 
 
             # returns layer versions that would be deleted on the new release__pk
@@ -2428,7 +2349,7 @@ if toastermain.settings.MANAGED:
                         retval.append(i)
 
                 return HttpResponse(jsonfilter( {"error":"ok",
-                    "list" : map( _lv_to_dict,  map(lambda x: x.layercommit, retval ))
+                    "list" : map( _lv_to_dict(prj),  map(lambda x: x.layercommit, retval ))
                     }), content_type = "application/json")
 
 
@@ -2446,52 +2367,8 @@ if toastermain.settings.MANAGED:
                     # and show only the selected layers for this project
                     final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all])
 
-                return HttpResponse(jsonfilter( { "error":"ok",  "list" : map( _lv_to_dict, final_list) }), content_type = "application/json")
+                return HttpResponse(jsonfilter( { "error":"ok",  "list" : map( _lv_to_dict(prj), final_list) }), content_type = "application/json")
 
-            # returns targets provided by current project layers
-            if request.GET.get('type', None) == "targets":
-                search_token = request.GET.get('search','')
-                queryset_all = Recipe.objects.filter(layer_version__layer__name__in = [x.layercommit.layer.name for x in prj.projectlayer_set.all().select_related("layercommit__layer")]).filter(Q(name__icontains=search_token) | Q(layer_version__layer__name__icontains=search_token))
-
-#                layer_equivalent_set = []
-#                for i in prj.projectlayer_set.all().select_related("layercommit__up_branch", "layercommit__layer"):
-#                    layer_equivalent_set += i.layercommit.get_equivalents_wpriority(prj)
-
-#                queryset_all = queryset_all.filter(layer_version__in =  layer_equivalent_set)
-
-                # if we have more than one hit here (for distinct name and version), max the id it out
-                queryset_all_maxids = queryset_all.values('name').distinct().annotate(max_id=Max('id')).values_list('max_id')
-                queryset_all = queryset_all.filter(id__in = queryset_all_maxids).order_by("name").select_related("layer_version__layer")
-
-
-                return HttpResponse(jsonfilter({ "error":"ok",
-                    "list" :
-                        # 7152 - sort by token position
-                        sorted (
-                            map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name +"]"},
-                        queryset_all[:8]),
-                            key = lambda i: i["name"].find(search_token) if i["name"].find(search_token) > -1 else 9999,
-                        )
-
-                    }), content_type = "application/json")
-
-            # returns machines provided by the current project layers
-            if request.GET.get('type', None) == "machines":
-                queryset_all = Machine.objects.all()
-                if 'project_id' in request.session:
-                    queryset_all = queryset_all.filter(layer_version__in =  prj.projectlayer_equivalent_set()).order_by("name")
-
-                search_token = request.GET.get('search','')
-                queryset_all = queryset_all.filter(Q(name__icontains=search_token) | Q(description__icontains=search_token))
-
-                return HttpResponse(jsonfilter({ "error":"ok",
-                        "list" :
-                        # 7152 - sort by the token position
-                        sorted (
-                            map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ "]"}, queryset_all[:8]),
-                            key = lambda i: i["name"].find(search_token) if i["name"].find(search_token) > -1 else 9999,
-                        )
-                    }), content_type = "application/json")
 
             raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied"))
         except Exception as e:
@@ -2791,7 +2668,41 @@ if toastermain.settings.MANAGED:
 
     @_template_renderer('projectbuilds.html')
     def projectbuilds(request, pid):
-        buildrequests = BuildRequest.objects.filter(project_id = pid).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
+        # process any build request
+        prj = Project.objects.get(id = pid)
+        if request.method == "POST":
+
+            if 'buildCancel' in request.POST:
+                for i in request.POST['buildCancel'].strip().split(" "):
+                    try:
+                        br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_QUEUED)
+                        br.state = BuildRequest.REQ_DELETED
+                        br.save()
+                    except BuildRequest.DoesNotExist:
+                        pass
+
+            if 'buildDelete' in request.POST:
+                for i in request.POST['buildDelete'].strip().split(" "):
+                    try:
+                        br = BuildRequest.objects.select_for_update().get(project = prj, pk = i, state__lte = BuildRequest.REQ_DELETED).delete()
+                    except BuildRequest.DoesNotExist:
+                        pass
+
+            if 'targets' in request.POST:
+                ProjectTarget.objects.filter(project = prj).delete()
+                s = str(request.POST['targets'])
+                for t in s.translate(None, ";%|\"").split(" "):
+                    if ":" in t:
+                        target, task = t.split(":")
+                    else:
+                        target = t
+                        task = ""
+                    ProjectTarget.objects.create(project = prj, target = target, task = task)
+
+                br = prj.schedule_build()
+
+
+        buildrequests = BuildRequest.objects.filter(project = prj).exclude(state__lte = BuildRequest.REQ_INPROGRESS).exclude(state=BuildRequest.REQ_DELETED)
 
         try:
             context, pagesize, orderby = _build_list_helper(request, buildrequests, False)
@@ -2940,9 +2851,10 @@ if toastermain.settings.MANAGED:
         # add fields needed in JSON dumps for API call support
         for p in project_info.object_list:
             p.id = p.pk
-            p.xhrProjectDataTypeaheadUrl = reverse('xhr_datatypeahead', args=(p.id,))
             p.projectPageUrl = reverse('project', args=(p.id,))
-            p.projectBuildUrl = reverse('xhr_projectbuild', args=(p.id,))
+            p.projectLayersUrl = reverse('projectlayers', args=(p.id,))
+            p.projectBuildsUrl = reverse('projectbuilds', args=(p.id,))
+            p.projectTargetsUrl = reverse('projecttargets', args=(p.id,))
 
         # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
         build_mru = _managed_get_latest_builds()
@@ -3242,14 +3154,6 @@ else:
         return {}
 
     @_template_renderer('landing_not_managed.html')
-    def xhr_projectbuild(request, pid):
-        return {}
-
-    @_template_renderer('landing_not_managed.html')
-    def xhr_datatypeahead(request):
-        return {}
-
-    @_template_renderer('landing_not_managed.html')
     def xhr_configvaredit(request, pid):
         return {}
 
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 8bc3d7f..f5a1b3e 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -253,8 +253,8 @@ class ToasterTable(TemplateView):
 
         data = cache.get(cache_name)
 
-        if data:
-            return data
+        #if data:
+        #    return data
 
         self.setup_columns(**kwargs)
 
@@ -277,9 +277,9 @@ class ToasterTable(TemplateView):
             'default_orderby' : self.default_orderby,
             'columns' : self.columns,
             'rows' : [],
+            'error' : "ok",
         }
 
-
         try:
             for row in page.object_list:
                 #Use collection to maintain the order
diff --git a/lib/toaster/toastermain/settings.py b/lib/toaster/toastermain/settings.py
index 225138b..3c7cb3b 100644
--- a/lib/toaster/toastermain/settings.py
+++ b/lib/toaster/toastermain/settings.py
@@ -223,7 +223,7 @@ CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
                'LOCATION': '/tmp/django-default-cache',
-               'TIMEOUT': 5,
+               'TIMEOUT': 1,
             }
           }
 
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 12/19] toastergui: remove xhr_datatypeahaed layerdeps call
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (10 preceding siblings ...)
  2015-06-10 14:38 ` [PATCH 11/19] toastergui: remove xhr_datatypeahead and xhr_XXXbuild Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 13/19] toaster: fixes after refactoring Alex DAMIAN
                   ` (6 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch removes the url-constructing calls to get the layer details
in favor of embedding the look-up URL in the JSON data on the
layer list page.

This allows further removal of the XHR-specific code for layer dependencies
in favor of REST calls to layer details data.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/static/js/importlayer.js    |  8 +--
 lib/toaster/toastergui/static/js/layerdetails.js   |  2 +-
 lib/toaster/toastergui/static/js/libtoaster.js     |  9 ++--
 lib/toaster/toastergui/static/js/projectapp.js     | 61 ++++++++++++++++------
 lib/toaster/toastergui/tables.py                   | 23 +++++---
 lib/toaster/toastergui/templates/importlayer.html  |  1 -
 lib/toaster/toastergui/templates/layerdetails.html |  1 -
 lib/toaster/toastergui/templates/project.html      |  1 -
 lib/toaster/toastergui/urls.py                     |  3 --
 lib/toaster/toastergui/views.py                    | 11 +---
 10 files changed, 73 insertions(+), 47 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/importlayer.js b/lib/toaster/toastergui/static/js/importlayer.js
index e1fc5c5..e9d7ae8 100644
--- a/lib/toaster/toastergui/static/js/importlayer.js
+++ b/lib/toaster/toastergui/static/js/importlayer.js
@@ -48,7 +48,7 @@ function importLayerPageInit (ctx) {
     newLayerDep.children("span").tooltip();
 
     var link = newLayerDep.children("a");
-    link.attr("href", ctx.layerDetailsUrl+String(currentLayerDepSelection.id));
+    link.attr("href", currentLayerDepSelection.layerDetailsUrl);
     link.text(currentLayerDepSelection.name);
     link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"});
 
@@ -63,11 +63,11 @@ function importLayerPageInit (ctx) {
 
     $("#layer-deps-list").append(newLayerDep);
 
-    libtoaster.getLayerDepsForProject(libtoaster.ctx.projectId, currentLayerDepSelection.id, function (data){
+    libtoaster.getLayerDepsForProject(currentLayerDepSelection.layerDetailsUrl, function (data){
         /* These are the dependencies of the layer added as a dependency */
         if (data.list.length > 0) {
-          currentLayerDepSelection.url = ctx.layerDetailsUrl+currentLayerDepSelection.id;
-          layerDeps[currentLayerDepSelection.id].deps = data.list
+          currentLayerDepSelection.url = currentLayerDepSelection.layerDetailsUrl;
+          layerDeps[currentLayerDepSelection.id].deps = data.list;
         }
 
         /* Clear the current selection */
diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
index ab78182..0accd97 100644
--- a/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -65,7 +65,7 @@ function layerDetailsPageInit (ctx) {
       newLayerDep.children("span").tooltip();
 
       var link = newLayerDep.children("a");
-      link.attr("href", ctx.layerDetailsUrl+String(currentLayerDepSelection.id));
+      link.attr("href", currentLayerDepSelection.layerDetailsUrl);
       link.text(currentLayerDepSelection.name);
       link.tooltip({title: currentLayerDepSelection.tooltip, placement: "right"});
 
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 2a9a790..b1038cf 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -170,10 +170,10 @@ var libtoaster = (function (){
     });
   }
 
-  function _getLayerDepsForProject(projectId, layerId, onSuccess, onFail){
+  function _getLayerDepsForProject(url, onSuccess, onFail){
     /* Check for dependencies not in the current project */
-    $.getJSON(libtoaster.ctx.projectLayersUrl,
-      { format: 'json', search: layerId },
+    $.getJSON(url,
+      { format: 'json' },
       function(data) {
         if (data.error != "ok") {
           console.log(data.error);
@@ -225,8 +225,7 @@ var libtoaster = (function (){
   function _addRmLayer(layerObj, add, doneCb){
     if (add === true) {
       /* If adding get the deps for this layer */
-      libtoaster.getLayerDepsForProject(libtoaster.ctx.projectId,
-        layerObj.id,
+      libtoaster.getLayerDepsForProject(layerObj.url,
         function (layers) {
 
         /* got result for dependencies */
diff --git a/lib/toaster/toastergui/static/js/projectapp.js b/lib/toaster/toastergui/static/js/projectapp.js
index 36c942f..a915278 100644
--- a/lib/toaster/toastergui/static/js/projectapp.js
+++ b/lib/toaster/toastergui/static/js/projectapp.js
@@ -16,7 +16,9 @@
 //  with this program; if not, write to the Free Software Foundation, Inc.,
 //  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-angular_formpost = function($httpProvider) {
+'use strict';
+
+var angular_formpost = function($httpProvider) {
   // Use x-www-form-urlencoded Content-Type
   // By Ezekiel Victor, http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/, no license, with attribution
   $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
@@ -127,10 +129,10 @@ projectApp.filter('timediff', function() {
             if (parseInt(j) < 10) {return "0" + j;}
             return j;
         }
-        seconds = parseInt(input);
-        minutes = Math.floor(seconds / 60);
+        var seconds = parseInt(input);
+        var minutes = Math.floor(seconds / 60);
         seconds = seconds - minutes * 60;
-        hours = Math.floor(seconds / 3600);
+        var hours = Math.floor(seconds / 3600);
         seconds = seconds - hours * 3600;
         return pad(hours) + ":" + pad(minutes) + ":" + pad(seconds);
     };
@@ -250,6 +252,31 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
         }
         var deffered = $q.defer();
 
+        /* we only talk in JSON to the server */
+        if (callparams.method == 'GET') {
+            if (callparams.data === undefined) {
+                callparams.data = {};
+            }
+            callparams.data.format = "json";
+        } else {
+            if (callparams.url.indexOf("?") > -1) {
+              callparams.url = callparams.url.split("?").map(function (element, index) {
+                if (index == 1) {
+                    var elements = [];
+                    if (element.indexOf("&")>-1) {
+                        elements = element.split("&");
+                    }
+                    elements.push("format=json");
+                    element = elements.join("&");
+                }
+                return element;
+              }).join("?");
+            } else {
+              callparams.url += "?format=json";
+            }
+        }
+
+
         if (undefined === callparams.headers) { callparams.headers = {}; }
         callparams.headers['X-CSRFToken'] = $cookies.csrftoken;
 
@@ -476,8 +503,9 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
     };
 
 
-    $scope.onLayerSelect = function (item) {
+    $scope.onLayerSelect = function (item, model, label) {
         $scope.layerToAdd = item;
+        $scope.layerAddName = item.layer__name;
     };
 
     $scope.machineSelect = function (machineName) {
@@ -501,20 +529,22 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
 
     $scope.layerAdd = function() {
 
-        $http({method:"GET", url: $scope.layerToAdd.layerdict.layerdetailurl, params : {}})
+        $http({method:"GET", url: $scope.layerToAdd.layerDetailsUrl, params : {format: "json"}})
         .success(function (_data) {
              if (_data.error != "ok") {
                  console.warn(_data.error);
              } else {
-                 if (_data.list.length > 0) {
+                 console.log("got layer deps", _data.layerdeps.list);
+                 if (_data.layerdeps.list.length > 0) {
                      // activate modal
+                     console.log("listing modals");
                      var modalInstance = $modal.open({
                        templateUrl: 'dependencies_modal',
                        controller: function ($scope, $modalInstance, items, layerAddName) {
                          $scope.items =  items;
                          $scope.layerAddName = layerAddName;
                          $scope.selectedItems = (function () {
-                                s = {};
+                                var s = {};
                                 for (var i = 0; i < items.length; i++)
                                     { s[items[i].id] = true; }
                                 return s;
@@ -535,16 +565,18 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
                        },
                        resolve: {
                          items: function () {
-                             return _data.list;
+                             return _data.layerdeps.list;
                          },
                          layerAddName: function () {
                              return $scope.layerAddName;
                          },
                        }
                      });
+                     console.log("built modal instance", modalInstance);
 
                      modalInstance.result.then(function (selectedArray) {
-                         selectedArray.push($scope.layerToAdd.layerversion.id);
+                         console.log("layer to add", $scope.layerToAdd)
+                         selectedArray.push($scope.layerToAdd.id);
                          console.warn("TRC6: selected", selectedArray);
 
                          $scope._makeXHRCall({
@@ -563,7 +595,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
                          $scope._makeXHRCall({
                              method: "POST", url: $scope.urls.xhr_edit,
                              data: {
-                                 layerAdd:  $scope.layerToAdd.layerversion.id,
+                                 layerAdd:  $scope.layerToAdd.id,
                              }
                          }).then(function () {
                              $scope.layerAddName = undefined;
@@ -772,8 +804,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
             return;
 
           if (imported.deps_added.length === 0) {
-            text = "You have imported <strong><a href=\""+$scope.urls.layer+
-              imported.imported_layer.id+"\">"+imported.imported_layer.name+
+            text = "You have imported <strong><a href=\""+imported.imported_layer.layerDetailsUrl+"\">"+imported.imported_layer.name+
               "</a></strong> and added it to your project.";
           } else {
             var links = "<a href=\""+$scope.urls.layer+
@@ -781,7 +812,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
               "</a>, ";
 
             imported.deps_added.map (function(item, index){
-              links +="<a href=\""+$scope.urls.layer+item.id+"\" >"+item.name+
+              links +="<a href=\""+item.layerDetailsUrl+"\" >"+item.name+
                 "</a>";
               /*If we're at the last element we don't want the trailing comma */
               if (imported.deps_added[index+1] !== undefined)
@@ -832,7 +863,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
         if (zone.maxid === undefined) { zone.maxid = 0; }
         var crtid = zone.maxid ++;
         angular.forEach(zone, function (o) { o.close(); });
-        o = {
+        var o = {
             id: crtid, text: text, type: type,
             close: function() {
                 zone.splice((function(id) {
diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index e03aa76..003b924 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -23,6 +23,7 @@ from toastergui.widgets import ToasterTable, ToasterTemplateView
 from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
 from django.db.models import Q, Max
 from django.conf.urls import url
+from django.core.urlresolvers import reverse
 from django.views.generic import TemplateView
 
 class LayersTable(ToasterTable):
@@ -35,9 +36,9 @@ class LayersTable(ToasterTable):
     def get_context_data(self, **kwargs):
         context = super(LayersTable, self).get_context_data(**kwargs)
 
-        context['project'] = Project.objects.get(pk=kwargs['pid'])
-
-        context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
+        project = Project.objects.get(pk=kwargs['pid'])
+        context['project'] = project
+        context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=project))
 
         return context
 
@@ -142,6 +143,13 @@ class LayersTable(ToasterTable):
                         static_data_name="add-del-layers",
                         static_data_template='{% include "layer_btn.html" %}')
 
+        project = Project.objects.get(pk=kwargs['pid'])
+        self.add_column(title="LayerDetailsUrl",
+                        displayable = False,
+                        field_name="layerDetailsUrl",
+                        computation = lambda x: reverse('layerdetails', args=(project.id, x.id)))
+
+
 
 
 class LayerDetails(ToasterTemplateView):
@@ -152,7 +160,8 @@ class LayerDetails(ToasterTemplateView):
         context['project'] = Project.objects.get(pk=kwargs['pid'])
         context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
         context['layerdict'] = _lv_to_dict(context['project'], context['layerversion'])
-        context['layerdeps'] = {"list": [x.depends_on.get_equivalents_wpriority(context['project'])[0] for x in context['layerversion'].dependencies.all()]}
+        context['layerdeps'] = {"list": [
+            [{"id": y.id, "name": y.layer.name} for y in x.depends_on.get_equivalents_wpriority(context['project'])][0] for x in context['layerversion'].dependencies.all()]}
         context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
 
         self.context_entries = ['project', 'layerversion', 'projectlayers', 'layerdict', 'layerdeps']
@@ -265,9 +274,10 @@ class RecipesTable(ToasterTable):
         self.default_orderby = "name"
 
     def get_context_data(self, **kwargs):
+        project = Project.objects.get(pk=kwargs['pid'])
         context = super(RecipesTable, self).get_context_data(**kwargs)
 
-        context['project'] = Project.objects.get(pk=kwargs['pid'])
+        context['project'] = project
 
         context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
 
@@ -342,10 +352,11 @@ class RecipesTable(ToasterTable):
                         static_data_name="add-del-layers",
                         static_data_template='{% include "recipe_btn.html" %}')
 
+        project = Project.objects.get(pk=kwargs['pid'])
         self.add_column(title="Project compatible Layer ID",
                         displayable = False,
                         field_name = "projectcompatible_layer",
-                        computation = lambda x: (x.layer_version.get_equivalents_wpriority(Project.objects.get(pk=kwargs['pid']))[0]))
+                        computation = lambda x: (x.layer_version.get_equivalents_wpriority(project)[0]))
 
 class LayerRecipesTable(RecipesTable):
     """ Smaller version of the Recipes table for use in layer details """
diff --git a/lib/toaster/toastergui/templates/importlayer.html b/lib/toaster/toastergui/templates/importlayer.html
index af8f4f9..498a204 100644
--- a/lib/toaster/toastergui/templates/importlayer.html
+++ b/lib/toaster/toastergui/templates/importlayer.html
@@ -14,7 +14,6 @@
                   <script>
                     $(document).ready(function (){
                       var ctx = {
-                        layerDetailsUrl : "{% url 'base_layerdetails' project.id %}",
                         xhrImportLayerUrl : "{% url 'xhr_importlayer' %}",
                       };
 
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index ef1795c..7d81b14 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -34,7 +34,6 @@
   $(document).ready(function (){
     var ctx = {
       projectBuildsUrl : "{% url 'projectbuilds' project.id %}",
-      layerDetailsUrl : "{% url 'base_layerdetails' project.id %}",
       xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}",
       layerVersion : {
         name : "{{layerversion.layer.name}}",
diff --git a/lib/toaster/toastergui/templates/project.html b/lib/toaster/toastergui/templates/project.html
index 63fbc40..7225363 100644
--- a/lib/toaster/toastergui/templates/project.html
+++ b/lib/toaster/toastergui/templates/project.html
@@ -455,7 +455,6 @@ angular.element(document).ready(function() {
   scope.urls.targets = "{% url 'projecttargets' project.id %}";
   scope.urls.machines = "{% url 'projectmachines' project.id %}";
   scope.urls.importlayer = "{% url 'importlayer' project.id %}";
-  scope.urls.layer = "{% url 'base_layerdetails' project.id %}";
   scope.project = {{prj|json}};
   scope.builds = {{builds|json}};
   scope.layers = {{layers|json}};
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index f1b74cd..5a79f88 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -80,12 +80,9 @@ urlpatterns = patterns('toastergui.views',
         url(r'^project/(?P<pid>\d+)/configuration$', 'projectconf', name='projectconf'),
         url(r'^project/(?P<pid>\d+)/builds/$', 'projectbuilds', name='projectbuilds'),
 
-        url(r'^project/(?P<pid>\d+)/layer/$', lambda x,pid: HttpResponseBadRequest(), name='base_layerdetails'),
-
         # the import layer is a project-specific functionality;
         url(r'^project/(?P<pid>\d+)/importlayer$', 'importlayer', name='importlayer'),
 
-
         # the table pages that have been converted to ToasterTable widget
         url(r'^project/(?P<pid>\d+)/machines/$',
             tables.MachinesTable.as_view(template_name="generic-toastertable-page.html"),
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 5221f1f..b2b263b 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -2238,7 +2238,7 @@ if toastermain.settings.MANAGED:
         if request.method == "POST":
             # add layers
             if 'layerAdd' in request.POST:
-                for lc in Layer_Version.objects.filter(pk__in=request.POST['layerAdd'].split(",")):
+                for lc in Layer_Version.objects.filter(pk__in=[i for i in request.POST['layerAdd'].split(",") if len(i) > 0]):
                     ProjectLayer.objects.get_or_create(project = prj, layercommit = lc)
 
             # remove layers
@@ -2328,15 +2328,6 @@ if toastermain.settings.MANAGED:
         try:
             prj = Project.objects.get(pk = pid)
 
-            # returns layer dependencies for a layer, excluding current project layers
-            if request.GET.get('type', None) == "layerdeps":
-                queryset = prj.compatible_layerversions().exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()]).filter(
-                    layer__name__in = [ x.depends_on.layer.name for x in LayerVersionDependency.objects.filter(layer_version_id = request.GET.get('search', None))])
-
-                final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset])
-
-                return HttpResponse(jsonfilter( { "error":"ok", "list" : map( _lv_to_dict(prj), sorted(final_list, key = lambda x: x.layer.name)) }), content_type = "application/json")
-
 
             # returns layer versions that would be deleted on the new release__pk
             if request.GET.get('type', None) == "versionlayers":
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 13/19] toaster: fixes after refactoring
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (11 preceding siblings ...)
  2015-06-10 14:39 ` [PATCH 12/19] toastergui: remove xhr_datatypeahaed layerdeps call Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 14/19] toaster: Code cleanup: bashisms Alex DAMIAN
                   ` (5 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch fixes issues brought in by refactoring:

* the New Build button is working with pre-set projects
* the xhr_datatypeahead is exposed for calls that are not
mapable to the REST objects
* a new table returing recipes provided by layers currently
selected in the project is used to provide recipe suggestions
* the field names in json are switched from "list" to "rows" as
to maintain consistency with the ToasterTables
* the "value" field in xhr_ calls is now named "search" to maintain
consistency

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/orm/models.py                      |   5 +-
 lib/toaster/toastergui/static/js/base.js       | 104 +++++++++++++++----------
 lib/toaster/toastergui/static/js/libtoaster.js |   2 +-
 lib/toaster/toastergui/static/js/projectapp.js |  24 ++++--
 lib/toaster/toastergui/tables.py               |  10 ++-
 lib/toaster/toastergui/templates/base.html     |   7 +-
 lib/toaster/toastergui/templates/project.html  |   3 +-
 lib/toaster/toastergui/urls.py                 |   8 ++
 lib/toaster/toastergui/views.py                |  17 ++--
 9 files changed, 116 insertions(+), 64 deletions(-)

diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index 8e73ee1..8819450 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -180,13 +180,12 @@ class Project(models.Model):
             queryset = queryset.filter(layer__name = layer_name)
 
         # order by layer version priority
-        queryset = queryset.filter(Q(layer_source=None) | Q(layer_source__releaselayersourcepriority__release = release)).select_related('layer_source', 'layer', 'up_branch').annotate(prio=Avg("layer_source__releaselayersourcepriority__priority")).order_by("-prio")
+        queryset = queryset.filter(Q(layer_source=None) | Q(layer_source__releaselayersourcepriority__release = release)).select_related('layer_source', 'layer', 'up_branch', "layer_source__releaselayersourcepriority__priority").order_by("-layer_source__releaselayersourcepriority__priority")
 
         return queryset
 
-    # returns a set of layer-equivalent set of layers already in project
     def projectlayer_equivalent_set(self):
-        return [j for i in [x.layercommit.get_equivalents_wpriority(self) for x in self.projectlayer_set.all().select_related("up_branch")] for j in i]
+        return self.compatible_layerversions().filter(layer__name__in = [x.layercommit.layer.name for x in self.projectlayer_set.all()]).select_related("up_branch")
 
     def schedule_build(self):
         from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake
diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js
index 747442c..06d0676 100644
--- a/lib/toaster/toastergui/static/js/base.js
+++ b/lib/toaster/toastergui/static/js/base.js
@@ -1,100 +1,118 @@
+'use strict';
 
-
-function basePageInit (ctx) {
+function basePageInit(ctx) {
 
   var newBuildButton = $("#new-build-button");
   /* Hide the button if we're on the project,newproject or importlyaer page
    * or if there are no projects yet defined
    */
-  if (ctx.numProjects == 0 || ctx.currentUrl.search('newproject|project/\\d$|importlayer$') > 0){
-      newBuildButton.hide();
-      return;
+  if (ctx.numProjects === 0 || ctx.currentUrl.search('newproject|project/\\d$|importlayer$') > 0) {
+    newBuildButton.hide();
+    return;
   }
 
   var currentProjectId = libtoaster.ctx.projectId;
 
   /* Hide the change project icon when there is only one project */
-  if (ctx.numProjects == 1){
-     $('#project .icon-pencil').hide();
+  if (ctx.numProjects === 1) {
+    $('#project .icon-pencil').hide();
   }
 
   newBuildButton.show().removeAttr("disabled");
 
 
-  _checkProjectBuildable()
+  var newBuildProjectInput = $("#new-build-button #project-name-input");
+  var newBuildTargetBuildBtn = $("#new-build-button #build-button");
+  var newBuildTargetInput = $("#new-build-button #build-target-input");
+  var newBuildProjectSaveBtn = $("#new-build-button #save-project-button");
+
+
+  var selectedTarget;
+
+  _checkProjectBuildable();
   _setupNewBuildButton();
 
 
-  function _checkProjectBuildable(){
-    if (libtoaster.ctx.projectId == undefined)
+  function _checkProjectBuildable() {
+    if (libtoaster.ctx.projectId === undefined) {
       return;
+    }
 
     libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
-      function(data){
-        if (data.machine.name == undefined || data.layers.length == 0) {
+      function (data) {
+        if (data.machine.name === undefined || data.layers.length === 0) {
           /* we can't build anything with out a machine and some layers */
           $("#new-build-button #targets-form").hide();
           $("#new-build-button .alert").show();
         } else {
           $("#new-build-button #targets-form").show();
           $("#new-build-button .alert").hide();
+
+          /* we can build this project; enable input fields */
+          newBuildTargetInput.prop("disabled", false);
+          newBuildTargetBuildBtn.prop("disabled", false);
+
+          libtoaster.makeTypeahead(newBuildTargetInput, libtoaster.ctx.projectTargetsUrl, { format: "json" }, function (item) {
+            /* successfully selected a target */
+            selectedTarget = item;
+          });
+
         }
-    }, null);
+      }, null);
   }
 
   function _setupNewBuildButton() {
     /* Setup New build button */
-    var newBuildProjectInput = $("#new-build-button #project-name-input");
-    var newBuildTargetBuildBtn = $("#new-build-button #build-button");
-    var newBuildTargetInput = $("#new-build-button #build-target-input");
-    var newBuildProjectSaveBtn = $("#new-build-button #save-project-button");
-    var selectedTarget;
     var selectedProject;
 
     /* If we don't have a current project then present the set project
      * form.
      */
-    if (libtoaster.ctx.projectId == undefined) {
+    if (libtoaster.ctx.projectId === undefined) {
       $('#change-project-form').show();
       $('#project .icon-pencil').hide();
     }
 
 
-    libtoaster.makeTypeahead(newBuildProjectInput, libtoaster.ctx.projectsUrl, { format : "json" }, function(item){
-        /* successfully selected a project */
-        newBuildProjectSaveBtn.removeAttr("disabled");
-        selectedProject = item;
+    libtoaster.makeTypeahead(newBuildProjectInput, libtoaster.ctx.projectsUrl, { format : "json" }, function (item) {
+      /* successfully selected a project */
+      newBuildProjectSaveBtn.removeAttr("disabled");
+      selectedProject = item;
     });
 
     /* Any typing in the input apart from enter key is going to invalidate
      * the value that has been set by selecting a suggestion from the typeahead
      */
-    newBuildProjectInput.on('input', function(event) {
-        if (event.keyCode == 13)
-          return;
-        newBuildProjectSaveBtn.attr("disabled", "disabled");
+    newBuildProjectInput.on('input', function (event) {
+      if (event.keyCode === 13) {
+        return;
+      }
+      newBuildProjectSaveBtn.attr("disabled", "disabled");
     });
 
-    newBuildTargetInput.on('input', function() {
-      if ($(this).val().length == 0)
+    newBuildTargetInput.on('input', function () {
+      if ($(this).val().length === 0) {
         newBuildTargetBuildBtn.attr("disabled", "disabled");
-      else
+      } else {
         newBuildTargetBuildBtn.removeAttr("disabled");
+      }
     });
 
-    newBuildTargetBuildBtn.click(function() {
-      if (!newBuildTargetInput.val())
+    newBuildTargetBuildBtn.click(function () {
+      if (!newBuildTargetInput.val()) {
         return;
+      }
 
-      if (!selectedTarget)
+      if (!selectedTarget) {
         selectedTarget = { name: newBuildTargetInput.val() };
+      }
       /* fire and forget */
-      libtoaster.startABuild(ctx.projectBuildsUrl, libtoaster.ctx.projectId, selectedTarget.name, null, null);
+      libtoaster.startABuild(libtoaster.ctx.projectBuildsUrl, libtoaster.ctx.projectId, selectedTarget.name, null, null);
       window.location.replace(libtoaster.ctx.projectPageUrl);
     });
 
-    newBuildProjectSaveBtn.click(function() {
-      libtoaster.ctx.projectId = selectedProject.pk
+    newBuildProjectSaveBtn.click(function () {
+      libtoaster.ctx.projectId = selectedProject.pk;
       /* Update the typeahead project_id paramater */
       _checkProjectBuildable();
 
@@ -111,10 +129,10 @@ function basePageInit (ctx) {
       newBuildTargetInput.prop("disabled", false);
       newBuildTargetBuildBtn.prop("disabled", false);
 
-      libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function(item){
+      libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function (item) {
         /* successfully selected a target */
         selectedTarget = item;
-	    });
+      });
 
       newBuildTargetInput.val("");
 
@@ -123,12 +141,12 @@ function basePageInit (ctx) {
       $("#new-build-button .alert a").attr('href', libtoaster.ctx.projectPageUrl);
       $("#project .icon-pencil").show();
 
-      $("#change-project-form").slideUp({ 'complete' : function() {
+      $("#change-project-form").slideUp({ 'complete' : function () {
           $("#new-build-button #project").show();
       }});
     });
 
-    $('#new-build-button #project .icon-pencil').click(function() {
+    $('#new-build-button #project .icon-pencil').click(function () {
       newBuildProjectSaveBtn.attr("disabled", "disabled");
       newBuildProjectInput.val($("#new-build-button #project a").text());
       $("#cancel-change-project").show();
@@ -136,8 +154,8 @@ function basePageInit (ctx) {
       $("#change-project-form").slideDown();
     });
 
-    $("#new-build-button #cancel-change-project").click(function() {
-      $("#change-project-form").hide(function(){
+    $("#new-build-button #cancel-change-project").click(function () {
+      $("#change-project-form").hide(function () {
         $('#new-build-button #project').show();
       });
 
@@ -146,7 +164,7 @@ function basePageInit (ctx) {
     });
 
     /* Keep the dropdown open even unless we click outside the dropdown area */
-    $(".new-build").click (function(event) {
+    $(".new-build").click (function (event) {
       event.stopPropagation();
     });
   };
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index b1038cf..23755a7 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -25,7 +25,7 @@ var libtoaster = (function (){
               return;
             }
 
-            return process (data.list);
+            return process (data.rows);
           });
         },
         updater: function(item) {
diff --git a/lib/toaster/toastergui/static/js/projectapp.js b/lib/toaster/toastergui/static/js/projectapp.js
index a915278..44e244d 100644
--- a/lib/toaster/toastergui/static/js/projectapp.js
+++ b/lib/toaster/toastergui/static/js/projectapp.js
@@ -217,13 +217,13 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
     $scope.getAutocompleteSuggestions = function(type, currentValue) {
         var deffered = $q.defer();
 
-        $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: type, value: currentValue}})
+        $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: type, search: currentValue}})
             .success(function (_data) {
                 if (_data.error != "ok") {
                     console.warn(_data.error);
                     deffered.reject(_data.error);
                 }
-                deffered.resolve(_data.list);
+                deffered.resolve(_data.rows);
             });
 
         return deffered.promise;
@@ -534,8 +534,17 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
              if (_data.error != "ok") {
                  console.warn(_data.error);
              } else {
-                 console.log("got layer deps", _data.layerdeps.list);
-                 if (_data.layerdeps.list.length > 0) {
+                /* filter out layers that are already in the project */
+                var filtered_list = [];
+                var projectlayers_ids = $scope.layers.map(function (e) { return e.id });
+                for (var i = 0; i < _data.layerdeps.list.length; i++) {
+                    if (projectlayers_ids.indexOf(_data.layerdeps.list[i].id) == -1) {
+                        filtered_list.push( _data.layerdeps.list[i]);
+                    }
+                }
+
+                _data.layerdeps.list = filtered_list;
+                if (_data.layerdeps.list.length > 0) {
                      // activate modal
                      console.log("listing modals");
                      var modalInstance = $modal.open({
@@ -575,7 +584,6 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
                      console.log("built modal instance", modalInstance);
 
                      modalInstance.result.then(function (selectedArray) {
-                         console.log("layer to add", $scope.layerToAdd)
                          selectedArray.push($scope.layerToAdd.id);
                          console.warn("TRC6: selected", selectedArray);
 
@@ -634,13 +642,13 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
     $scope.testProjectSettingsChange = function(elementid) {
         if (elementid != '#change-project-version') throw "Not implemented";
 
-        $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "versionlayers", value: $scope.projectVersion }}).
+        $http({method:"GET", url: $scope.urls.xhr_datatypeahead, params : { type: "versionlayers", search: $scope.projectVersion }}).
         success(function (_data) {
             if (_data.error != "ok") {
                 alert (_data.error);
             }
             else {
-                 if (_data.list.length > 0) {
+                 if (_data.rows.length > 0) {
                      // activate modal
                      var modalInstance = $modal.open({
                        templateUrl: 'change_version_modal',
@@ -660,7 +668,7 @@ projectApp.controller('prjCtrl', function($scope, $modal, $http, $interval, $loc
                        },
                        resolve: {
                          items: function () {
-                             return _data.list;
+                             return _data.rows;
                          },
                          releaseName: function () {
                              return $scope.releases.filter(function (e) { if (e.id == $scope.projectVersion) return e;})[0].name;
diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index 003b924..b85527e 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -287,7 +287,7 @@ class RecipesTable(ToasterTable):
     def setup_queryset(self, *args, **kwargs):
         prj = Project.objects.get(pk = kwargs['pid'])
 
-        self.queryset = Recipe.objects.filter(Q(layer_version__up_branch__name= prj.release.name) | Q(layer_version__build__in = prj.build_set.all())).filter(name__regex=r'.{1,}.*')
+        self.queryset = Recipe.objects.filter(layer_version__in = prj.compatible_layerversions())
 
         search_maxids = map(lambda i: i[0], list(self.queryset.values('name').distinct().annotate(max_id=Max('id')).values_list('max_id')))
 
@@ -392,3 +392,11 @@ class LayerRecipesTable(RecipesTable):
         self.add_column(title="Build recipe",
                         static_data_name="add-del-layers",
                         static_data_template=build_recipe_template)
+
+class ProjectLayersRecipesTable(RecipesTable):
+    """ Table that lists only recipes available for layers added to the project """
+
+    def setup_queryset(self, *args, **kwargs):
+        super(ProjectLayersRecipesTable, self).setup_queryset(*args, **kwargs)
+        prj = Project.objects.get(pk = kwargs['pid'])
+        self.queryset = self.queryset.filter(layer_version__in = prj.projectlayer_equivalent_set())
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index 7fee26e..6cdc5e8 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 {% load static %}
+{% load projecttags %}
 <html lang="en">
     <head>
         <title>{% if objectname %} {{objectname|title}} - {% endif %}Toaster</title>
@@ -33,8 +34,10 @@
     htmlUrl : "{% static 'html/' %}",
     projectsUrl : "{% url 'all-projects' %}",
     {% if project.id %}
-      projectPageUrl : "{% url 'project' project.id %}",
-      projectName : "{{project.name}}",
+      projectPageUrl : {% url 'project' project.id as purl%}{{purl|json}},
+      projectName : {{project.name|json}},
+      projectTargetsUrl: {% url 'projectavailabletargets' project.id as paturl%}{{paturl|json}},
+      projectBuildsUrl: {% url 'projectbuilds' project.id as pburl %}{{pburl|json}},
       projectId : {{project.id}},
     {% else %}
       projectPageUrl : undefined,
diff --git a/lib/toaster/toastergui/templates/project.html b/lib/toaster/toastergui/templates/project.html
index 7225363..bca703a 100644
--- a/lib/toaster/toastergui/templates/project.html
+++ b/lib/toaster/toastergui/templates/project.html
@@ -452,9 +452,10 @@ angular.element(document).ready(function() {
   scope.urls.xhr_build = "{% url 'projectbuilds' project.id %}";
   scope.urls.xhr_edit = "{% url 'project' project.id %}?format=json";
   scope.urls.layers = "{% url 'projectlayers' project.id %}";
-  scope.urls.targets = "{% url 'projecttargets' project.id %}";
+  scope.urls.targets = "{% url 'projectavailabletargets' project.id %}";
   scope.urls.machines = "{% url 'projectmachines' project.id %}";
   scope.urls.importlayer = "{% url 'importlayer' project.id %}";
+  scope.urls.xhr_datatypeahead = {% url 'xhr_datatypeahead' project.id as xhrdta %}{{xhrdta|json}};
   scope.project = {{prj|json}};
   scope.builds = {{builds|json}};
   scope.layers = {{layers|json}};
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index 5a79f88..bd3eb40 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -96,6 +96,12 @@ urlpatterns = patterns('toastergui.views',
               'title' : 'All compatible recipes' },
             name="projecttargets"),
 
+        url(r'^project/(?P<pid>\d+)/availablerecipes/$',
+            tables.ProjectLayersRecipesTable.as_view(template_name="generic-toastertable-page.html"),
+            { 'table_name': tables.ProjectLayersRecipesTable.__name__.lower(),
+              'title' : 'Recipes available for layers in the current project' },
+            name="projectavailabletargets"),
+
         url(r'^project/(?P<pid>\d+)/layers/$',
             tables.LayersTable.as_view(template_name="generic-toastertable-page.html"),
             { 'table_name': tables.LayersTable.__name__.lower(),
@@ -118,6 +124,8 @@ urlpatterns = patterns('toastergui.views',
               'title' : 'All machines in layer' },
             name=tables.LayerMachinesTable.__name__.lower()),
 
+
+        url(r'^xhr_datatypeahead/(?P<pid>\d+)$', 'xhr_datatypeahead', name='xhr_datatypeahead'),
         url(r'^xhr_configvaredit/(?P<pid>\d+)$', 'xhr_configvaredit', name='xhr_configvaredit'),
 
         url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index b2b263b..4dac62c 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -129,9 +129,9 @@ def _template_renderer(template):
 
             if request.GET.get('format', None) == 'json':
                 # objects is a special keyword - it's a Page, but we need the actual objects here
-                # in XHR, the objects come in the "list" property
+                # in XHR, the objects come in the "rows" property
                 if "objects" in context:
-                    context["list"] = context["objects"].object_list
+                    context["rows"] = context["objects"].object_list
                     del context["objects"]
 
                 # we're about to return; to keep up with the XHR API, we set the error to OK
@@ -2340,7 +2340,7 @@ if toastermain.settings.MANAGED:
                         retval.append(i)
 
                 return HttpResponse(jsonfilter( {"error":"ok",
-                    "list" : map( _lv_to_dict(prj),  map(lambda x: x.layercommit, retval ))
+                    "rows" : map( _lv_to_dict(prj),  map(lambda x: x.layercommit, retval ))
                     }), content_type = "application/json")
 
 
@@ -2358,7 +2358,7 @@ if toastermain.settings.MANAGED:
                     # and show only the selected layers for this project
                     final_list = set([x.get_equivalents_wpriority(prj)[0] for x in queryset_all])
 
-                return HttpResponse(jsonfilter( { "error":"ok",  "list" : map( _lv_to_dict(prj), final_list) }), content_type = "application/json")
+                return HttpResponse(jsonfilter( { "error":"ok",  "rows" : map( _lv_to_dict(prj), final_list) }), content_type = "application/json")
 
 
             raise Exception("Unknown request! " + request.GET.get('type', "No parameter supplied"))
@@ -2845,7 +2845,7 @@ if toastermain.settings.MANAGED:
             p.projectPageUrl = reverse('project', args=(p.id,))
             p.projectLayersUrl = reverse('projectlayers', args=(p.id,))
             p.projectBuildsUrl = reverse('projectbuilds', args=(p.id,))
-            p.projectTargetsUrl = reverse('projecttargets', args=(p.id,))
+            p.projectTargetsUrl = reverse('projectavailabletargets', args=(p.id,))
 
         # build view-specific information; this is rendered specifically in the builds page, at the top of the page (i.e. Recent builds)
         build_mru = _managed_get_latest_builds()
@@ -3144,6 +3144,13 @@ else:
     def project(request, pid):
         return {}
 
+    from django.views.decorators.csrf import csrf_exempt
+    @csrf_exempt
+    @_template_renderer('landing_not_managed.html')
+    def xhr_datatypeahead(request, pid):
+        return {}
+
+
     @_template_renderer('landing_not_managed.html')
     def xhr_configvaredit(request, pid):
         return {}
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 00/19] Toaster refactoring and bugfixing patchset
@ 2015-06-10 14:39 Alex DAMIAN
  2015-06-10 14:38 ` [PATCH 01/19] toaster: move project data typeahead to the REST API Alex DAMIAN
                   ` (18 more replies)
  0 siblings, 19 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel; +Cc: Vlad Fulgeanu

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This is a Toaster patchest that brings in refactoring in order to eliminate
duplicated code paths, and bring in improvements in the communication between
the browser and back-end.

The changes have been tested and reviewed on the Toaster mailing list.

Can you please pull when possible ?

Cheers,
Alex


The following changes since commit 430f7a288b4446600b3a943c51f6711ffcf9e619:

  bitbake: Bump version to 1.27.1 (2015-06-09 13:29:19 +0100)

are available in the git repository at:

  git://git.yoctoproject.org/poky-contrib adamian/20150610-submission-bb
  http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=adamian/20150610-submission-bb

Alexandru DAMIAN (14):
  toaster: move project data typeahead to the REST API
  convert all project-based view to JSON APIs
  toaster: toastertables REST refactoring
  toastergui: use defaults on xhr_datatypeahead parameters
  toastergui: remove xhr_projectedit and xhr_projectinfo URLs
  toaster: toaster table add raw data
  toaster: ToasterTables add computational fields
  toaster: toastertables raise errors
  toaster: add class template view for single-object pages
  toaster: eliminate duplicate querysets
  toastergui: remove xhr_datatypeahead and xhr_XXXbuild
  toastergui: remove xhr_datatypeahaed layerdeps call
  toaster: fixes after refactoring
  toastergui: prevent contamination of libtoaster context

Ed Bartosh (2):
  toaster: Code cleanup: bashisms
  toaster: Make toaster script to work in dash

Michael Wood (2):
  toaster: Add ajax loading spinner
  toaster: Add global ajaxError handler

Vlad Fulgeanu (1):
  toaster: tables: Remove obsolete field_name for select column

 bin/toaster                                        |  82 ++--
 lib/toaster/contrib/tts/urllist.py                 |   5 -
 lib/toaster/orm/models.py                          |  44 +-
 lib/toaster/toastergui/static/css/default.css      |  49 +++
 lib/toaster/toastergui/static/js/base.js           | 140 +++---
 lib/toaster/toastergui/static/js/importlayer.js    |  14 +-
 lib/toaster/toastergui/static/js/layerdetails.js   |   6 +-
 lib/toaster/toastergui/static/js/libtoaster.js     |  60 ++-
 lib/toaster/toastergui/static/js/projectapp.js     | 146 ++++--
 lib/toaster/toastergui/static/js/table.js          |  37 +-
 lib/toaster/toastergui/tables.py                   | 120 +++--
 lib/toaster/toastergui/templates/base.html         |  29 +-
 .../toastergui/templates/basetable_bottom.html     |   2 +-
 .../templates/detail_pagination_bottom.html        |   2 +-
 lib/toaster/toastergui/templates/importlayer.html  |   1 -
 lib/toaster/toastergui/templates/layerdetails.html |   5 +-
 .../toastergui/templates/managed_mrb_section.html  |   2 +-
 lib/toaster/toastergui/templates/project.html      |  56 ++-
 lib/toaster/toastergui/templates/runagain.html     |   2 +-
 .../toastergui/templates/toastertable-simple.html  |   2 +-
 lib/toaster/toastergui/templates/toastertable.html |   2 +-
 lib/toaster/toastergui/templatetags/projecttags.py |   4 +-
 lib/toaster/toastergui/tests.py                    |   7 +-
 lib/toaster/toastergui/urls.py                     |  52 ++-
 lib/toaster/toastergui/views.py                    | 489 ++++++++++-----------
 lib/toaster/toastergui/widgets.py                  | 122 +++--
 lib/toaster/toastermain/settings.py                |   2 +-
 27 files changed, 881 insertions(+), 601 deletions(-)

-- 
1.9.1



^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 14/19] toaster: Code cleanup: bashisms
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (12 preceding siblings ...)
  2015-06-10 14:39 ` [PATCH 13/19] toaster: fixes after refactoring Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 15/19] toaster: Make toaster script to work in dash Alex DAMIAN
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel

From: Ed Bartosh <ed.bartosh@linux.intel.com>

Fixed the following bashisms:
 replaced echo -e -> printf
 removed 'function' from function definitions
 replaced $(< ${file}) -> `cat ${file}`

Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
 bin/toaster | 55 ++++++++++++++++++++++++++++---------------------------
 1 file changed, 28 insertions(+), 27 deletions(-)

diff --git a/bin/toaster b/bin/toaster
index ee19937..0fa8b60 100755
--- a/bin/toaster
+++ b/bin/toaster
@@ -28,23 +28,24 @@
 
 # Helper function to kill a background toaster development server
 
-function webserverKillAll()
+webserverKillAll()
 {
     local pidfile
     for pidfile in ${BUILDDIR}/.toastermain.pid; do
         if [ -f ${pidfile} ]; then
-        while kill -0 $(< ${pidfile}) 2>/dev/null; do
-            kill -SIGTERM -$(< ${pidfile}) 2>/dev/null
-            sleep 1
-            # Kill processes if they are still running - may happen in interactive shells
-            pkill -U $UID -f "python.*manage.py runserver"
-        done;
-        rm  ${pidfile}
+            pid=`cat ${pidfile}`
+            while kill -0 $pid 2>/dev/null; do
+                kill -SIGTERM -$pid 2>/dev/null
+                sleep 1
+                # Kill processes if they are still running - may happen in interactive shells
+                ps fux | grep "python.*manage.py runserver" | awk '{print $2}' | xargs kill
+            done
+            rm  ${pidfile}
         fi
     done
 }
 
-function webserverStartAll()
+webserverStartAll()
 {
     # do not start if toastermain points to a valid process
     if ! cat "${BUILDDIR}/.toastermain.pid" 2>/dev/null | xargs -I{} kill -0 {} ; then
@@ -62,7 +63,7 @@ function webserverStartAll()
     if [ $retval -eq 1 ]; then
         echo "Failed db sync, stopping system start" 1>&2
     elif [ $retval -eq 2 ]; then
-        echo -e "\nError on migration, trying to recover... \n"
+        printf "\nError on migration, trying to recover... \n"
         python $BBBASEDIR/lib/toaster/manage.py migrate orm 0001_initial --fake
         retval=0
         python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=1
@@ -87,7 +88,7 @@ function webserverStartAll()
 
 # Helper functions to add a special configuration file
 
-function addtoConfiguration()
+addtoConfiguration()
 {
     file=$1
     shift
@@ -98,13 +99,13 @@ function addtoConfiguration()
 INSTOPSYSTEM=0
 
 # define the stop command
-function stop_system()
+stop_system()
 {
     # prevent reentry
     if [ $INSTOPSYSTEM -eq 1 ]; then return; fi
     INSTOPSYSTEM=1
     if [ -f ${BUILDDIR}/.toasterui.pid ]; then
-        kill $(< ${BUILDDIR}/.toasterui.pid ) 2>/dev/null
+        kill `cat ${BUILDDIR}/.toasterui.pid` 2>/dev/null
         rm ${BUILDDIR}/.toasterui.pid
     fi
     BBSERVER=0.0.0.0:-1 bitbake -m
@@ -117,12 +118,12 @@ function stop_system()
     INSTOPSYSTEM=0
 }
 
-function check_pidbyfile() {
-    [ -e $1 ] && kill -0 $(< $1) 2>/dev/null
+check_pidbyfile() {
+    [ -e $1 ] && kill -0 `cat $1` 2>/dev/null
 }
 
 
-function notify_chldexit() {
+notify_chldexit() {
     if [ $NOTOASTERUI -eq 0 ]; then
         check_pidbyfile ${BUILDDIR}/.toasterui.pid && return
         stop_system
@@ -130,16 +131,16 @@ function notify_chldexit() {
 }
 
 
-function verify_prereq() {
+verify_prereq() {
     # Verify prerequisites
 
     if ! echo "import django; print (1,) == django.VERSION[0:1] and django.VERSION[1:2][0] in (6,)" | python 2>/dev/null | grep True >/dev/null; then
-        echo -e "This program needs Django 1.6. Please install with\n\npip install django==1.6\n"
+        printf "This program needs Django 1.6. Please install with\n\npip install django==1.6\n"
         return 2
     fi
 
     if ! echo "import south; print reduce(lambda x, y: 2 if x==2 else 0 if x == 0 else y, map(lambda x: 1+cmp(x[1]-x[0],0), zip([0,8,4], map(int,south.__version__.split(\".\"))))) > 0" | python 2>/dev/null | grep True >/dev/null; then
-        echo -e "This program needs South 0.8.4. Please install with\n\npip install south==0.8.4\n"
+        printf "This program needs South 0.8.4. Please install with\n\npip install south==0.8.4\n"
         return 2
     fi
     return 0
@@ -182,42 +183,42 @@ if [ `basename \"$0\"` = `basename \"${SRCFILE}\"` ]; then
     # Start just the web server, point the web browser to the interface, and start any Django services.
 
     if ! verify_prereq; then
-        echo -e "Error: Could not verify that the needed dependencies are installed. Please use virtualenv and pip to install dependencies listed in toaster-requirements.txt" 1>&2
+        echo "Error: Could not verify that the needed dependencies are installed. Please use virtualenv and pip to install dependencies listed in toaster-requirements.txt" 1>&2
         exit 1
     fi
 
     if [ -n "$BUILDDIR" ]; then
-        echo -e "Error: It looks like you sourced oe-init-build-env. Toaster cannot start in build mode from an oe-core build environment.\n You should be starting Toaster from a new terminal window." 1>&2
+        printf "Error: It looks like you sourced oe-init-build-env. Toaster cannot start in build mode from an oe-core build environment.\n You should be starting Toaster from a new terminal window." 1>&2
         exit 1
     fi
 
     # Define a fake builddir where only the pid files are actually created. No real builds will take place here.
     BUILDDIR=/tmp/toaster_$$
     if [ -d "$BUILDDIR" ]; then
-        echo -e "Previous toaster run directory $BUILDDIR found, cowardly refusing to start. Please remove the directory when that toaster instance is over" 2>&1
+        echo "Previous toaster run directory $BUILDDIR found, cowardly refusing to start. Please remove the directory when that toaster instance is over" 2>&1
         exit 1
     fi
 
     mkdir -p "$BUILDDIR"
 
     RUNNING=1
-    function trap_ctrlc() {
+    trap_ctrlc() {
         echo "** Stopping system"
         webserverKillAll
         RUNNING=0
     }
 
-    function do_cleanup() {
+    do_cleanup() {
         find "$BUILDDIR" -type f | xargs rm
         rmdir "$BUILDDIR"
     }
-    function cleanup() {
+    cleanup() {
         if grep -ir error "$BUILDDIR" >/dev/null; then
             if grep -irn "That port is already in use" "$BUILDDIR"; then
                 echo "You can use the \"webport=PORTNUMBER\" parameter to start Toaster on a different port (port $WEB_PORT is already in use)"
                 do_cleanup
             else
-                echo -e "\nErrors found in the Toaster log files present in '$BUILDDIR'. Directory will not be cleaned.\n Please review the errors and notify toaster@yoctoproject.org or submit a bug https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Toaster"
+                printf "\nErrors found in the Toaster log files present in '$BUILDDIR'. Directory will not be cleaned.\n Please review the errors and notify toaster@yoctoproject.org or submit a bug https://bugzilla.yoctoproject.org/enter_bug.cgi?product=Toaster"
             fi
         else
             echo "No errors found, removing the run directory '$BUILDDIR'"
@@ -248,7 +249,7 @@ fi
 
 
 if ! verify_prereq; then
-    echo -e "Error: Could not verify that the needed dependencies are installed. Please use virtualenv and pip to install dependencies listed in toaster-requirements.txt" 1>&2
+    echo "Error: Could not verify that the needed dependencies are installed. Please use virtualenv and pip to install dependencies listed in toaster-requirements.txt" 1>&2
     return 1
 fi
 
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 15/19] toaster: Make toaster script to work in dash
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (13 preceding siblings ...)
  2015-06-10 14:39 ` [PATCH 14/19] toaster: Code cleanup: bashisms Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 16/19] toaster: tables: Remove obsolete field_name for select column Alex DAMIAN
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel

From: Ed Bartosh <ed.bartosh@linux.intel.com>

Made it working in dash. Note, that due to dash limitations
script will not work if sourced.

Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 bin/toaster | 27 ++++++++++++++++++---------
 1 file changed, 18 insertions(+), 9 deletions(-)

diff --git a/bin/toaster b/bin/toaster
index 0fa8b60..411ce2c 100755
--- a/bin/toaster
+++ b/bin/toaster
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 # (c) 2013 Intel Corp.
 
 # This program is free software; you can redistribute it and/or modify
@@ -54,7 +54,7 @@ webserverStartAll()
     fi
 
     retval=0
-    if [ "$TOASTER_MANAGED" '==' '1' ]; then
+    if [ "$TOASTER_MANAGED" '=' '1' ]; then
         python $BBBASEDIR/lib/toaster/manage.py syncdb || retval=1
     else
         python $BBBASEDIR/lib/toaster/manage.py syncdb --noinput || retval=1
@@ -68,7 +68,7 @@ webserverStartAll()
         retval=0
         python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=1
     fi
-    if [ "$TOASTER_MANAGED" '==' '1' ]; then
+    if [ "$TOASTER_MANAGED" = '1' ]; then
         python $BBBASEDIR/lib/toaster/manage.py migrate bldcontrol || retval=1
         python $BBBASEDIR/lib/toaster/manage.py checksettings  --traceback || retval=1
     fi
@@ -148,7 +148,16 @@ verify_prereq() {
 
 
 # read command line parameters
-BBBASEDIR=`dirname ${BASH_SOURCE:-${(%):-%x}}`/..
+if [ -n "$BASH_SOURCE" ] ; then
+    TOASTER=${BASH_SOURCE}
+elif [ -n "$ZSH_NAME" ] ; then
+    TOASTER=${(%):-%x}
+else
+    TOASTER=$0
+fi
+
+BBBASEDIR=`dirname $TOASTER`/..
+
 RUNNING=0
 
 NOTOASTERUI=0
@@ -176,7 +185,7 @@ for param in $*; do
     esac
 done
 
-[ -z "$ZSH_NAME" ] && SRCFILE=${BASH_SOURCE} || SRCFILE=$_
+[ -n "${BASH_SOURCE}" ] && SRCFILE=${BASH_SOURCE} || SRCFILE=$_
 
 if [ `basename \"$0\"` = `basename \"${SRCFILE}\"` ]; then
     # We are called as standalone. We refuse to run in a build environment - we need the interactive mode for that.
@@ -236,7 +245,7 @@ if [ `basename \"$0\"` = `basename \"${SRCFILE}\"` ]; then
         echo "Starting browser..."
         xdg-open http://127.0.0.1:$WEB_PORT/ >/dev/null 2>&1 &
     fi
-    trap trap_ctrlc SIGINT
+    trap trap_ctrlc 2
     echo "Toaster is now running. You can stop it with Ctrl-C"
     while [ $RUNNING -gt 0 ]; do
         python $BBBASEDIR/lib/toaster/manage.py runbuilds 2>&1 | tee -a "$BUILDDIR/toaster.log"
@@ -262,7 +271,7 @@ fi
 
 
 # Determine the action. If specified by arguments, fine, if not, toggle it
-if [ "$1" '==' 'start' ] || [ "$1" '==' 'stop' ]; then
+if [ "$1" = 'start' ] || [ "$1" = 'stop' ]; then
     CMD="$1"
 else
     if [ -z "$BBSERVER" ]; then
@@ -281,13 +290,13 @@ if [ -e $BUILDDIR/bitbake.lock ]; then
     python -c "import fcntl; fcntl.flock(open(\"$BUILDDIR/bitbake.lock\"), fcntl.LOCK_EX|fcntl.LOCK_NB)" 2>/dev/null || lock=0
 fi
 
-if [ ${CMD} '==' 'start' ] && [ $lock -eq 0 ]; then
+if [ ${CMD} = 'start' ] && [ $lock -eq 0 ]; then
     echo "Error: bitbake lock state error. File locks show that the system is on." 1>&2
     echo "Please wait for the current build to finish, stop and then start the system again." 1>&2
     return 3
 fi
 
-if [ ${CMD} '==' 'start' ] && [ -e $BUILDDIR/.toastermain.pid ] && kill -0 `cat $BUILDDIR/.toastermain.pid`; then
+if [ ${CMD} = 'start' ] && [ -e $BUILDDIR/.toastermain.pid ] && kill -0 `cat $BUILDDIR/.toastermain.pid`; then
     echo "Warning: bitbake appears to be dead, but the Toaster web server is running. Something fishy is going on." 1>&2
     echo "Cleaning up the web server to start from a clean slate."
     webserverKillAll
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 16/19] toaster: tables: Remove obsolete field_name for select column
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (14 preceding siblings ...)
  2015-06-10 14:39 ` [PATCH 15/19] toaster: Make toaster script to work in dash Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 17/19] toaster: Add ajax loading spinner Alex DAMIAN
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel; +Cc: Vlad Fulgeanu

From: Vlad Fulgeanu <andrei-vlad.fulgeanu@intel.com>

field_name is no longer used for this kind of column. So no need to specify
it here.

Signed-off-by: Vlad Fulgeanu <andrei-vlad.fulgeanu@intel.com>
---
 lib/toaster/toastergui/tables.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index b85527e..43b81a3 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -227,8 +227,7 @@ class MachinesTable(ToasterTable):
                         help_text="Sets the selected machine as the project machine. You can only have one machine per project",
                         hideable=False,
                         static_data_name="add-del-layers",
-                        static_data_template='{% include "machine_btn.html" %}',
-                        field_name="layer_version__id")
+                        static_data_template='{% include "machine_btn.html" %}')
 
 
 class LayerMachinesTable(MachinesTable):
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 17/19] toaster: Add ajax loading spinner
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (15 preceding siblings ...)
  2015-06-10 14:39 ` [PATCH 16/19] toaster: tables: Remove obsolete field_name for select column Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 18/19] toaster: Add global ajaxError handler Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 19/19] toastergui: prevent contamination of libtoaster context Alex DAMIAN
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

This adds an ajax loading spinner to provide feedback when in-page
loading is happening. It will show after 1.2 seconds of initial loading.

[YOCTO #7790]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 lib/toaster/toastergui/static/css/default.css  | 49 ++++++++++++++++++++++++++
 lib/toaster/toastergui/static/js/libtoaster.js | 22 ++++++++++++
 lib/toaster/toastergui/templates/base.html     |  5 +++
 3 files changed, 76 insertions(+)

diff --git a/lib/toaster/toastergui/static/css/default.css b/lib/toaster/toastergui/static/css/default.css
index 739efbf..115abc4 100644
--- a/lib/toaster/toastergui/static/css/default.css
+++ b/lib/toaster/toastergui/static/css/default.css
@@ -259,3 +259,52 @@ div.add-deps { margin-top: 15px; }
 
 thead .description, .get_description_or_summary { width: 364px; }
 thead .add-del-layers { width: 124px; }
+
+#loading-notification {
+  position: fixed;
+  z-index: 101;
+  top: 3%;
+  left: 40%;
+  right: 40%;
+  #c09853
+  -webkit-box-shadow: 0 0 10px #c09853;
+  -moz-box-shadow: 0 0 10px #c09853;
+  box-shadow: 0 0 10px #c09853;
+}
+
+/* Copied in from newer version of Font-Awesome 4.3.0 */
+.fa-spin {
+  -webkit-animation: fa-spin 2s infinite linear;
+  animation: fa-spin 2s infinite linear;
+  display: inline-block;
+}
+.fa-pulse {
+  -webkit-animation: fa-spin 1s infinite steps(8);
+  animation: fa-spin 1s infinite steps(8);
+  display: inline-block;
+}
+
+@-webkit-keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+
+@keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    -moz-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    -moz-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+/* End copied in from newer version of Font-Awesome 4.3.0 */
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 23755a7..c379833 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -341,6 +341,8 @@ function reload_params(params) {
 /* Things that happen for all pages */
 $(document).ready(function() {
 
+  var ajaxLoadingTimer;
+
   /* If we don't have a console object which might be the case in some
      * browsers, no-op it to avoid undefined errors.
      */
@@ -482,6 +484,26 @@ $(document).ready(function() {
         $('#collapse-warnings').addClass('in');
     }
 
+    /* Show the loading notification if nothing has happend after 1.5
+     * seconds
+     */
+    $(document).bind("ajaxStart", function(){
+      if (ajaxLoadingTimer)
+        window.clearTimeout(ajaxLoadingTimer);
+
+      ajaxLoadingTimer = window.setTimeout(function() {
+        $("#loading-notification").fadeIn();
+      }, 1200);
+    });
+
+    $(document).bind("ajaxStop", function(){
+      if (ajaxLoadingTimer)
+        window.clearTimeout(ajaxLoadingTimer);
+
+      $("#loading-notification").fadeOut();
+    });
+
+
     function check_for_duplicate_ids () {
       /* warn about duplicate element ids */
       var ids = {};
diff --git a/lib/toaster/toastergui/templates/base.html b/lib/toaster/toastergui/templates/base.html
index 6cdc5e8..9f19c03 100644
--- a/lib/toaster/toastergui/templates/base.html
+++ b/lib/toaster/toastergui/templates/base.html
@@ -68,6 +68,11 @@
     </head>
 
 <body style="height: 100%">
+
+  <div id="loading-notification" class="alert lead text-center" style="display:none">
+    Loading <i class="fa-pulse icon-spinner"></i>
+  </div>
+
 <div class="navbar navbar-static-top">
     <div class="navbar-inner">
             <a class="brand logo" href="#"><img src="{% static 'img/logo.png' %}" class="" alt="Yocto logo project"/></a>
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 18/19] toaster: Add global ajaxError handler
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (16 preceding siblings ...)
  2015-06-10 14:39 ` [PATCH 17/19] toaster: Add ajax loading spinner Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  2015-06-10 14:39 ` [PATCH 19/19] toastergui: prevent contamination of libtoaster context Alex DAMIAN
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel

From: Michael Wood <michael.g.wood@intel.com>

If any ajax calls fail and debug is enabled log the error to the console.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 lib/toaster/toastergui/static/js/libtoaster.js | 5 +++++
 lib/toaster/toastergui/static/js/table.js      | 5 -----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index c379833..667aca2 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -503,6 +503,11 @@ $(document).ready(function() {
       $("#loading-notification").fadeOut();
     });
 
+    $(document).ajaxError(function(event, jqxhr, settings, errMsg){
+      console.warn("Problem with xhr call");
+      console.warn(errMsg);
+      console.warn(jqxhr.responseText);
+    });
 
     function check_for_duplicate_ids () {
       /* warn about duplicate element ids */
diff --git a/lib/toaster/toastergui/static/js/table.js b/lib/toaster/toastergui/static/js/table.js
index 45c6184..1072c75 100644
--- a/lib/toaster/toastergui/static/js/table.js
+++ b/lib/toaster/toastergui/static/js/table.js
@@ -53,11 +53,6 @@ function tableInit(ctx){
               tableData: tableData,
               tableParams: tableParams
           }, null, libtoaster.dumpsUrlParams(tableParams));
-        },
-
-        error: function (_data) {
-          console.warn("Call failed");
-          console.warn(_data);
         }
     });
   }
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 19/19] toastergui: prevent contamination of libtoaster context
  2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
                   ` (17 preceding siblings ...)
  2015-06-10 14:39 ` [PATCH 18/19] toaster: Add global ajaxError handler Alex DAMIAN
@ 2015-06-10 14:39 ` Alex DAMIAN
  18 siblings, 0 replies; 20+ messages in thread
From: Alex DAMIAN @ 2015-06-10 14:39 UTC (permalink / raw
  To: bitbake-devel

From: Alexandru DAMIAN <alexandru.damian@intel.com>

This patch modifies the New Build button to use a local
copy of the default libtoaster project context in order
to prevent page contamination when a different project is
selected in the drop-down menu.

Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
 lib/toaster/toastergui/static/js/base.js | 38 +++++++++++++++-----------------
 1 file changed, 18 insertions(+), 20 deletions(-)

diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js
index 06d0676..d079f23 100644
--- a/lib/toaster/toastergui/static/js/base.js
+++ b/lib/toaster/toastergui/static/js/base.js
@@ -11,7 +11,8 @@ function basePageInit(ctx) {
     return;
   }
 
-  var currentProjectId = libtoaster.ctx.projectId;
+  var selectedProject = libtoaster.ctx;
+  var selectedTarget;
 
   /* Hide the change project icon when there is only one project */
   if (ctx.numProjects === 1) {
@@ -27,18 +28,16 @@ function basePageInit(ctx) {
   var newBuildProjectSaveBtn = $("#new-build-button #save-project-button");
 
 
-  var selectedTarget;
-
   _checkProjectBuildable();
   _setupNewBuildButton();
 
 
   function _checkProjectBuildable() {
-    if (libtoaster.ctx.projectId === undefined) {
+    if (selectedProject.projectId === undefined) {
       return;
     }
 
-    libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl,
+    libtoaster.getProjectInfo(selectedProject.projectPageUrl,
       function (data) {
         if (data.machine.name === undefined || data.layers.length === 0) {
           /* we can't build anything with out a machine and some layers */
@@ -52,9 +51,14 @@ function basePageInit(ctx) {
           newBuildTargetInput.prop("disabled", false);
           newBuildTargetBuildBtn.prop("disabled", false);
 
-          libtoaster.makeTypeahead(newBuildTargetInput, libtoaster.ctx.projectTargetsUrl, { format: "json" }, function (item) {
+          libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.projectTargetsUrl, { format: "json" }, function (item) {
             /* successfully selected a target */
-            selectedTarget = item;
+            selectedProject.projectPageUrl = item.projectPageUrl;
+            selectedProject.projectName = item.name;
+            selectedProject.projectId = item.id;
+            selectedProject.projectBuildsUrl = item.projectBuildsUrl;
+
+
           });
 
         }
@@ -63,18 +67,17 @@ function basePageInit(ctx) {
 
   function _setupNewBuildButton() {
     /* Setup New build button */
-    var selectedProject;
 
     /* If we don't have a current project then present the set project
      * form.
      */
-    if (libtoaster.ctx.projectId === undefined) {
+    if (selectedProject.projectId === undefined) {
       $('#change-project-form').show();
       $('#project .icon-pencil').hide();
     }
 
 
-    libtoaster.makeTypeahead(newBuildProjectInput, libtoaster.ctx.projectsUrl, { format : "json" }, function (item) {
+    libtoaster.makeTypeahead(newBuildProjectInput, selectedProject.projectsUrl, { format : "json" }, function (item) {
       /* successfully selected a project */
       newBuildProjectSaveBtn.removeAttr("disabled");
       selectedProject = item;
@@ -107,23 +110,18 @@ function basePageInit(ctx) {
         selectedTarget = { name: newBuildTargetInput.val() };
       }
       /* fire and forget */
-      libtoaster.startABuild(libtoaster.ctx.projectBuildsUrl, libtoaster.ctx.projectId, selectedTarget.name, null, null);
-      window.location.replace(libtoaster.ctx.projectPageUrl);
+      libtoaster.startABuild(selectedProject.projectBuildsUrl, selectedProject.projectId, selectedTarget.name, null, null);
+      window.location.replace(selectedProject.projectPageUrl);
     });
 
     newBuildProjectSaveBtn.click(function () {
-      libtoaster.ctx.projectId = selectedProject.pk;
+      selectedProject.projectId = selectedProject.pk;
       /* Update the typeahead project_id paramater */
       _checkProjectBuildable();
 
       /* we set the effective context of the page to the currently selected project */
       /* TBD: do we override even if we already have a context project ?? */
       /* TODO: replace global library context with references to the "selected" project */
-      libtoaster.ctx.projectPageUrl = selectedProject.projectPageUrl;
-      libtoaster.ctx.projectName = selectedProject.name;
-      libtoaster.ctx.projectId = selectedProject.id;
-
-      ctx.projectBuildsUrl = selectedProject.projectBuildsUrl;
 
       /* we can create a target typeahead only after we have a project selected */
       newBuildTargetInput.prop("disabled", false);
@@ -137,8 +135,8 @@ function basePageInit(ctx) {
       newBuildTargetInput.val("");
 
       /* set up new form aspect */
-      $("#new-build-button #project a").text(selectedProject.name).attr('href', libtoaster.ctx.projectPageUrl);
-      $("#new-build-button .alert a").attr('href', libtoaster.ctx.projectPageUrl);
+      $("#new-build-button #project a").text(selectedProject.name).attr('href', selectedProject.projectPageUrl);
+      $("#new-build-button .alert a").attr('href', selectedProject.projectPageUrl);
       $("#project .icon-pencil").show();
 
       $("#change-project-form").slideUp({ 'complete' : function () {
-- 
1.9.1



^ permalink raw reply related	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2015-06-10 14:40 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-06-10 14:39 [PATCH 00/19] Toaster refactoring and bugfixing patchset Alex DAMIAN
2015-06-10 14:38 ` [PATCH 01/19] toaster: move project data typeahead to the REST API Alex DAMIAN
2015-06-10 14:38 ` [PATCH 02/19] convert all project-based view to JSON APIs Alex DAMIAN
2015-06-10 14:38 ` [PATCH 03/19] toaster: toastertables REST refactoring Alex DAMIAN
2015-06-10 14:38 ` [PATCH 04/19] toastergui: use defaults on xhr_datatypeahead parameters Alex DAMIAN
2015-06-10 14:38 ` [PATCH 05/19] toastergui: remove xhr_projectedit and xhr_projectinfo URLs Alex DAMIAN
2015-06-10 14:38 ` [PATCH 06/19] toaster: toaster table add raw data Alex DAMIAN
2015-06-10 14:38 ` [PATCH 07/19] toaster: ToasterTables add computational fields Alex DAMIAN
2015-06-10 14:38 ` [PATCH 08/19] toaster: toastertables raise errors Alex DAMIAN
2015-06-10 14:38 ` [PATCH 09/19] toaster: add class template view for single-object pages Alex DAMIAN
2015-06-10 14:38 ` [PATCH 10/19] toaster: eliminate duplicate querysets Alex DAMIAN
2015-06-10 14:38 ` [PATCH 11/19] toastergui: remove xhr_datatypeahead and xhr_XXXbuild Alex DAMIAN
2015-06-10 14:39 ` [PATCH 12/19] toastergui: remove xhr_datatypeahaed layerdeps call Alex DAMIAN
2015-06-10 14:39 ` [PATCH 13/19] toaster: fixes after refactoring Alex DAMIAN
2015-06-10 14:39 ` [PATCH 14/19] toaster: Code cleanup: bashisms Alex DAMIAN
2015-06-10 14:39 ` [PATCH 15/19] toaster: Make toaster script to work in dash Alex DAMIAN
2015-06-10 14:39 ` [PATCH 16/19] toaster: tables: Remove obsolete field_name for select column Alex DAMIAN
2015-06-10 14:39 ` [PATCH 17/19] toaster: Add ajax loading spinner Alex DAMIAN
2015-06-10 14:39 ` [PATCH 18/19] toaster: Add global ajaxError handler Alex DAMIAN
2015-06-10 14:39 ` [PATCH 19/19] toastergui: prevent contamination of libtoaster context Alex DAMIAN

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.