Skip to contentSkip to conversation

Toaster refactoring and bugfixing patchset

PATCH New Alex DAMIAN 1 participant 20 messages Jun 10, 2015

lore
Share & tools
lore
#0 lore raw

cover Toaster refactoring and bugfixing patchset

From: Alexandru DAMIAN <[email protected]>

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(-)
Signature · 1 line
1.9.1
#1 lore raw

PATCH 1/19 toaster: move project data typeahead to the REST API

From: Alexandru DAMIAN <[email protected]>

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.

---
 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(-)
lib/toaster/toastergui/static/js/base.js +34 -17
index ccc23e0..9424b6c 100644
@@ -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();
     });
lib/toaster/toastergui/static/js/importlayer.js +3 -3
index 875cc34..beb2ede 100644
@@ -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) {
lib/toaster/toastergui/static/js/layerdetails.js +1 -1
index 3746ea2..8e14b8f 100644
@@ -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");
lib/toaster/toastergui/static/js/libtoaster.js +6 -6
index 99e1f03..72fb0a93 100644
@@ -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") {
lib/toaster/toastergui/templates/base.html +15 -9
index 230dee4..e10dc11 100644
@@ -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>
lib/toaster/toastergui/templates/basetable_bottom.html +1 -1
index 4c28cae..ce023f5 100644
@@ -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}};
     }
lib/toaster/toastergui/templates/detail_pagination_bottom.html +1 -1
index 434facb..f40c21d 100644
@@ -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}};
     }
lib/toaster/toastergui/templatetags/projecttags.py +2 -2
index 8028ae0..e79a4e5 100644
@@ -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):
lib/toaster/toastergui/urls.py +0 -3
index 7a1132f..4e328da 100644
@@ -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'),
lib/toaster/toastergui/views.py +56 -33

175 lines not on this page · raw message

Signature · 1 line
1.9.1
#2 lore raw

PATCH 2/19 convert all project-based view to JSON APIs

From: Alexandru DAMIAN <[email protected]>

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.

---
 lib/toaster/toastergui/views.py | 164 ++++++++++++++++++++++++----------------
 1 file changed, 99 insertions(+), 65 deletions(-)
lib/toaster/toastergui/views.py +99 -65
index d4a9b4c..f2626f8 100755
@@ -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 {}
Signature · 1 line
1.9.1
#3 lore raw

PATCH 3/19 toaster: toastertables REST refactoring

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 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(-)
lib/toaster/toastergui/static/js/table.js +3 -2
index 2e35e38..7588a4a 100644
@@ -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) {
lib/toaster/toastergui/tables.py +56 -28

137 lines not on this page · raw message

lib/toaster/toastergui/templates/layerdetails.html +1 -1
index 259a59e..c27d259 100644
@@ -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 : {
lib/toaster/toastergui/templates/toastertable-simple.html +1 -1
index 23a7526..ea7b38e 100644
@@ -10,7 +10,7 @@
 
     var ctx = {
       tableName : "{{table_name}}",
-      url : "{{ xhr_table_url }}",
+      url : "{{ xhr_table_url }}?format=json",
       title : "{{title}}",
       projectLayers : {{projectlayers|json}},
     };
lib/toaster/toastergui/templates/toastertable.html +1 -1
index 5c79ab4..c7c7a84 100644
@@ -10,7 +10,7 @@
 
     var ctx = {
       tableName : "{{table_name}}",
-      url : "{{ xhr_table_url }}",
+      url : "{{ xhr_table_url }}?format=json",
       title : "{{title}}",
       projectLayers : {{projectlayers|json}},
     };
lib/toaster/toastergui/urls.py +21 -9
index 4e328da..e10e0bb 100644
@@ -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'),
lib/toaster/toastergui/widgets.py +19 -28

68 lines not on this page · raw message

Signature · 1 line
1.9.1
#4 lore raw

PATCH 4/19 toastergui: use defaults on xhr_datatypeahead parameters

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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

---
 lib/toaster/toastergui/views.py | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
lib/toaster/toastergui/views.py +10 -10
index f2626f8..678e356 100755
@@ -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")
Signature · 1 line
1.9.1
#5 lore raw

PATCH 5/19 toastergui: remove xhr_projectedit and xhr_projectinfo URLs

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 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(-)
lib/toaster/contrib/tts/urllist.py +0 -3
index a7d6d6e..433ac9f 100644
@@ -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/',
lib/toaster/toastergui/static/js/base.js +1 -2
index 9424b6c..9c8d01e 100644
@@ -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;
 
lib/toaster/toastergui/static/js/libtoaster.js +4 -4
index 72fb0a93..8791029 100644
@@ -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) {
lib/toaster/toastergui/static/js/projectapp.js +61 -12

112 lines not on this page · raw message

lib/toaster/toastergui/templates/base.html +0 -3
index e10dc11..5d51bc3 100644
@@ -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}}";
 
