Skip to contentSkip to conversation

please pull toaster patchset

PATCH New Alex DAMIAN 1 participant 15 messages Nov 27, 2014

lore
Share & tools
lore
#0 lore raw

cover please pull toaster patchset

From: Alexandru DAMIAN <[email protected]>

Hi,

This is a toaster patchset consisting of new features (New Project page), and bug fixes in toasterui data gathering, better layer identification, ability to use http_proxy to fetch data, and other fixes.

This patch has been reviewed on "toaster/master" poky contrib.

Can you please pull at your convenience ?

Cheers,
Alex

The following changes since commit 27023ce2d264ce05008ef9af33982b054c6a87b5:

toaster: do not show target if target name is empty (2014-11-20 15:43:57 +0000)

are available in the git repository at:

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

Alexandru DAMIAN (8):
toaster: use http proxies to fetch data
toastergui: update layer search criteria
toastergui: do not show project info in interactive mode
toaster: display Toaster exceptions and other fixes
toasterui: fix layer identification for managed builds
toaster: fix loadconf path calculation
toastergui: new project page
toasterui: Compatibility patch for daisy and dizzy

Belen Barros Pena (5):
toaster: fix errors and warnings display
toaster: make 'latest builds' section consistent across pages
toaster: fix padding of build notifications
toaster: release name consistency
toaster: remove the word 'project' from layers and machine

Michael Wood (1):
toaster: base Only show New Build button when there are > 0 projects

 lib/bb/ui/buildinfohelper.py                       |  96 ++++--
 lib/bb/ui/toasterui.py                             |   8 +-
 .../management/commands/checksettings.py           |   8 +
 .../bldcontrol/management/commands/loadconf.py     |  42 +--
 lib/toaster/bldcontrol/tests.py                    |  19 ++
 .../0018_auto__add_field_layer_version_project.py  | 331 +++++++++++++++++++++
 lib/toaster/orm/models.py                          |  58 +++-
 lib/toaster/toastergui/static/css/default.css      |   4 +
 lib/toaster/toastergui/static/js/base.js           |  13 +-
 lib/toaster/toastergui/static/js/libtoaster.js     |   7 +
 lib/toaster/toastergui/templates/base.html         |   5 +-
 .../toastergui/templates/builddashboard.html       |  35 +++
 lib/toaster/toastergui/templates/layers.html       |   9 +-
 lib/toaster/toastergui/templates/mrb_section.html  |  25 +-
 lib/toaster/toastergui/templates/newproject.html   | 116 ++++++--
 lib/toaster/toastergui/templates/project.html      |  28 +-
 lib/toaster/toastergui/templatetags/projecttags.py |   8 +
 lib/toaster/toastergui/views.py                    |  70 +++--
 18 files changed, 736 insertions(+), 146 deletions(-)
 create mode 100644 lib/toaster/orm/migrations/0018_auto__add_field_layer_version_project.py
Signature · 1 line
1.9.1
#1 lore raw

PATCH 1/14 toaster: use http proxies to fetch data

From: Alexandru DAMIAN <[email protected]>

Under some network configurations http proxies are used for Internet access. This patch makes Toaster obey the http_proxy environment variable when fetching information from layer indexes.

---
 lib/toaster/orm/models.py | 45 ++++++++++++++++++++++++++++++++-------------
 1 file changed, 32 insertions(+), 13 deletions(-)
lib/toaster/orm/models.py +32 -13
index c90e047..99cc695 100644
@@ -599,24 +599,41 @@ class LayerIndexLayerSource(LayerSource):
         assert self.apiurl is not None
         from django.db import IntegrityError
 
+        import httplib, urlparse, json
+        import os
+        proxy_settings = os.environ.get("http_proxy", None)
+
         def _get_json_response(apiurl = self.apiurl):
-            import httplib, urlparse, json
-            parsedurl = urlparse.urlparse(apiurl)
-            try:
-                (host, port) = parsedurl.netloc.split(":")
-            except ValueError:
-                host = parsedurl.netloc
-                port = None
+            conn = None
+            _parsedurl = urlparse.urlparse(apiurl)
+            path = _parsedurl.path
+            query = _parsedurl.query
+            def parse_url(url):
+                parsedurl = urlparse.urlparse(url)
+                try:
+                    (host, port) = parsedurl.netloc.split(":")
+                except ValueError:
+                    host = parsedurl.netloc
+                    port = None
+
+                if port is None:
+                    port = 80
+                else:
+                    port = int(port)
+                return (host, port)
 
-            if port is None:
-                port = 80
+            if proxy_settings is None:
+                host, port = parse_url(apiurl)
+                conn = httplib.HTTPConnection(host, port)
+                conn.request("GET", path + "?" + query)
             else:
-                port = int(port)
-            conn = httplib.HTTPConnection(host, port)
-            conn.request("GET", parsedurl.path + "?" + parsedurl.query)
+                host, port = parse_url(proxy_settings)
+                conn = httplib.HTTPConnection(host, port)
+                conn.request("GET", apiurl)
+
             r = conn.getresponse()
             if r.status != 200:
-                raise Exception("Failed to read " + parsedurl.path + ": %d %s" % (r.status, r.reason))
+                raise Exception("Failed to read " + path + ": %d %s" % (r.status, r.reason))
             return json.loads(r.read())
 
         # verify we can get the basic api
@@ -624,6 +641,8 @@ class LayerIndexLayerSource(LayerSource):
             apilinks = _get_json_response()
         except Exception as e:
             import traceback
+            if proxy_settings is not None:
+                print "EE: Using proxy ", proxy_settings
             print "EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e))
             return
 
Signature · 1 line
1.9.1
#2 lore raw

PATCH 2/14 toaster: base Only show New Build button when there are > 0 projects

From: Michael Wood <[email protected]>

Only show new build button if we have defined at least one project as we can't select a project to build against if there are no projects created yet.

---
 lib/toaster/toastergui/static/js/base.js   | 13 +++++++++----
 lib/toaster/toastergui/templates/base.html |  1 +
 2 files changed, 10 insertions(+), 4 deletions(-)