lib/toaster/toastergui/templates/project.html +1 -1
index 933da4f..e598631 100644
@@ -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 %}";
lib/toaster/toastergui/tests.py +0 -1
index 0f10020..c92c5fe 100644
@@ -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):
lib/toaster/toastergui/urls.py +0 -2
index e10e0bb..d527be0 100644
@@ -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'),
lib/toaster/toastergui/views.py +41 -69

144 lines not on this page · raw message

Signature · 1 line
1.9.1
#6 lore raw

PATCH 6/19 toaster: toaster table add raw data

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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).

---
 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(-)
lib/toaster/toastergui/static/js/table.js +13 -1
index 7588a4a..80e9ec2 100644
@@ -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]);
         }
lib/toaster/toastergui/views.py +30 -1
index 91c4fa2..280159a 100755
@@ -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)
lib/toaster/toastergui/widgets.py +11 -5
index 4347a3f..82b7514 100644
@@ -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
Signature · 1 line
1.9.1
#7 lore raw

PATCH 7/19 toaster: ToasterTables add computational fields

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 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(-)
lib/toaster/toastergui/static/js/table.js +13 -0
index 80e9ec2..45c6184 100644
@@ -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);
 
lib/toaster/toastergui/tables.py +6 -2
index b75e565..e540b91 100644
@@ -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 """
 
lib/toaster/toastergui/views.py +3 -0
index 280159a..c25c512 100755
@@ -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)))
 
lib/toaster/toastergui/widgets.py +16 -3
index 82b7514..407a0fb 100644
@@ -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)
 
Signature · 1 line
1.9.1
#8 lore raw

PATCH 8/19 toaster: toastertables raise errors

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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

---
 lib/toaster/toastergui/widgets.py | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)
lib/toaster/toastergui/widgets.py +7 -5
index 407a0fb..3d3c1d1 100644
@@ -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)
 
Signature · 1 line
1.9.1
#9 lore raw

PATCH 9/19 toaster: add class template view for single-object pages

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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

---
 lib/toaster/toastergui/tables.py  | 12 ++++++++++--
 lib/toaster/toastergui/widgets.py | 26 ++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 2 deletions(-)
lib/toaster/toastergui/tables.py +10 -2
index e540b91..e03aa76 100644
@@ -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"""
 
lib/toaster/toastergui/widgets.py +26 -0
index 3d3c1d1..8bc3d7f 100644
@@ -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)
Signature · 1 line
1.9.1
#10 lore raw

PATCH 10/19 toaster: eliminate duplicate querysets

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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)

---
 lib/toaster/orm/models.py | 41 +++--------------------------------------
 1 file changed, 3 insertions(+), 38 deletions(-)
lib/toaster/orm/models.py +3 -38
index 198c6f5..8e73ee1 100644
@@ -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:
Signature · 1 line
1.9.1
#11 lore raw

PATCH 11/19 toastergui: remove xhr_datatypeahead and xhr_XXXbuild

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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).

---
 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(-)
lib/toaster/contrib/tts/urllist.py +0 -2
index 433ac9f..0226334 100644
@@ -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',
lib/toaster/toastergui/static/js/base.js +3 -3
index 9c8d01e..747442c 100644
@@ -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;
 	    });
lib/toaster/toastergui/static/js/importlayer.js +3 -3
index beb2ede..e1fc5c5 100644
@@ -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) {
lib/toaster/toastergui/static/js/layerdetails.js +2 -2
index 8e14b8f..ab78182 100644
@@ -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);
     });
   });
lib/toaster/toastergui/static/js/libtoaster.js +4 -4
index 8791029..2a9a790 100644
@@ -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");
lib/toaster/toastergui/templates/base.html +0 -2
index 5d51bc3..7fee26e 100644
@@ -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,
lib/toaster/toastergui/templates/layerdetails.html +2 -2
index c27d259..ef1795c 100644
@@ -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 : {
lib/toaster/toastergui/templates/managed_mrb_section.html +1 -1
index c93c2af..47e64ea 100644
@@ -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 %}
 
lib/toaster/toastergui/templates/project.html +36 -16

125 lines not on this page · raw message

lib/toaster/toastergui/templates/runagain.html +1 -1
index a78c0dd..b4ba5fb 100644
@@ -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}})
lib/toaster/toastergui/templatetags/projecttags.py +1 -1
index e79a4e5..75f2261 100644
@@ -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):
lib/toaster/toastergui/tests.py +3 -3
index c92c5fe..77e80fe 100644
@@ -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'))
 
lib/toaster/toastergui/urls.py +3 -5
index d527be0..f1b74cd 100644
@@ -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'),
 
lib/toaster/toastergui/views.py +56 -152

276 lines not on this page · raw message

lib/toaster/toastergui/widgets.py +3 -3
index 8bc3d7f..f5a1b3e 100644
@@ -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
lib/toaster/toastermain/settings.py +1 -1
index 225138b..3c7cb3b 100644
@@ -223,7 +223,7 @@ CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
                'LOCATION': '/tmp/django-default-cache',
-               'TIMEOUT': 5,
+               'TIMEOUT': 1,
             }
           }
 
Signature · 1 line
1.9.1
#12 lore raw

PATCH 12/19 toastergui: remove xhr_datatypeahaed layerdeps call

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 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(-)
lib/toaster/toastergui/static/js/importlayer.js +4 -4
index e1fc5c5..e9d7ae8 100644
@@ -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 */
lib/toaster/toastergui/static/js/layerdetails.js +1 -1
index ab78182..0accd97 100644
@@ -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"});
 
lib/toaster/toastergui/static/js/libtoaster.js +4 -5
index 2a9a790..b1038cf 100644
@@ -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 */
lib/toaster/toastergui/static/js/projectapp.js +46 -15

142 lines not on this page · raw message