lib/toaster/toastergui/static/js/base.js +9 -4
index 864130d..fac59e6 100644
@@ -3,10 +3,12 @@
 function basePageInit (ctx) {
 
   var newBuildButton = $("#new-build-button");
-  /* Hide the button if we're on the project,newproject or importlyaer page */
-  if (ctx.currentUrl.search('newproject|project/\\d/$|importlayer/$') > 0){
-    newBuildButton.hide();
-    return;
+  /* Hide the button if we're on the project,newproject or importlyaer page
+   * or if there are no projects yet defined
+   */
+  if (ctx.numProjects == 0 || ctx.currentUrl.search('newproject|project/\\d/$|importlayer/$') > 0){
+      newBuildButton.hide();
+      return;
   }
 
 
@@ -17,6 +19,9 @@ function basePageInit (ctx) {
 
 
   function _checkProjectBuildable(){
+    if (ctx.projectId == undefined)
+      return;
+
     libtoaster.getProjectInfo(ctx.projectInfoUrl, ctx.projectId,
       function(data){
         if (data.machine.name == undefined || data.layers.length == 0) {
lib/toaster/toastergui/templates/base.html +1 -0
index 734d2ad..f457b91 100644
@@ -31,6 +31,7 @@
     ctx.projectBuildUrl = "{% url 'xhr_build' %}";
     ctx.projectPageUrl = "{% url 'project' %}";
     ctx.projectInfoUrl = "{% url 'xhr_projectinfo' %}";
+    ctx.numProjects = {{projects|length}};
     {% if project %}
       ctx.projectId = {{project.id}};
     {% endif %}
Signature · 1 line
1.9.1
#3 lore raw

PATCH 3/14 toastergui: update layer search criteria

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

In order to accomodate the designs around imported layer, we add a "project" field for in the layer versions.

The field must be set if and only if the layer is manually imported in the project. This will prevent information leakage between different projects.

The views have been updated to perform relevant layer queries in a single location.

---
 .../0018_auto__add_field_layer_version_project.py  | 331 +++++++++++++++++++++
 lib/toaster/orm/models.py                          |   4 +-
 lib/toaster/toastergui/views.py                    |  59 ++--
 3 files changed, 371 insertions(+), 23 deletions(-)
 create mode 100644 lib/toaster/orm/migrations/0018_auto__add_field_layer_version_project.py
lib/toaster/orm/migrations/0018_auto__add_field_layer_version_project.py new file +331 -0
new file mode 100644
index 0000000..7284bb8
@@ -0,0 +1,331 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'Layer_Version.project'
+        db.add_column(u'orm_layer_version', 'project',
+                      self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['orm.Project'], null=True),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'Layer_Version.project'
+        db.delete_column(u'orm_layer_version', 'project_id')
+
+
+    models = {
+        u'orm.bitbakeversion': {
+            'Meta': {'object_name': 'BitbakeVersion'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'giturl': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.branch': {
+            'Meta': {'unique_together': "(('layer_source', 'name'), ('layer_source', 'up_id'))", 'object_name': 'Branch'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'True', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.build': {
+            'Meta': {'object_name': 'Build'},
+            'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
+            'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+            'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'errors_no': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']", 'null': 'True'}),
+            'started_on': ('django.db.models.fields.DateTimeField', [], {}),
+            'timespent': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'warnings_no': ('django.db.models.fields.IntegerField', [], {'default': '0'})
+        },
+        u'orm.helptext': {
+            'Meta': {'object_name': 'HelpText'},
+            'area': ('django.db.models.fields.IntegerField', [], {}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'helptext_build'", 'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        u'orm.layer': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'), ('layer_source', 'name'))", 'object_name': 'Layer'},
+            'description': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_index_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'local_path': ('django.db.models.fields.FilePathField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'summary': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'vcs_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_file_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_tree_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'})
+        },
+        u'orm.layer_version': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Layer_Version'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'layer_version_build'", 'null': 'True', 'to': u"orm['orm.Build']"}),
+            'commit': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'layer_version_layer'", 'to': u"orm['orm.Layer']"}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Project']", 'null': 'True'}),
+            'up_branch': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Branch']", 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.layersource': {
+            'Meta': {'unique_together': "(('sourcetype', 'apiurl'),)", 'object_name': 'LayerSource'},
+            'apiurl': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '63'}),
+            'sourcetype': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.layerversiondependency': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'LayerVersionDependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependees'", 'to': u"orm['orm.Layer_Version']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependencies'", 'to': u"orm['orm.Layer_Version']"}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.logmessage': {
+            'Meta': {'object_name': 'LogMessage'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'lineno': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
+            'pathname': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Task']", 'null': 'True', 'blank': 'True'})
+        },
+        u'orm.machine': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Machine'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.package': {
+            'Meta': {'object_name': 'Package'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'installed_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'installed_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Recipe']", 'null': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.package_dependency': {
+            'Meta': {'object_name': 'Package_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_target'", 'to': u"orm['orm.Package']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_source'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']", 'null': 'True'})
+        },
+        u'orm.package_file': {
+            'Meta': {'object_name': 'Package_File'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildfilelist_package'", 'to': u"orm['orm.Package']"}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.project': {
+            'Meta': {'object_name': 'Project'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+        },
+        u'orm.projectlayer': {
+            'Meta': {'unique_together': "(('project', 'layercommit'),)", 'object_name': 'ProjectLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layercommit': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']", 'null': 'True'}),
+            'optional': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"})
+        },
+        u'orm.projecttarget': {
+            'Meta': {'object_name': 'ProjectTarget'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+        },
+        u'orm.projectvariable': {
+            'Meta': {'object_name': 'ProjectVariable'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.recipe': {
+            'Meta': {'unique_together': "(('layer_version', 'file_path'),)", 'object_name': 'Recipe'},
+            'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'file_path': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'recipe_layer_version'", 'to': u"orm['orm.Layer_Version']"}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.recipe_dependency': {
+            'Meta': {'object_name': 'Recipe_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_depends'", 'to': u"orm['orm.Recipe']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_recipe'", 'to': u"orm['orm.Recipe']"})
+        },
+        u'orm.release': {
+            'Meta': {'object_name': 'Release'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
+            'branch_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}),
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'helptext': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.releasedefaultlayer': {
+            'Meta': {'object_name': 'ReleaseDefaultLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.releaselayersourcepriority': {
+            'Meta': {'unique_together': "(('release', 'layer_source'),)", 'object_name': 'ReleaseLayerSourcePriority'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.LayerSource']"}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.target': {
+            'Meta': {'object_name': 'Target'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'license_manifest_path': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'orm.target_file': {
+            'Meta': {'object_name': 'Target_File'},
+            'directory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'directory_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inodetype': ('django.db.models.fields.IntegerField', [], {}),
+            'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+            'size': ('django.db.models.fields.IntegerField', [], {}),
+            'sym_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'symlink_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_image_file': {
+            'Meta': {'object_name': 'Target_Image_File'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '254'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_installed_package': {
+            'Meta': {'object_name': 'Target_Installed_Package'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildtargetlist_package'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.task': {
+            'Meta': {'ordering': "('order', 'recipe')", 'unique_together': "(('build', 'recipe', 'task_name'),)", 'object_name': 'Task'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_build'", 'to': u"orm['orm.Build']"}),
+            'cpu_usage': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
+            'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'logfile': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
+            'path_to_sstate_obj': ('django.db.models.fields.FilePathField', [], {'max_length': '500', 'blank': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'build_recipe'", 'to': u"orm['orm.Recipe']"}),
+            'script_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'source_url': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'sstate_checksum': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'sstate_result': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'task_executed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'task_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'work_directory': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        u'orm.task_dependency': {
+            'Meta': {'object_name': 'Task_Dependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_depends'", 'to': u"orm['orm.Task']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_task'", 'to': u"orm['orm.Task']"})
+        },
+        u'orm.toastersetting': {
+            'Meta': {'object_name': 'ToasterSetting'},
+            'helptext': ('django.db.models.fields.TextField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '63'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        u'orm.variable': {
+            'Meta': {'object_name': 'Variable'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variable_build'", 'to': u"orm['orm.Build']"}),
+            'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'human_readable_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'variable_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'variable_value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.variablehistory': {
+            'Meta': {'object_name': 'VariableHistory'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'operation': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'variable': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vhistory'", 'to': u"orm['orm.Variable']"})
+        }
+    }
+
+    complete_apps = ['orm']
\ No newline at end of file
lib/toaster/orm/models.py +3 -1
index 99cc695..364b215 100644
@@ -843,6 +843,8 @@ class Layer_Version(models.Model):
     dirpath = models.CharField(max_length=255, null = True, default = None)          # LayerBranch.vcs_subdir
     priority = models.IntegerField(default = 0)         # if -1, this is a default layer
 
+    project = models.ForeignKey('Project', null = True, default = None)   # Set if this layer is project-specific; always set for imported layers, and project-set branches
+
     # code lifted, with adaptations, from the layerindex-web application https://git.yoctoproject.org/cgit/cgit.cgi/layerindex-web/
     def _handle_url_path(self, base_url, path):
         import re
@@ -902,7 +904,7 @@ class Layer_Version(models.Model):
         return sorted(
                 Layer_Version.objects.filter( layer__name = self.layer.name, up_branch__name = self.up_branch.name ),
                 key = lambda x: _get_ls_priority(x.layer_source),
-                reverse = False)
+                reverse = True)
 
 
     def __unicode__(self):
lib/toaster/toastergui/views.py +37 -22

111 lines not on this page · raw message

Signature · 1 line
1.9.1
#4 lore raw

PATCH 4/14 toastergui: do not show project info in interactive mode

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

The most recent builds section showed the project name and the Run Again buttons regardless whenever the Toaster was in interactive or managed modes. These widgets have no meaning in interactive mode, and this patch disables the widgets if toaster is not running in Managed mode.

[YOCTO #6776]

---
 lib/toaster/toastergui/templates/mrb_section.html | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
lib/toaster/toastergui/templates/mrb_section.html +3 -3
index 586c47b..4237980 100644
@@ -13,7 +13,7 @@
   <div id="latest-builds">
   {% for build in mru %}
     <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}" style="padding-top: 0;">
-    {% if build.project %}
+    {% if MANAGED and build.project %}
        <span class="label {%if build.outcome == build.SUCCEEDED%}label-success{%elif build.outcome == build.FAILED%}label-danger{%else%}label-info{%endif%}" style="font-weight: normal; margin-bottom: 5px; margin-left:-15px; padding-top:5px;"> {{build.project.name}} </span>
     {% endif %}
 
@@ -40,10 +40,10 @@
       {% endif %}
             </div>
             <div class="lead ">
-              <span class="lead{%if not build.project%} pull-right{%endif%}">
+              <span class="lead{%if not MANAGED or not build.project%} pull-right{%endif%}">
                 Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a>
               </span>
-          {% if build.project %}
+          {% if MANAGED and build.project %}
               <a class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%elif build.outcome == build.FAILED%}btn-danger{%else%}btn-info{%endif%} pull-right" onclick="scheduleBuild({% url 'xhr_projectbuild' build.project.id as bpi%}{{bpi|json}}, {{build.project.name|json}}, {{build.get_sorted_target_list|mapselect:'target'|json}})">Run again</a>
           {% endif %}
             </div>
Signature · 1 line
1.9.1
#5 lore raw

PATCH 5/14 toaster: display Toaster exceptions and other fixes

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

Changing ToasterUI to log toaster exceptions on a different level than build errors.

Updating the build dashboard to show Toaster exceptions.

We add extra logging to console for exceptions.

Fixed a problem where packages database entries were created instead of being looked up in the database, conficting with entries created to satisfy dependency information.

Toaster now checks for invalid states at startup and performs needed cleanups.

Removed loading reference to jquery-ui.min.css as we do not have this file.

---
 lib/bb/ui/buildinfohelper.py                       | 15 ++++++++--
 lib/bb/ui/toasterui.py                             |  7 +++--
 .../management/commands/checksettings.py           |  8 +++++
 lib/toaster/orm/models.py                          |  9 +++++-
 lib/toaster/toastergui/static/js/libtoaster.js     |  7 +++++
 lib/toaster/toastergui/templates/base.html         |  2 +-
 .../toastergui/templates/builddashboard.html       | 35 ++++++++++++++++++++++
 lib/toaster/toastergui/views.py                    |  2 +-
 8 files changed, 77 insertions(+), 8 deletions(-)
lib/bb/ui/buildinfohelper.py +13 -2
index a907a03..a3401ce 100644
@@ -381,7 +381,7 @@ class ORMWrapper(object):
                 searchname = pkgpnmap[p]['OPKGN']
 
             packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname )
-            if created or package[p]['object'].size == -1:    # save the data anyway we can, not just if it was not created here; bug [YOCTO #6887]
+            if created or packagedict[p]['object'].size == -1:    # save the data anyway we can, not just if it was not created here; bug [YOCTO #6887]
                 # fill in everything we can from the runtime-reverse package data
                 try:
                     packagedict[p]['object'].recipe = recipes[pkgpnmap[p]['PN']]
@@ -462,7 +462,7 @@ class ORMWrapper(object):
         if 'OPKGN' in package_info.keys():
             pname = package_info['OPKGN']
 
-        bp_object = Package.objects.create( build = build_obj,
+        bp_object, created = Package.objects.get_or_create( build = build_obj,
                                        name = pname )
 
         bp_object.installed_name = package_info['PKG']
@@ -1043,6 +1043,15 @@ class BuildInfoHelper(object):
         mockevent.lineno = -1
         self.store_log_event(mockevent)
 
+    def store_log_exception(self, text, backtrace = ""):
+        mockevent = MockEvent()
+        mockevent.levelno = -1
+        mockevent.msg = text
+        mockevent.pathname = backtrace
+        mockevent.lineno = -1
+        self.store_log_event(mockevent)
+
+
     def store_log_event(self, event):
         if event.levelno < format.WARNING:
             return
@@ -1078,6 +1087,8 @@ class BuildInfoHelper(object):
             log_information['level'] = LogMessage.ERROR
         elif event.levelno == format.WARNING:
             log_information['level'] = LogMessage.WARNING
+        elif event.levelno == -1:   # toaster self-logging
+            log_information['level'] = -1
         else:
             log_information['level'] = LogMessage.INFO
 
lib/bb/ui/toasterui.py +4 -3
index b9e8029..9bd04df 100644
@@ -299,12 +299,13 @@ def main(server, eventHandler, params ):
             logger.error(e)
             import traceback
             exception_data = traceback.format_exc()
+            print(exception_data)
 
             # save them to database, if possible; if it fails, we already logged to console.
             try:
-                buildinfohelper.store_log_error("%s\n%s" % (str(e), exception_data))
-            except Exception:
-                pass
+                buildinfohelper.store_log_exception("%s\n%s" % (str(e), exception_data))
+            except Exception as ce:
+                print("CRITICAL: failed to to save toaster exception to the database: %s" % str(ce))
 
             pass
 
lib/toaster/bldcontrol/management/commands/checksettings.py +8 -0
index cd604eb..96d2d51 100644
@@ -139,4 +139,12 @@ class Command(NoArgsCommand):
             ToasterSetting.objects.filter(name = 'DEFAULT_RELEASE').delete()
             ToasterSetting.objects.get_or_create(name = 'DEFAULT_RELEASE', value = '')
 
+        # we are just starting up. we must not have any builds in progress, or build environments taken
+        for b in BuildRequest.objects.filter(state = BuildRequest.REQ_INPROGRESS):
+            BRError.objects.create(req = b, errtype = "toaster", errmsg = "Toaster found this build IN PROGRESS while Toaster started up. This is an inconsistent state, and the build was marked as failed")
+
+        BuildRequest.objects.filter(state = BuildRequest.REQ_INPROGRESS).update(state = BuildRequest.REQ_FAILED)
+
+        BuildEnvironment.objects.update(lock = BuildEnvironment.LOCK_FREE)
+
         return 0
lib/toaster/orm/models.py +8 -1
index 364b215..34d3754 100644
@@ -178,6 +178,11 @@ class Build(models.Model):
         tgts = Target.objects.filter(build_id = self.id).order_by( 'target' );
         return( tgts );
 
+    @property
+    def toaster_exceptions(self):
+        return self.logmessage_set.filter(level=LogMessage.EXCEPTION)
+
+
 class ProjectTarget(models.Model):
     project = models.ForeignKey(Project)
     target = models.CharField(max_length=100)
@@ -966,13 +971,15 @@ class HelpText(models.Model):
     text = models.TextField()
 
 class LogMessage(models.Model):
+    EXCEPTION = -1      # used to signal self-toaster-exceptions
     INFO = 0
     WARNING = 1
     ERROR = 2
 
     LOG_LEVEL = ( (INFO, "info"),
             (WARNING, "warn"),
-            (ERROR, "error") )
+            (ERROR, "error"),
+            (EXCEPTION, "toaster exception"))
 
     build = models.ForeignKey(Build)
     task  = models.ForeignKey(Task, blank = True, null=True)
lib/toaster/toastergui/static/js/libtoaster.js +7 -0
index 4983ef6..8e76ecb 100644
@@ -252,6 +252,13 @@ $(document).ready(function() {
     $('.toggle-warnings').click(function() {
         $('#collapse-warnings').toggleClass('in');
     });
+    $('.show-exceptions').click(function() {
+        $('#collapse-exceptions').addClass('in');
+    });
+    $('.toggle-exceptions').click(function() {
+        $('#collapse-exceptions').toggleClass('in');
+    });
+
     //show warnings section when requested from the previous page
     if (location.href.search('#warnings') > -1) {
         $('#collapse-warnings').addClass('in');
lib/toaster/toastergui/templates/base.html +1 -1
index f457b91..8170a3d 100644
@@ -8,7 +8,7 @@
 <link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'>
 <link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'>
 <link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'>
-<link rel="stylesheet" href="assets/css/jquery-ui-1.10.3.custom.min.css" type='text/css'>
+
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
 <script src="{% static 'js/jquery-2.0.3.min.js' %}">
lib/toaster/toastergui/templates/builddashboard.html +35 -0
index 2aa7b6b..e682094 100644
@@ -39,6 +39,14 @@
             <span class="pull-right">Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a></span>
 {%endif%}
     </div>
+    {% if build.toaster_exceptions.count > 0 %}
+    <div class="row">
+        <small class="pull-right">
+		<i class="icon-question-sign get-help get-help-blue" title="" data-original-title="Toaster exceptions do not affect your build: only the operation of Toaster"></i>
+		<a class="show-exceptions" href="#exceptions">Toaster threw {{build.toaster_exceptions.count}} exception{{build.toaster_exceptions.count|pluralize}}</a>
+	</small>
+    </div>
+    {% endif %}
   </div>
 </div>
 
@@ -223,6 +231,33 @@
 </div>
 {% endif %}
 
+
+{% if build.toaster_exceptions.count > 0 %}
+<div class="accordion span10 pull-right" id="exceptions">
+  <div class="accordion-group">
+    <div class="accordion-heading">
+      <a class="accordion-toggle exception toggle-exceptions">
+        <h2 id="exception-toggle">
+          <i class="icon-warning-sign"></i>
+          {{build.toaster_exceptions.count}} Toaster exception{{build.toaster_exceptions.count|pluralize}}
+        </h2>
+      </a>
+    </div>
+    <div class="accordion-body collapse" id="collapse-exceptions">
+      <div class="accordion-inner">
+        <div class="span10">
+          {% for exception in build.toaster_exceptions %}
+            <div class="alert alert-exception">
+              <pre>{{exception.message}}</pre>
+            </div>
+          {% endfor %}
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+{% endif %}
+
 <script type="text/javascript">
     $(document).ready(function() {
         //show warnings section when requested from the previous page
lib/toaster/toastergui/views.py +1 -1
index 11c373a..b13f3e8 100755
@@ -461,7 +461,7 @@ def builddashboard( request, build_id ):
     template = "builddashboard.html"
     if Build.objects.filter( pk=build_id ).count( ) == 0 :
         return redirect( builds )
-    build = Build.objects.filter( pk = build_id )[ 0 ];
+    build = Build.objects.get( pk = build_id );
     layerVersionId = Layer_Version.objects.filter( build = build_id );
     recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
     tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
Signature · 1 line
1.9.1
#6 lore raw

PATCH 6/14 toasterui: fix layer identification for managed builds

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

If we have a managed build, we match the layers used for build with the layers configured for project, as we know where the layers are coming from

[YOCTO #6962]

---
 lib/bb/ui/buildinfohelper.py | 38 +++++++++++++++++++++++++-------------
 1 file changed, 25 insertions(+), 13 deletions(-)
lib/bb/ui/buildinfohelper.py +25 -13
index a3401ce..e428e4c 100644
@@ -117,15 +117,14 @@ class ORMWrapper(object):
 
         if brbe is not None:
             from bldcontrol.models import BuildEnvironment, BuildRequest
-            try:
-                br, be = brbe.split(":")
-                buildrequest = BuildRequest.objects.get(pk = br)
-                buildrequest.build = build
-                buildrequest.save()
-                build.project_id = buildrequest.project_id
-                build.save()
-            except BuildRequest.DoesNotExist:
-                pass
+            br, be = brbe.split(":")
+
+            buildrequest = BuildRequest.objects.get(pk = br)
+            buildrequest.build = build
+            buildrequest.save()
+
+            build.project_id = buildrequest.project_id
+            build.save()
         return build
 
     def create_target_objects(self, target_info):
@@ -250,17 +249,30 @@ class ORMWrapper(object):
 
         return layer_version_object
 
-    def get_update_layer_object(self, layer_information):
+    def get_update_layer_object(self, layer_information, brbe):
         assert 'name' in layer_information
         assert 'local_path' in layer_information
         assert 'layer_index_url' in layer_information
 
-        layer_object, created = Layer.objects.get_or_create(
+        if brbe is None:
+            layer_object, created = Layer.objects.get_or_create(
                                 name=layer_information['name'],
                                 local_path=layer_information['local_path'],
                                 layer_index_url=layer_information['layer_index_url'])
+            return layer_object
+        else:
+            # we are under managed mode; we must match the layer used in the Project Layer
+            from bldcontrol.models import BuildEnvironment, BuildRequest
+            br, be = brbe.split(":")
+
+            buildrequest = BuildRequest.objects.get(pk = br)
+
+            # we might have a race condition here, as the project layers may change between the build trigger and the actual build execution
+            # but we can only match on the layer name, so the worst thing can happen is a mis-identification of the layer, not a total failure
+            layer_object = buildrequest.project.projectlayer_set.get(layercommit__layer__name=layer_information['name']).layercommit.layer
+
+            return layer_object
 
-        return layer_object
 
     def save_target_file_information(self, build_obj, target_obj, filedata):
         assert isinstance(build_obj, Build)
@@ -689,7 +701,7 @@ class BuildInfoHelper(object):
         layerinfos = event._localdata
         self.internal_state['lvs'] = {}
         for layer in layerinfos:
-            self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer])] = layerinfos[layer]['version']
+            self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version']
 
 
     def store_started_build(self, event):
Signature · 1 line
1.9.1
#7 lore raw

PATCH 7/14 toaster: fix loadconf path calculation

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

Fixing the path calculation for local layer sources, as the path need to be absolute.

Added tests for pieces of code.

---
 .../bldcontrol/management/commands/loadconf.py     | 42 +++++++++++++---------
 lib/toaster/bldcontrol/tests.py                    | 19 ++++++++++
 2 files changed, 44 insertions(+), 17 deletions(-)
lib/toaster/bldcontrol/management/commands/loadconf.py +25 -17
index 6e1f97a..2257a71 100644
@@ -5,20 +5,29 @@ import os
 
 from checksettings import DN
 
+def _reduce_canon_path(path):
+    components = []
+    for c in path.split("/"):
+        if c == "..":
+            del components[-1]
+        elif c == ".":
+            pass
+        else:
+            components.append(c)
+    if len(components) < 2:
+        components.append('')
+    return "/".join(components)
+
+def _get_id_for_sourcetype(s):
+    for i in LayerSource.SOURCE_TYPE:
+        if s == i[1]:
+            return i[0]
+    raise Exception("Could not find definition for sourcetype " + s)
+
 class Command(BaseCommand):
     help = "Loads a toasterconf.json file in the database"
     args = "filepath"
 
-    def _reduce_canon_path(self, path):
-        components = []
-        for c in path.split("/"):
-            if c == "..":
-                del components[-1]
-            elif c == ".":
-                pass
-            else:
-                components.append(c)
-        return "/".join(components)
 
 
     def _import_layer_config(self, filepath):
@@ -71,16 +80,13 @@ class Command(BaseCommand):
             assert 'name' in lsi
             assert 'branches' in lsi
 
-            def _get_id_for_sourcetype(s):
-                for i in LayerSource.SOURCE_TYPE:
-                    if s == i[1]:
-                        return i[0]
-                raise Exception("Could not find definition for sourcetype " + s)
 
             if _get_id_for_sourcetype(lsi['sourcetype']) == LayerSource.TYPE_LAYERINDEX or lsi['apiurl'].startswith("/"):
                 apiurl = lsi['apiurl']
             else:
-                apiurl = self._reduce_canon_path(os.path.join(DN(filepath), lsi['apiurl']))
+                apiurl = _reduce_canon_path(os.path.join(DN(os.path.abspath(filepath)), lsi['apiurl']))
+
+            assert ((_get_id_for_sourcetype(lsi['sourcetype']) == LayerSource.TYPE_LAYERINDEX) or apiurl.startswith("/")), (lsi['sourcetype'],apiurl)
 
             try:
                 ls = LayerSource.objects.get(sourcetype = _get_id_for_sourcetype(lsi['sourcetype']), apiurl = apiurl)
@@ -102,7 +108,7 @@ class Command(BaseCommand):
                     if layerinfo['local_path'].startswith("/"):
                         lo.local_path = layerinfo['local_path']
                     else:
-                        lo.local_path = self._reduce_canon_path(os.path.join(DN(DN(DN(filepath))), layerinfo['local_path']))
+                        lo.local_path = _reduce_canon_path(os.path.join(ls.apiurl, layerinfo['local_path']))
 
                     if not os.path.exists(lo.local_path):
                         raise Exception("Local layer path %s must exists." % lo.local_path)
@@ -110,6 +116,8 @@ class Command(BaseCommand):
                     lo.vcs_url = layerinfo['vcs_url']
                     if layerinfo['vcs_url'].startswith("remote:"):
                         lo.vcs_url = _read_git_url_from_local_repository(layerinfo['vcs_url'])
+                    else:
+                        lo.vcs_url = layerinfo['vcs_url']
 
                     if 'layer_index_url' in layerinfo:
                         lo.layer_index_url = layerinfo['layer_index_url']
lib/toaster/bldcontrol/tests.py +19 -0
index 37d6524..5a9d1df 100644
@@ -141,3 +141,22 @@ class RunBuildsCommandTests(TestCase):
         self.assertTrue(br.state == BuildRequest.REQ_INPROGRESS, "Request is not updated")
         # no more selections possible here
         self.assertRaises(IndexError, command._selectBuildRequest)
+
+
+class UtilityTests(TestCase):
+    def test_reduce_path(self):
+        from bldcontrol.management.commands.loadconf import _reduce_canon_path, _get_id_for_sourcetype
+
+        self.assertTrue( _reduce_canon_path("/") == "/")
+        self.assertTrue( _reduce_canon_path("/home/..") == "/")
+        self.assertTrue( _reduce_canon_path("/home/../ana") == "/ana")
+        self.assertTrue( _reduce_canon_path("/home/../ana/..") == "/")
+        self.assertTrue( _reduce_canon_path("/home/ana/mihai/../maria") == "/home/ana/maria")
+
+    def test_get_id_for_sorucetype(self):
+        from bldcontrol.management.commands.loadconf import _reduce_canon_path, _get_id_for_sourcetype
+        self.assertTrue( _get_id_for_sourcetype("layerindex") == 1)
+        self.assertTrue( _get_id_for_sourcetype("local") == 0)
+        self.assertTrue( _get_id_for_sourcetype("imported") == 2)
+        with self.assertRaises(Exception):
+            _get_id_for_sourcetype("unknown")
Signature · 1 line
1.9.1
#8 lore raw

PATCH 8/14 toastergui: new project page

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

Patch that brings in to new project page according to specifications.

[YOCTO #6596]

---
 lib/toaster/toastergui/templates/base.html       |   2 +-
 lib/toaster/toastergui/templates/newproject.html | 116 +++++++++++++++++------
 lib/toaster/toastergui/views.py                  |   7 +-
 3 files changed, 92 insertions(+), 33 deletions(-)
lib/toaster/toastergui/templates/base.html +1 -1
index 8170a3d..594c495 100644
@@ -60,7 +60,7 @@
             </a>
             {%if MANAGED %}
             <div class="btn-group pull-right">
-              <a class="btn" href="{% url 'newproject' %}">New project</a>
+                  <a class="btn" id="new-project-button" href="{% url 'newproject' %}">New project</a>
             </div>
             <!-- New build popover -->
             <div class="btn-group pull-right" id="new-build-button">
lib/toaster/toastergui/templates/newproject.html +86 -30

121 lines not on this page · raw message

lib/toaster/toastergui/views.py +5 -2
index b13f3e8..49a7769 100755
@@ -1894,9 +1894,12 @@ if toastermain.settings.MANAGED:
             'email': request.user.email if request.user.is_authenticated() else '',
             'username': request.user.username if request.user.is_authenticated() else '',
             'releases': Release.objects.order_by("id"),
-            'defaultbranch': ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value,
         }
 
+        try:
+            context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
+        except ToasterSetting.DoesNotExist:
+            pass
 
         if request.method == "GET":
             # render new project page
@@ -2156,7 +2159,7 @@ if toastermain.settings.MANAGED:
 
 
             def _lv_to_dict(x):
-                return {"id": x.pk, "name": x.layer.name, 
+                return {"id": x.pk, "name": x.layer.name,
                         "detail": "(" + x.layer.vcs_url + (")" if x.up_branch == None else " | "+x.up_branch.name+")"),
                         "giturl": x.layer.vcs_url, "layerdetailurl" : reverse('layerdetails', args=(x.pk,))}
 
Signature · 1 line
1.9.1
#9 lore raw

PATCH 9/14 toaster: fix errors and warnings display

In reply to Alex DAMIAN

From: Belen Barros Pena <[email protected]>

In the 'Latest builds' section of the project page, show number of errors and number of warnings as we show them in the all builds page.

---
 lib/toaster/toastergui/templates/project.html | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)
lib/toaster/toastergui/templates/project.html +18 -2
index 18a52bf..0b2821c 100644
@@ -178,8 +178,24 @@ vim: expandtab tabstop=2
                  {[b.build[0].completed_on|date:'dd/MM/yy HH:mm']}
               </ngif>
             </div>
-            <div class="span2"><span><a href="{[b.build[0].build_page_url]}#errors" class="lead error" ng-if="b.build[0].errors">{[b.build[0].errors]}</a></span></div>
-            <div class="span2"><span><a href="{[b.build[0].build_page_url]}#warnings" class="lead warning" ng-if="b.build[0].warnings">{[b.build[0].warnings]}</a></span></div>
+            <div class="span2">
+              <ngif ng-if="b.build[0].errors">
+                <span>
+                  <i class="icon-minus-sign red lead"></i>
+                  <a href="{[b.build[0].build_page_url]}#errors" class="lead error">{[b.build[0].errors]}
+                  <ng-pluralize count="b.build[0].errors" when="{'1':'error','other':'errors'}"></ng-pluralize></a>
+                </span>
+              </ngif>
+            </div>
+            <div class="span2">
+              <ngif ng-if="b.build[0].warnings">
+                <span>
+                  <i class="icon-warning-sign yellow lead"></i>
+                  <a href="{[b.build[0].build_page_url]}#warnings" class="lead warning">{[b.build[0].warnings]}
+                  <ng-pluralize count="b.build[0].warnings" when="{'1':'warning','other':'warnings'}"></ng-pluralize></a>
+                </span>
+              </ngif>
+            </div>
             <div> <span class="lead">Build time: <a href="{[b.build[0].build_time_page_url]}">{[b.build[0].build_time|timediff]}</a></span>
                 <button class="btn pull-right" ng-class="{'Succeeded':  'btn-success', 'Failed': 'btn-danger'}[b.build[0].status]"
                     ng-click="targetExistingBuild(b.targets)">Run again</button>
Signature · 1 line
1.9.1
#10 lore raw

PATCH 10/14 toaster: make 'latest builds' section consistent across pages

In reply to Alex DAMIAN

From: Belen Barros Pena <[email protected]>

Make sure that the 'latest builds' sections in the all builds page and the project page are identical:

* no icon to represent build outcome
* remove machine name
* show date only when the build is more than 24 hours old
* same date and time format

---
 lib/toaster/toastergui/templates/mrb_section.html  | 15 +++++++++++----
 lib/toaster/toastergui/templatetags/projecttags.py |  8 ++++++++
 2 files changed, 19 insertions(+), 4 deletions(-)
lib/toaster/toastergui/templates/mrb_section.html +11 -4
index 4237980..33afb7b 100644
@@ -7,7 +7,7 @@
 
   <div class="page-header top-air">
       <h1>
-          Latest Builds
+          Latest builds
        </h1>
   </div>
   <div id="latest-builds">
@@ -18,16 +18,23 @@
     {% endif %}
 
         <div class="row-fluid">
-            <div class="span4 lead">
-                {%if build.outcome == build.SUCCEEDED%}<i class="icon-ok-sign success"></i>{%elif build.outcome == build.FAILED%}<i class="icon-minus-sign error"></i>{%else%}{%endif%}
+            <div class="span3 lead">
     {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
                 <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}">
     {% endif %}
-                <span data-toggle="tooltip" {%if build.target_set.all.count > 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} {{build.machine}} ({{build.completed_on|naturaltime}})</span>
+                <span data-toggle="tooltip" {%if build.target_set.all.count > 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%}
+		        </span>
     {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
                 </a>
     {% endif %}
             </div>
+            <div class="span2 lead">
+                {% if build.completed_on|format_build_date  %}
+                    {{ build.completed_on|date:'d/m/y H:i' }}
+                {% else %}
+                    {{ build.completed_on|date:'H:i' }}
+                {% endif %}
+            </div>
     {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
             <div class="span2 lead">
       {% if  build.errors_no %}
lib/toaster/toastergui/templatetags/projecttags.py +8 -0
index 99fd4cf..f564edf 100644
@@ -268,3 +268,11 @@ def get_dict_value(dictionary, key):
         return dictionary[key]
     except (KeyError, IndexError):
         return ''
+
[email protected]
+def format_build_date(completed_on):
+    now = timezone.now()
+    delta = now - completed_on
+
+    if delta.days >= 1:
+        return True
Signature · 1 line
1.9.1
#11 lore raw

PATCH 11/14 toaster: fix padding of build notifications

In reply to Alex DAMIAN

From: Belen Barros Pena <[email protected]>

In managed mode, we show the name of the project in the build notifications of the all builds page. The way we show the project requires modifying the default padding of the .alert class. This patch makes sure the padding modification is only applied in managed mode, i.e., when the project name needs to be shown.

---
 lib/toaster/toastergui/static/css/default.css     | 4 ++++
 lib/toaster/toastergui/templates/mrb_section.html | 4 ++--
 2 files changed, 6 insertions(+), 2 deletions(-)
lib/toaster/toastergui/static/css/default.css +4 -0
index dcb8e34..199c753 100644
@@ -136,6 +136,10 @@ select { width: auto; }
 .new-build .alert { margin-top: 10px; }
 .new-build .alert p { margin-top: 10px; }
 
+/* styles for showing the project name in build mode */
+.project-name { padding-top: 0; }
+.project-name .label { font-weight: normal; margin-bottom: 5px; margin-left: -15px; padding: 5px; }
+
 /* Remove bottom margin for forms inside modal dialogs */
 #dependencies_modal_form { margin-bottom: 0px; }
 
lib/toaster/toastergui/templates/mrb_section.html +2 -2
index 33afb7b..73031e2 100644
@@ -12,9 +12,9 @@
   </div>
   <div id="latest-builds">
   {% for build in mru %}
-    <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%}" style="padding-top: 0;">
+    <div class="alert {%if build.outcome == build.SUCCEEDED%}alert-success{%elif build.outcome == build.FAILED%}alert-error{%else%}alert-info{%endif%} {% if MANAGED and build.project %}project-name{% endif %} ">
     {% if MANAGED and build.project %}
-       <span class="label {%if build.outcome == build.SUCCEEDED%}label-success{%elif build.outcome == build.FAILED%}label-danger{%else%}label-info{%endif%}" style="font-weight: normal; margin-bottom: 5px; margin-left:-15px; padding-top:5px;"> {{build.project.name}} </span>
+       <span class="label {%if build.outcome == build.SUCCEEDED%}label-success{%elif build.outcome == build.FAILED%}label-danger{%else%}label-info{%endif%}"> {{build.project.name}} </span>
     {% endif %}
 
         <div class="row-fluid">
Signature · 1 line
1.9.1
#12 lore raw

PATCH 12/14 toasterui: Compatibility patch for daisy and dizzy

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

This patch brings in changes that allow a toasterUI coming in from 'master' branch to record data from a 'daisy' or 'dizzy' bitbake server.

This is needed to allow Toaster to record builds running on older branch releases.

---
 lib/bb/ui/buildinfohelper.py | 43 ++++++++++++++++++++++++++-----------------
 lib/bb/ui/toasterui.py       |  3 +--
 2 files changed, 27 insertions(+), 19 deletions(-)
lib/bb/ui/buildinfohelper.py +26 -17
index e428e4c..9aa8fe3 100644
@@ -695,10 +695,19 @@ class BuildInfoHelper(object):
 
     ################################
     ## external available methods to store information
+    @staticmethod
+    def _get_data_from_event(event):
+        evdata = None
+        if '_localdata' in vars(event):
+            evdata = event._localdata
+        elif 'data' in vars(event):
+            evdata = event.data
+        else:
+            raise Exception("Event with neither _localdata or data properties")
+        return evdata
 
     def store_layer_info(self, event):
-        assert '_localdata' in vars(event)
-        layerinfos = event._localdata
+        layerinfos = BuildInfoHelper._get_data_from_event(event)
         self.internal_state['lvs'] = {}
         for layer in layerinfos:
             self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version']
@@ -732,15 +741,16 @@ class BuildInfoHelper(object):
         return self.brbe
 
 
-
     def update_target_image_file(self, event):
         image_fstypes = self.server.runCommand(["getVariable", "IMAGE_FSTYPES"])[0]
+        evdata = BuildInfoHelper._get_data_from_event(event)
+
         for t in self.internal_state['targets']:
             if t.is_image == True:
-                output_files = list(event._localdata.viewkeys())
+                output_files = list(evdata.viewkeys())
                 for output in output_files:
                     if t.target in output and output.split('.rootfs.')[1] in image_fstypes:
-                        self.orm_wrapper.save_target_image_file_information(t, output, event._localdata[output])
+                        self.orm_wrapper.save_target_image_file_information(t, output, evdata[output])
 
     def update_build_information(self, event, errors, warnings, taskfailures):
         if 'build' in self.internal_state:
@@ -748,8 +758,8 @@ class BuildInfoHelper(object):
 
 
     def store_license_manifest_path(self, event):
-        deploy_dir = event._localdata['deploy_dir']
-        image_name =  event._localdata['image_name']
+        deploy_dir = BuildInfoHelper._get_data_from_event(event)['deploy_dir']
+        image_name = BuildInfoHelper._get_data_from_event(event)['image_name']
         path = deploy_dir + "/licenses/" + image_name + "/"
         for target in self.internal_state['targets']:
             if target.target in image_name:
@@ -797,7 +807,7 @@ class BuildInfoHelper(object):
 
 
     def store_tasks_stats(self, event):
-        for (taskfile, taskname, taskstats, recipename) in event._localdata:
+        for (taskfile, taskname, taskstats, recipename) in BuildInfoHelper._get_data_from_event(event):
             localfilepath = taskfile.split(":")[-1]
             assert localfilepath.startswith("/")
 
@@ -812,7 +822,8 @@ class BuildInfoHelper(object):
             task_information['task_name'] = taskname
             task_information['cpu_usage'] = taskstats['cpu_usage']
             task_information['disk_io'] = taskstats['disk_io']
-            task_information['elapsed_time'] = taskstats['elapsed_time']
+            if 'elapsed_time' in taskstats:
+                task_information['elapsed_time'] = taskstats['elapsed_time']
             task_obj = self.orm_wrapper.get_update_task_object(task_information, True)  # must exist
 
     def update_and_store_task(self, event):
@@ -870,7 +881,7 @@ class BuildInfoHelper(object):
 
 
     def store_missed_state_tasks(self, event):
-        for (fn, taskname, taskhash, sstatefile) in event._localdata['missed']:
+        for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['missed']:
 
             identifier = fn + taskname + "_setscene"
             recipe_information = self._get_recipe_information_from_taskfile(fn)
@@ -888,7 +899,7 @@ class BuildInfoHelper(object):
 
             self.orm_wrapper.get_update_task_object(task_information)
 
-        for (fn, taskname, taskhash, sstatefile) in event._localdata['found']:
+        for (fn, taskname, taskhash, sstatefile) in BuildInfoHelper._get_data_from_event(event)['found']:
 
             identifier = fn + taskname + "_setscene"
             recipe_information = self._get_recipe_information_from_taskfile(fn)
@@ -904,15 +915,14 @@ class BuildInfoHelper(object):
 
 
     def store_target_package_data(self, event):
-        assert '_localdata' in vars(event)
         # for all image targets
         for target in self.internal_state['targets']:
             if target.is_image:
                 try:
-                    pkgdata = event._localdata['pkgdata']
-                    imgdata = event._localdata['imgdata'][target.target]
+                    pkgdata = BuildInfoHelper._get_data_from_event(event)['pkgdata']
+                    imgdata = BuildInfoHelper._get_data_from_event(event)['imgdata'][target.target]
                     self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'])
-                    filedata = event._localdata['filedata'][target.target]
+                    filedata = BuildInfoHelper._get_data_from_event(event)['filedata'][target.target]
                     self.orm_wrapper.save_target_file_information(self.internal_state['build'], target, filedata)
                 except KeyError:
                     # we must have not got the data for this image, nothing to save
@@ -1026,8 +1036,7 @@ class BuildInfoHelper(object):
 
 
     def store_build_package_information(self, event):
-        assert '_localdata' in vars(event)
-        package_info = event._localdata
+        package_info = BuildInfoHelper._get_data_from_event(event)
         self.orm_wrapper.save_build_package_information(self.internal_state['build'],
                             package_info,
                             self.internal_state['recipes'],
lib/bb/ui/toasterui.py +1 -2
index 9bd04df..3a6104b 100644
@@ -296,10 +296,9 @@ def main(server, eventHandler, params ):
             pass
         except Exception as e:
             # print errors to log
-            logger.error(e)
             import traceback
             exception_data = traceback.format_exc()
-            print(exception_data)
+            logger.error("%s\n%s" % (e, exception_data))
 
             # save them to database, if possible; if it fails, we already logged to console.
             try:
Signature · 1 line
1.9.1
#13 lore raw

PATCH 13/14 toaster: release name consistency

In reply to Alex DAMIAN

From: Belen Barros Pena <[email protected]>

Small changes to the project, new project and all layers pages to ensure consitency in release naming across the interface.

The changes are:

* In the new project page, change the label 'release version' to 'release'

* In the new project page, sort the releases in the dropdown menu in ascending alphabetical order

* In the new project page, remove the release name that was showing between brackets after the release description in the dropdown menu

* In the project page, make sure the release information shows the release description field instead of the release name, to keep consistency with the new project page

* In the all layers page, provide some help text for the branch 'HEAD'

---
 lib/toaster/toastergui/templates/layers.html     | 9 ++++++++-
 lib/toaster/toastergui/templates/newproject.html | 4 ++--
 lib/toaster/toastergui/templates/project.html    | 4 ++--
 lib/toaster/toastergui/views.py                  | 4 ++--
 4 files changed, 14 insertions(+), 7 deletions(-)
lib/toaster/toastergui/templates/layers.html +8 -1
index 4e92332..ced54c2 100644
@@ -52,7 +52,14 @@
             <a target="_blank" href="{{ o.get_vcs_dirpath_link_url }}"><i class="icon-share get-info"></i></a>
                 {% endif %}
         </td>
-            <td class="branch">{% if o.branch %}{{o.branch}}{% else %}{{o.up_branch.name}}{% endif %}</td>
+            <td class="branch">
+                {% if o.branch %}
+                    {{o.branch}}
+                {% else %}
+                    {{o.up_branch.name}}
+                    <i class="icon-question-sign get-help hover-help" title="Your builds will use the tip of the branch you have cloned or downloaded to your computer, so nothing will be fetched"></i>
+                {% endif %}
+            </td>
             <td class="dependencies">
         {% with ods=o.dependencies.all%}
             {% if ods.count %}
lib/toaster/toastergui/templates/newproject.html +2 -2
index 5a5e1e6..512a8fa 100644
@@ -18,12 +18,12 @@
               <input type="text" class="input-xlarge" required id="new-project-name" name="projectname">
        {% if releases.count > 1 %}
               <label class="project-form">
-                Release version
+                Release
                 <i class="icon-question-sign get-help" title="The version of the build system you want to use"></i>
               </label>
               <select name="projectversion" id="projectversion">
   {% for release in releases %}
-    <option value="{{release.id}}"{%if projectversion == release.id %} selected{%endif%}>{{release.description}} ({{release.name}})</option>
+    <option value="{{release.id}}"{%if projectversion == release.id %} selected{%endif%}>{{release.description}}</option>
   {% endfor %}
               </select>
   {% for release in releases %}
lib/toaster/toastergui/templates/project.html +2 -2
index 0b2821c..fdfc3af 100644
@@ -350,13 +350,13 @@ vim: expandtab tabstop=2
       <i class="icon-question-sign get-help heading-help" title="The version of the build system you want to use"></i>
     </h3>
     <p class="lead" id="change-project-version-opposite">
-      <span id="project-version">{[project.release.name]}</span>
+      <span id="project-version">{[project.release.desc]}</span>
       <i id="change-version" class="icon-pencil" ng-click="toggle('#change-project-version')" ></i>
     </p>
     <div class="div-inline" id="change-project-version" style="display:none;">
       <form ng-submit="test('#change-project-version')" class="input-append">
         <select id="select-version" ng-model="projectVersion">
-          <option ng-repeat="r in releases" value="{[r.id]}" ng-selected="r.id == project.release.id">{[r.name]}</option>
+          <option ng-repeat="r in releases" value="{[r.id]}" ng-selected="r.id == project.release.id">{[r.description]}</option>
         </select>
         <input type="submit" class="btn" style="margin-left:5px;" value="Save" ng-disabled="project.release.id == projectVersion"/>
         <input type="reset"  class="btn btn-link" value="Cancel" ng-click="toggle('#change-project-version')" ng-disabled="project.release.id == projectVersion"/>
lib/toaster/toastergui/views.py +2 -2
index 49a7769..8301f6c 100755
@@ -1893,7 +1893,7 @@ if toastermain.settings.MANAGED:
         context = {
             'email': request.user.email if request.user.is_authenticated() else '',
             'username': request.user.username if request.user.is_authenticated() else '',
-            'releases': Release.objects.order_by("id"),
+            'releases': Release.objects.order_by("description"),
         }
 
         try:
@@ -2016,7 +2016,7 @@ if toastermain.settings.MANAGED:
                     prj.projectlayer_set.all().order_by("id")),
             "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
             "freqtargets": freqtargets,
-            "releases": map(lambda x: {"id": x.pk, "name": x.name}, Release.objects.all()),
+            "releases": map(lambda x: {"id": x.pk, "name": x.name, "description":x.description}, Release.objects.all()),
         }
         try:
             context["machine"] = {"name": prj.projectvariable_set.get(name="MACHINE").value}
Signature · 1 line
1.9.1
#14 lore raw

PATCH 14/14 toaster: remove the word 'project' from layers and machine

In reply to Alex DAMIAN

From: Belen Barros Pena <[email protected]>

Remove the word 'project' from the 'Layers' and 'Machine' sections of the project page, following feedback from the QA team, who suggested that the word 'project' raised questions regarding the scope of the configuration,

---
 lib/toaster/toastergui/templates/project.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
lib/toaster/toastergui/templates/project.html +2 -2
index fdfc3af..113e382 100644
@@ -227,7 +227,7 @@ vim: expandtab tabstop=2
     <!-- project layers -->
     <div id="layer-container" class="well well-transparent span4">
       <h3>
-        Project layers <span class="muted counter">({[layers.length]})</span>
+        Layers <span class="muted counter">({[layers.length]})</span>
         <i class="icon-question-sign get-help heading-help" title="OpenEmbedded organises metadata into modules called 'layers'. Layers allow you to isolate different types of customizations from each other. <a href='http://www.yoctoproject.org/docs/current/dev-manual/dev-manual.html#understanding-and-creating-layers' target='_blank'>More on layers</a>"></i>
       </h3>
       <div class="alert" ng-if="!layers.length">
@@ -292,7 +292,7 @@ vim: expandtab tabstop=2
     <!-- project configuration -->
     <div id="machine-distro" class="well well-transparent span4">
       <h3>
-        Project machine
+        Machine
         <i class="icon-question-sign get-help heading-help" title="The machine is the hardware for which you want to build. You can only set one machine per project"></i>
       </h3>
       <p class="lead" id="select-machine-opposite">
Signature · 1 line
1.9.1