lib/toaster/toastergui/tables.py +17 -6
index e03aa76..003b924 100644
@@ -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 """
lib/toaster/toastergui/templates/importlayer.html +0 -1
index af8f4f9..498a204 100644
@@ -14,7 +14,6 @@
                   <script>
                     $(document).ready(function (){
                       var ctx = {
-                        layerDetailsUrl : "{% url 'base_layerdetails' project.id %}",
                         xhrImportLayerUrl : "{% url 'xhr_importlayer' %}",
                       };
 
lib/toaster/toastergui/templates/layerdetails.html +0 -1
index ef1795c..7d81b14 100644
@@ -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}}",
lib/toaster/toastergui/templates/project.html +0 -1
index 63fbc40..7225363 100644
@@ -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}};
lib/toaster/toastergui/urls.py +0 -3
index f1b74cd..5a79f88 100644
@@ -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"),
lib/toaster/toastergui/views.py +1 -10
index 5221f1f..b2b263b 100755
@@ -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":
Signature · 1 line
1.9.1
#13 lore raw

PATCH 13/19 toaster: fixes after refactoring

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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

---
 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(-)
lib/toaster/orm/models.py +2 -3
index 8e73ee1..8819450 100644
@@ -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
lib/toaster/toastergui/static/js/base.js +61 -43

198 lines not on this page · raw message

lib/toaster/toastergui/static/js/libtoaster.js +1 -1
index b1038cf..23755a7 100644
@@ -25,7 +25,7 @@ var libtoaster = (function (){
               return;
             }
 
-            return process (data.list);
+            return process (data.rows);
           });
         },
         updater: function(item) {
lib/toaster/toastergui/static/js/projectapp.js +16 -8
index a915278..44e244d 100644
@@ -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;
lib/toaster/toastergui/tables.py +9 -1
index 003b924..b85527e 100644
@@ -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())
lib/toaster/toastergui/templates/base.html +5 -2
index 7fee26e..6cdc5e8 100644
@@ -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,
lib/toaster/toastergui/templates/project.html +2 -1
index 7225363..bca703a 100644
@@ -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}};
lib/toaster/toastergui/urls.py +8 -0
index 5a79f88..bd3eb40 100644
@@ -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'),
lib/toaster/toastergui/views.py +12 -5
index b2b263b..4dac62c 100755
@@ -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 {}
Signature · 1 line
1.9.1
#14 lore raw

PATCH 14/19 toaster: Code cleanup: bashisms

In reply to Alex DAMIAN

From: Ed Bartosh <[email protected]>

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

---
 bin/toaster | 55 ++++++++++++++++++++++++++++---------------------------
 1 file changed, 28 insertions(+), 27 deletions(-)
bin/toaster +28 -27
index ee19937..0fa8b60 100755
@@ -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 [email protected] 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 [email protected] 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
 
Signature · 1 line
1.9.1
#15 lore raw

PATCH 15/19 toaster: Make toaster script to work in dash

In reply to Alex DAMIAN

From: Ed Bartosh <[email protected]>

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

---
 bin/toaster | 27 ++++++++++++++++++---------
 1 file changed, 18 insertions(+), 9 deletions(-)
bin/toaster +18 -9
index 0fa8b60..411ce2c 100755
@@ -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
Signature · 1 line
1.9.1
#16 lore raw

PATCH 16/19 toaster: tables: Remove obsolete field_name for select column

In reply to Alex DAMIAN

From: Vlad Fulgeanu <[email protected]>

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

---
 lib/toaster/toastergui/tables.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
lib/toaster/toastergui/tables.py +1 -2
index b85527e..43b81a3 100644
@@ -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):
Signature · 1 line
1.9.1
#17 lore raw

PATCH 17/19 toaster: Add ajax loading spinner

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

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]

---
 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(+)
lib/toaster/toastergui/static/css/default.css +49 -0
index 739efbf..115abc4 100644
@@ -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 */
lib/toaster/toastergui/static/js/libtoaster.js +22 -0
index 23755a7..c379833 100644
@@ -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 = {};
lib/toaster/toastergui/templates/base.html +5 -0
index 6cdc5e8..9f19c03 100644
@@ -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>
Signature · 1 line
1.9.1
#18 lore raw

PATCH 18/19 toaster: Add global ajaxError handler

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

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

---
 lib/toaster/toastergui/static/js/libtoaster.js | 5 +++++
 lib/toaster/toastergui/static/js/table.js      | 5 -----
 2 files changed, 5 insertions(+), 5 deletions(-)
lib/toaster/toastergui/static/js/libtoaster.js +5 -0
index c379833..667aca2 100644
@@ -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 */
lib/toaster/toastergui/static/js/table.js +0 -5
index 45c6184..1072c75 100644
@@ -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);
         }
     });
   }
Signature · 1 line
1.9.1
#19 lore raw

PATCH 19/19 toastergui: prevent contamination of libtoaster context

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 lib/toaster/toastergui/static/js/base.js | 38 +++++++++++++++-----------------
 1 file changed, 18 insertions(+), 20 deletions(-)
lib/toaster/toastergui/static/js/base.js +18 -20
index 06d0676..d079f23 100644
@@ -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 () {
Signature · 1 line
1.9.1