* [PATCH 01/22] toaster: Fix build button current project race
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
@ 2015-05-28 14:13 ` Alex DAMIAN
2015-05-28 14:13 ` [PATCH 02/22] toaster: enable server-side sessions for all usages Alex DAMIAN
` (20 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:13 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
Make sure the current project value is set before we check to see if the
project is buildable. Also update the blacklist url patterns where we
aren't displaying the button.
[YOCTO #7739]
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/toastergui/static/js/base.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/toaster/toastergui/static/js/base.js b/lib/toaster/toastergui/static/js/base.js
index c4c96c8..ccc23e0 100644
--- a/lib/toaster/toastergui/static/js/base.js
+++ b/lib/toaster/toastergui/static/js/base.js
@@ -6,11 +6,13 @@ function basePageInit (ctx) {
/* 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){
+ if (ctx.numProjects == 0 || ctx.currentUrl.search('newproject|project/\\d$|importlayer$') > 0){
newBuildButton.hide();
return;
}
+ var currentProjectId = libtoaster.ctx.projectId;
+
/* Hide the change project icon when there is only one project */
if (ctx.numProjects == 1){
$('#project .icon-pencil').hide();
@@ -21,7 +23,6 @@ function basePageInit (ctx) {
_checkProjectBuildable()
_setupNewBuildButton();
- var currentProjectId = libtoaster.ctx.projectId;
function _checkProjectBuildable(){
if (currentProjectId == undefined)
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 02/22] toaster: enable server-side sessions for all usages
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
2015-05-28 14:13 ` [PATCH 01/22] toaster: Fix build button current project race Alex DAMIAN
@ 2015-05-28 14:13 ` Alex DAMIAN
2015-05-28 14:13 ` [PATCH 03/22] toaster logger: refactor recipe and layer file paths Alex DAMIAN
` (19 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:13 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
In an effort to fix client-side session storage using
cookies, we enable server-side session support for all pages.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
bin/toaster | 6 +++++-
lib/toaster/toastergui/views.py | 6 ++++--
lib/toaster/toastermain/settings.py | 17 ++++++-----------
3 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/bin/toaster b/bin/toaster
index 18eba8e..93abcfa 100755
--- a/bin/toaster
+++ b/bin/toaster
@@ -53,7 +53,11 @@ function webserverStartAll()
fi
retval=0
- python $BBBASEDIR/lib/toaster/manage.py syncdb || retval=1
+ 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
+ fi
python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=2
if [ $retval -eq 1 ]; then
echo "Failed db sync, stopping system start" 1>&2
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 9217d8a..c16fc22 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -266,8 +266,10 @@ def _get_queryset(model, queryset, filter_string, search_term, ordering_string,
# if the value is given explicitly as a GET parameter it will be the first selected,
# otherwise the cookie value will be used.
def _get_parameters_values(request, default_count, default_order):
- pagesize = request.GET.get('count', request.COOKIES.get('count', default_count))
- orderby = request.GET.get('orderby', request.COOKIES.get('orderby', default_order))
+ from django.core.urlresolvers import resolve
+ current_url = resolve(request.path_info).url_name
+ pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
+ orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
return (pagesize, orderby)
diff --git a/lib/toaster/toastermain/settings.py b/lib/toaster/toastermain/settings.py
index 80f26e0..225138b 100644
--- a/lib/toaster/toastermain/settings.py
+++ b/lib/toaster/toastermain/settings.py
@@ -258,8 +258,13 @@ TEMPLATE_CONTEXT_PROCESSORS = ('django.contrib.auth.context_processors.auth',
)
INSTALLED_APPS = (
- #'django.contrib.sites',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.messages',
+ 'django.contrib.sessions',
+ 'django.contrib.admin',
'django.contrib.staticfiles',
+
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
'django.contrib.humanize',
@@ -307,16 +312,6 @@ if os.environ.get('TOASTER_DEVEL', None) is not None:
SOUTH_TESTS_MIGRATE = False
-# if we run in managed mode, we need user support
-if MANAGED:
- INSTALLED_APPS = ('django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.messages',
- 'django.contrib.sessions',
- # Uncomment the next line to enable the admin:
- 'django.contrib.admin',
- ) + INSTALLED_APPS
-
# We automatically detect and install applications here if
# they have a 'models.py' or 'views.py' file
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 03/22] toaster logger: refactor recipe and layer file paths
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
2015-05-28 14:13 ` [PATCH 01/22] toaster: Fix build button current project race Alex DAMIAN
2015-05-28 14:13 ` [PATCH 02/22] toaster: enable server-side sessions for all usages Alex DAMIAN
@ 2015-05-28 14:13 ` Alex DAMIAN
2015-05-28 14:13 ` [PATCH 04/22] toaster logger: assign projects to all builds Alex DAMIAN
` (18 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:13 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
This refactoring brings the "local_path" of the
layer from the Layer object to the Layer_Version object, which
is more appropriate as different checkouts of the same
Layer may live in different directories.
This enables us to store Recipe file paths relative to a Layer_Version
at all times, aleviating the need to store full file paths in the
database. We also turn the prefix of the path (e.g. virtual:native
path name space) into a pathflag field.
In turn, this solves the problem of mis-identification of tasks based
on the recipe file paths, since we can also match the namespace of the
file paths on the recipe files.
[YOCTO #7594]
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/bb/ui/buildinfohelper.py | 47 ++-
.../tts/toasteruitest/toaster_automation_test.py | 14 +-
...ion_local_path__add_field_recipe_pathflags__.py | 361 +++++++++++++++++++++
lib/toaster/orm/models.py | 23 +-
lib/toaster/toastergui/templates/bpackage.html | 4 +-
.../toastergui/templates/configuration.html | 2 +-
.../toastergui/templates/package_detail_base.html | 2 +-
lib/toaster/toastergui/templates/recipe.html | 4 +-
lib/toaster/toastergui/templates/recipes.html | 4 +-
lib/toaster/toastergui/templates/target.html | 2 +-
lib/toaster/toastergui/views.py | 22 +-
11 files changed, 427 insertions(+), 58 deletions(-)
create mode 100644 lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py
diff --git a/lib/bb/ui/buildinfohelper.py b/lib/bb/ui/buildinfohelper.py
index 647d663..6812a52 100644
--- a/lib/bb/ui/buildinfohelper.py
+++ b/lib/bb/ui/buildinfohelper.py
@@ -221,12 +221,12 @@ class ORMWrapper(object):
def get_update_recipe_object(self, recipe_information, must_exist = False):
assert 'layer_version' in recipe_information
assert 'file_path' in recipe_information
+ assert 'pathflags' in recipe_information
- if recipe_information['file_path'].startswith(recipe_information['layer_version'].layer.local_path):
- recipe_information['file_path'] = recipe_information['file_path'][len(recipe_information['layer_version'].layer.local_path):].lstrip("/")
+ assert not recipe_information['file_path'].startswith("/") # we should have layer-relative paths at all times
recipe_object, created = self._cached_get_or_create(Recipe, layer_version=recipe_information['layer_version'],
- file_path=recipe_information['file_path'])
+ file_path=recipe_information['file_path'], pathflags = recipe_information['pathflags'])
if created and must_exist:
raise NotExisting("Recipe object created when expected to exist", recipe_information)
@@ -247,13 +247,15 @@ class ORMWrapper(object):
assert 'branch' in layer_version_information
assert 'commit' in layer_version_information
assert 'priority' in layer_version_information
+ assert 'local_path' in layer_version_information
layer_version_object, created = Layer_Version.objects.get_or_create(
build = build_obj,
layer = layer_obj,
branch = layer_version_information['branch'],
commit = layer_version_information['commit'],
- priority = layer_version_information['priority']
+ priority = layer_version_information['priority'],
+ local_path = layer_version_information['local_path'],
)
self.layer_version_objects.append(layer_version_object)
@@ -262,13 +264,11 @@ class ORMWrapper(object):
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
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:
@@ -297,7 +297,6 @@ class ORMWrapper(object):
for pl in buildrequest.project.projectlayer_set.filter(layercommit__layer__name = brl.name):
if pl.layercommit.layer.vcs_url == brl.giturl :
layer = pl.layercommit.layer
- layer.local_path = layer_information['local_path']
layer.save()
return layer
@@ -687,12 +686,12 @@ class BuildInfoHelper(object):
if self.brbe is None:
def _slkey_interactive(layer_version):
assert isinstance(layer_version, Layer_Version)
- return len(layer_version.layer.local_path)
+ return len(layer_version.local_path)
# Heuristics: we always match recipe to the deepest layer path in the discovered layers
for lvo in sorted(self.orm_wrapper.layer_version_objects, reverse=True, key=_slkey_interactive):
# we can match to the recipe file path
- if path.startswith(lvo.layer.local_path):
+ if path.startswith(lvo.local_path):
return lvo
else:
@@ -721,7 +720,7 @@ class BuildInfoHelper(object):
logger.warn("Could not match layer version for recipe path %s : %s" % (path, self.orm_wrapper.layer_version_objects))
#mockup the new layer
- unknown_layer, created = Layer.objects.get_or_create(name="__FIXME__unidentified_layer", local_path="/", layer_index_url="")
+ unknown_layer, created = Layer.objects.get_or_create(name="__FIXME__unidentified_layer", layer_index_url="")
unknown_layer_version_obj, created = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build'])
# append it so we don't run into this error again and again
@@ -731,11 +730,20 @@ class BuildInfoHelper(object):
def _get_recipe_information_from_taskfile(self, taskfile):
localfilepath = taskfile.split(":")[-1]
+ filepath_flags = ":".join(sorted(taskfile.split(":")[:-1]))
layer_version_obj = self._get_layer_version_for_path(localfilepath)
+
+
recipe_info = {}
recipe_info['layer_version'] = layer_version_obj
- recipe_info['file_path'] = taskfile
+ recipe_info['file_path'] = localfilepath
+ recipe_info['pathflags'] = filepath_flags
+
+ if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
+ recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
+ else:
+ raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
return recipe_info
@@ -787,6 +795,7 @@ class BuildInfoHelper(object):
for layer in layerinfos:
try:
self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version']
+ self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path']
except NotExisting as nee:
logger.warn("buildinfohelper: cannot identify layer exception:%s " % nee)
@@ -899,8 +908,8 @@ class BuildInfoHelper(object):
recipe_information = self._get_recipe_information_from_taskfile(taskfile)
try:
- if recipe_information['file_path'].startswith(recipe_information['layer_version'].layer.local_path):
- recipe_information['file_path'] = recipe_information['file_path'][len(recipe_information['layer_version'].layer.local_path):].lstrip("/")
+ if recipe_information['file_path'].startswith(recipe_information['layer_version'].local_path):
+ recipe_information['file_path'] = recipe_information['file_path'][len(recipe_information['layer_version'].local_path):].lstrip("/")
recipe_object = Recipe.objects.get(layer_version = recipe_information['layer_version'],
file_path__endswith = recipe_information['file_path'],
@@ -1051,8 +1060,9 @@ class BuildInfoHelper(object):
self.internal_state['recipes'] = {}
for pn in event._depgraph['pn']:
- file_name = event._depgraph['pn'][pn]['filename']
- layer_version_obj = self._get_layer_version_for_path(file_name.split(":")[-1])
+ file_name = event._depgraph['pn'][pn]['filename'].split(":")[-1]
+ pathflags = ":".join(sorted(event._depgraph['pn'][pn]['filename'].split(":")[:-1]))
+ layer_version_obj = self._get_layer_version_for_path(file_name)
assert layer_version_obj is not None
@@ -1082,6 +1092,13 @@ class BuildInfoHelper(object):
recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker']
recipe_info['file_path'] = file_name
+ recipe_info['pathflags'] = pathflags
+
+ if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path):
+ recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/")
+ else:
+ raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path))
+
recipe = self.orm_wrapper.get_update_recipe_object(recipe_info)
recipe.is_image = False
if 'inherits' in event._depgraph['pn'][pn].keys():
diff --git a/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py b/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
index c03a937..0cb3934 100755
--- a/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
+++ b/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
@@ -926,7 +926,7 @@ class toaster_cases(toaster_cases_base):
self.driver.find_element_by_id("depends_on").click()
self.driver.find_element_by_id("layer_version__branch").click()
self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("layer_version__layer__local_path").click()
+ self.driver.find_element_by_id("layer_version__local_path").click()
self.driver.find_element_by_id("depends_by").click()
self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
@@ -945,7 +945,7 @@ class toaster_cases(toaster_cases_base):
table_head_dict = {'Recipe file':'recipe_file', 'Section':'recipe_section', \
'License':'recipe_license', 'Layer':'layer_version__layer__name', \
- 'Layer branch':'layer_version__branch', 'Layer directory':'layer_version__layer__local_path'}
+ 'Layer branch':'layer_version__branch', 'Layer directory':'layer_version__local_path'}
for key in table_head_dict:
self.find_element_by_link_text_in_table(self.table_name, key).click()
column_list = self.get_table_column_text("class", table_head_dict[key])
@@ -1066,7 +1066,7 @@ class toaster_cases(toaster_cases_base):
self.driver.find_element_by_id("depends_on").click()
self.driver.find_element_by_id("layer_version__branch").click()
self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("layer_version__layer__local_path").click()
+ self.driver.find_element_by_id("layer_version__local_path").click()
self.driver.find_element_by_id("depends_by").click()
self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
# check if columns selected above is shown
@@ -1081,7 +1081,7 @@ class toaster_cases(toaster_cases_base):
self.driver.find_element_by_id("depends_on").click()
self.driver.find_element_by_id("layer_version__branch").click()
self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("layer_version__layer__local_path").click()
+ self.driver.find_element_by_id("layer_version__local_path").click()
self.driver.find_element_by_id("depends_by").click()
self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
# don't exist any more
@@ -1425,7 +1425,7 @@ class toaster_cases(toaster_cases_base):
# Step 4
# pulldown menu
option_ids = ['recipe__layer_version__layer__name', 'recipe__layer_version__branch', \
- 'recipe__layer_version__layer__commit', 'recipe__layer_version__layer__local_path', \
+ 'recipe__layer_version__layer__commit', 'recipe__layer_version__local_path', \
'license', 'recipe__version']
self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
for item in option_ids:
@@ -1470,7 +1470,7 @@ class toaster_cases(toaster_cases_base):
self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
self.driver.find_element_by_id("layer_version__branch").click()
self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("layer_version__layer__local_path").click()
+ self.driver.find_element_by_id("layer_version__local_path").click()
self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
# otable is the recipes table here
otable_head_text = self.get_table_head_text('otable')
@@ -1488,7 +1488,7 @@ class toaster_cases(toaster_cases_base):
self.driver.find_element_by_id("recipe__layer_version__layer__name").click()
self.driver.find_element_by_id("recipe__layer_version__branch").click()
self.driver.find_element_by_id("recipe__layer_version__layer__commit").click()
- self.driver.find_element_by_id("recipe__layer_version__layer__local_path").click()
+ self.driver.find_element_by_id("recipe__layer_version__local_path").click()
self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
otable_head_text = self.get_table_head_text("otable")
for item in ["Layer", "Layer branch", "Layer commit", "Layer directory"]:
diff --git a/lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py b/lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py
new file mode 100644
index 0000000..0ec5795
--- /dev/null
+++ b/lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py
@@ -0,0 +1,361 @@
+# -*- 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):
+ # Removing unique constraint on 'Recipe', fields ['layer_version', 'file_path']
+ db.delete_unique(u'orm_recipe', ['layer_version_id', 'file_path'])
+
+ # Adding field 'Layer_Version.local_path'
+ db.add_column(u'orm_layer_version', 'local_path',
+ self.gf('django.db.models.fields.FilePathField')(default="/", max_length=1024),
+ keep_default=False)
+
+ # Adding field 'Recipe.pathflags'
+ db.add_column(u'orm_recipe', 'pathflags',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=200, blank=True),
+ keep_default=False)
+
+ # Adding unique constraint on 'Recipe', fields ['layer_version', 'file_path', 'pathflags']
+ db.create_unique(u'orm_recipe', ['layer_version_id', 'file_path', 'pathflags'])
+
+ # Migrate data from Layer.local_path to Layer_Version.local_path
+ if not db.dry_run:
+ for lv in orm.Layer_Version.objects.all():
+ if lv.layer.local_path is not None:
+ lv.local_path = lv.layer.local_path
+ else:
+ lv.local_path = "/"
+ lv.save()
+
+ db.delete_column(u'orm_layer', 'local_path')
+
+
+ def backwards(self, orm):
+ raise RuntimeError("Cannot reverse this migration")
+
+
+ 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']"}),
+ '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.buildartifact': {
+ 'Meta': {'object_name': 'BuildArtifact'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+ 'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+ 'file_size': ('django.db.models.fields.IntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 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'}),
+ 'local_path': ('django.db.models.fields.FilePathField', [], {'default': 'None', 'max_length': '255', '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']", 'null': 'True'}),
+ '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']", 'null': 'True'}),
+ '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', 'pathflags'), ('file_path', 'pathflags'))", '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'}),
+ 'pathflags': ('django.db.models.fields.CharField', [], {'max_length': '200', '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': '8', 'decimal_places': '2'}),
+ 'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', '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': "'tasks'", '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']
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index 20b02bb..29ede65 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -457,7 +457,7 @@ class Task_Dependency(models.Model):
depends_on = models.ForeignKey(Task, related_name='task_dependencies_depends')
class Package(models.Model):
- search_allowed_fields = ['name', 'version', 'revision', 'recipe__name', 'recipe__version', 'recipe__license', 'recipe__layer_version__layer__name', 'recipe__layer_version__branch', 'recipe__layer_version__commit', 'recipe__layer_version__layer__local_path', 'installed_name']
+ search_allowed_fields = ['name', 'version', 'revision', 'recipe__name', 'recipe__version', 'recipe__license', 'recipe__layer_version__layer__name', 'recipe__layer_version__branch', 'recipe__layer_version__commit', 'recipe__layer_version__local_path', 'installed_name']
build = models.ForeignKey('Build')
recipe = models.ForeignKey('Recipe', null=True)
name = models.CharField(max_length=100)
@@ -528,7 +528,7 @@ class Package_File(models.Model):
size = models.IntegerField()
class Recipe(models.Model):
- search_allowed_fields = ['name', 'version', 'file_path', 'section', 'summary', 'description', 'license', 'layer_version__layer__name', 'layer_version__branch', 'layer_version__commit', 'layer_version__layer__local_path', 'layer_version__layer_source__name']
+ search_allowed_fields = ['name', 'version', 'file_path', 'section', 'summary', 'description', 'license', 'layer_version__layer__name', 'layer_version__branch', 'layer_version__commit', 'layer_version__local_path', 'layer_version__layer_source__name']
layer_source = models.ForeignKey('LayerSource', default = None, null = True) # from where did we get this recipe
up_id = models.IntegerField(null = True, default = None) # id of entry in the source
@@ -544,6 +544,7 @@ class Recipe(models.Model):
homepage = models.URLField(blank=True)
bugtracker = models.URLField(blank=True)
file_path = models.FilePathField(max_length=255)
+ pathflags = models.CharField(max_length=200, blank=True)
def get_layersource_view_url(self):
if self.layer_source is None:
@@ -555,18 +556,6 @@ class Recipe(models.Model):
def __unicode__(self):
return "Recipe " + self.name + ":" + self.version
- def get_local_path(self):
- if settings.MANAGED and self.layer_version.build is not None and self.layer_version.build.project is not None:
- # strip any tag prefixes ('virtual:native:')
- layer_path=self.layer_version.layer.local_path.split(":")[-1]
- recipe_path=self.file_path.split(":")[-1]
- if 0 == recipe_path.find(layer_path):
- return recipe_path[len(layer_path)+1:]
- else:
- return recipe_path
-
- return self.file_path
-
def get_vcs_recipe_file_link_url(self):
return self.layer_version.get_vcs_file_link_url(self.file_path)
@@ -579,7 +568,8 @@ class Recipe(models.Model):
return ""
class Meta:
- unique_together = ("layer_version", "file_path")
+ unique_together = (("layer_version", "file_path", "pathflags"), )
+
class Recipe_DependencyManager(models.Manager):
use_for_related_fields = True
@@ -976,7 +966,6 @@ class Layer(models.Model):
up_date = models.DateTimeField(null = True, default = None)
name = models.CharField(max_length=100)
- local_path = models.FilePathField(max_length=255, null = True, default = None)
layer_index_url = models.URLField()
vcs_url = GitURLField(default = None, null = True)
vcs_web_url = models.URLField(null = True, default = None)
@@ -1009,6 +998,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
+ local_path = models.FilePathField(max_length=1024, default = "/") # where this layer was checked-out
+
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/
diff --git a/lib/toaster/toastergui/templates/bpackage.html b/lib/toaster/toastergui/templates/bpackage.html
index 6fbdc7b..1c47354 100644
--- a/lib/toaster/toastergui/templates/bpackage.html
+++ b/lib/toaster/toastergui/templates/bpackage.html
@@ -89,7 +89,7 @@
</td>
<!-- Layer directory -->
{% if not MANAGED or not build.project %}
- <td class="recipe__layer_version__layer__local_path">{{package.recipe.layer_version.layer.local_path}}</td>
+ <td class="recipe__layer_version__local_path">{{package.recipe.layer_version.local_path}}</td>
{% endif %}
{%else%}
<td class="recipe__name"></td>
@@ -97,7 +97,7 @@
<td class="recipe__layer_version__layer__name"></td>
<td class="recipe__layer_version__branch"></td>
<td class="recipe__layer_version__layer__commit"></td>
- <td class="recipe__layer_version__layer__local_path"></td>
+ <td class="recipe__layer_version__local_path"></td>
{%endif%}
</tr>
diff --git a/lib/toaster/toastergui/templates/configuration.html b/lib/toaster/toastergui/templates/configuration.html
index d3b34a2..2aa1ae1 100644
--- a/lib/toaster/toastergui/templates/configuration.html
+++ b/lib/toaster/toastergui/templates/configuration.html
@@ -64,7 +64,7 @@
{{lv.commit|truncatechars:13}}
</a></td>
{% if not MANAGED or not build.project %}
- <td>{{lv.layer.local_path}}</td>
+ <td>{{lv.local_path}}</td>
{% endif %}
</tr>{% endfor %}
</tbody>
diff --git a/lib/toaster/toastergui/templates/package_detail_base.html b/lib/toaster/toastergui/templates/package_detail_base.html
index ad75454..e2ec75d 100644
--- a/lib/toaster/toastergui/templates/package_detail_base.html
+++ b/lib/toaster/toastergui/templates/package_detail_base.html
@@ -141,7 +141,7 @@
Layer directory
<i class="icon-question-sign get-help" title="Path to the layer providing the recipe that builds this package"></i>
</dt>
- <dd><code>{{package.recipe.layer_version.layer.local_path}}</code></dd>
+ <dd><code>{{package.recipe.layer_version.local_path}}</code></dd>
{% endif %}
</dl>
</div> <!-- row4 well -->
diff --git a/lib/toaster/toastergui/templates/recipe.html b/lib/toaster/toastergui/templates/recipe.html
index a0fe6d7..2da41c3 100644
--- a/lib/toaster/toastergui/templates/recipe.html
+++ b/lib/toaster/toastergui/templates/recipe.html
@@ -58,13 +58,13 @@
<i class="icon-question-sign get-help" title="Path to the layer providing the recipe"></i>
Layer directory
</dt>
- <dd><code>{{layer.local_path}}</code></dd>
+ <dd><code>{{object.layer_version.local_path}}</code></dd>
{% endif %}
<dt>
<i class="icon-question-sign get-help" title="Path to the recipe .bb file"></i>
Recipe file
</dt>
- <dd><code>{{object.get_local_path}}</code></dd>
+ <dd><code>{{object.file_path}} {% if object.pathflags %}<i>({{object.pathflags}})</i>{% endif %}</code></dd>
{% if layer_version.branch %}
<dt>
<i class="icon-question-sign get-help" title="The Git branch of the layer providing the recipe"></i>
diff --git a/lib/toaster/toastergui/templates/recipes.html b/lib/toaster/toastergui/templates/recipes.html
index dc2d9e8..8d4494e 100644
--- a/lib/toaster/toastergui/templates/recipes.html
+++ b/lib/toaster/toastergui/templates/recipes.html
@@ -84,7 +84,7 @@
{% endwith %}
</td>
<!-- Recipe file -->
- <td class="recipe_file">{{recipe.get_local_path}}</td>
+ <td class="recipe_file">{{recipe.file_path}} {% if recipe.pathflags %}<i>({{recipe.pathflags}})</i>{% endif %}</td>
<!-- Section -->
<td class="recipe_section">{{recipe.section}}</td>
<!-- License -->
@@ -105,7 +105,7 @@
{% if not MANAGED or not build.project %}
<!-- Layer directory -->
- <td class="layer_version__layer__local_path">{{recipe.layer_version.layer.local_path}}</td>
+ <td class="layer_version__local_path">{{recipe.layer_version.local_path}}</td>
{% endif %}
</tr>
diff --git a/lib/toaster/toastergui/templates/target.html b/lib/toaster/toastergui/templates/target.html
index 1309b52..e7febaf 100644
--- a/lib/toaster/toastergui/templates/target.html
+++ b/lib/toaster/toastergui/templates/target.html
@@ -154,7 +154,7 @@
</td>
{% if not MANAGED or not build.project %}
<td class="layer_directory">
- {{ package.recipe.layer_version.layer.local_path }}
+ {{ package.recipe.layer_version.local_path }}
</td>
{% endif %}
</tr>
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index c16fc22..761bf53 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -725,9 +725,9 @@ eans multiple licenses exist that cover different parts of the source',
tc_layerDir = {
'name':'Layer directory',
'qhelp':'Location in disk of the layer providing the recipe that builds the package',
- 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__local_path" ),
- 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__local_path" ),
- 'orderkey' : "recipe__layer_version__layer__local_path",
+ 'orderfield' : _get_toggle_order( request, "recipe__layer_version__local_path" ),
+ 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__local_path" ),
+ 'orderkey' : "recipe__layer_version__local_path",
'clclass' : 'layer_directory',
'hidden' : 1,
}
@@ -1254,10 +1254,10 @@ def recipes(request, build_id):
{
'name':'Layer directory',
'qhelp':'Path to the layer prodiving the recipe',
- 'orderfield': _get_toggle_order(request, "layer_version__layer__local_path"),
- 'ordericon':_get_toggle_order_icon(request, "layer_version__layer__local_path"),
- 'orderkey' : 'layer_version__layer__local_path',
- 'clclass': 'layer_version__layer__local_path', 'hidden': 1,
+ 'orderfield': _get_toggle_order(request, "layer_version__local_path"),
+ 'ordericon':_get_toggle_order_icon(request, "layer_version__local_path"),
+ 'orderkey' : 'layer_version__local_path',
+ 'clclass': 'layer_version__local_path', 'hidden': 1,
})
@@ -1464,10 +1464,10 @@ def bpackage(request, build_id):
tc_layerDir = {
'name':'Layer directory',
'qhelp':'Path to the layer providing the recipe that builds the package',
- 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__local_path"),
- 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__local_path"),
- 'orderkey' : 'recipe__layer_version__layer__local_path',
- 'clclass': 'recipe__layer_version__layer__local_path', 'hidden': 1,
+ 'orderfield': _get_toggle_order(request, "recipe__layer_version__local_path"),
+ 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__local_path"),
+ 'orderkey' : 'recipe__layer_version__local_path',
+ 'clclass': 'recipe__layer_version__local_path', 'hidden': 1,
}
context['tablecols'].append(tc_layerDir)
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 04/22] toaster logger: assign projects to all builds
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (2 preceding siblings ...)
2015-05-28 14:13 ` [PATCH 03/22] toaster logger: refactor recipe and layer file paths Alex DAMIAN
@ 2015-05-28 14:13 ` Alex DAMIAN
2015-05-28 14:13 ` [PATCH 05/22] toastergui: table header bring back Alex DAMIAN
` (17 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:13 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
Bitbake logger now sets a default project if the TOASTER_BRBE
or TOASTER_PROJECT Bitbake variables are not set.
This a necessary step in getting all builds under a project,
as to unify the MANAGED and interactive modes.
Other small fixes are included, related to the size of the
fields in the database.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/bb/ui/buildinfohelper.py | 37 ++-
...ect__chg_field_project_bitbake_version__chg_.py | 368 +++++++++++++++++++++
lib/toaster/orm/models.py | 15 +-
lib/toaster/orm/views.py | 2 +-
4 files changed, 405 insertions(+), 17 deletions(-)
create mode 100644 lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py
diff --git a/lib/bb/ui/buildinfohelper.py b/lib/bb/ui/buildinfohelper.py
index 6812a52..3ea842c 100644
--- a/lib/bb/ui/buildinfohelper.py
+++ b/lib/bb/ui/buildinfohelper.py
@@ -31,6 +31,9 @@ from toaster.orm.models import Variable, VariableHistory
from toaster.orm.models import Package, Package_File, Target_Installed_Package, Target_File
from toaster.orm.models import Task_Dependency, Package_Dependency
from toaster.orm.models import Recipe_Dependency
+
+from toaster.orm.models import Project
+
from bb.msg import BBLogFormatter as format
from django.db import models
from pprint import pformat
@@ -103,7 +106,7 @@ class ORMWrapper(object):
return vars(self)[dictname][key]
- def create_build_object(self, build_info, brbe):
+ def create_build_object(self, build_info, brbe, project_id):
assert 'machine' in build_info
assert 'distro' in build_info
assert 'distro_version' in build_info
@@ -112,7 +115,26 @@ class ORMWrapper(object):
assert 'build_name' in build_info
assert 'bitbake_version' in build_info
+ prj = None
+ buildrequest = None
+ if brbe is not None: # this build was triggered by a request from a user
+ logger.debug(1, "buildinfohelper: brbe is %s" % brbe)
+ from bldcontrol.models import BuildEnvironment, BuildRequest
+ br, be = brbe.split(":")
+ buildrequest = BuildRequest.objects.get(pk = br)
+ prj = buildrequest.project
+
+ elif project_id is not None: # this build was triggered by an external system for a specific project
+ logger.debug(1, "buildinfohelper: project is %s" % prj)
+ prj = Project.objects.get(pk = project_id)
+
+ else: # this build was triggered by a legacy system, or command line interactive mode
+ prj, created = Project.objects.get_or_create(pk=0, name="Default Project")
+ logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj)
+
+
build = Build.objects.create(
+ project = prj,
machine=build_info['machine'],
distro=build_info['distro'],
distro_version=build_info['distro_version'],
@@ -123,17 +145,11 @@ class ORMWrapper(object):
bitbake_version=build_info['bitbake_version'])
logger.debug(1, "buildinfohelper: build is created %s" % build)
- if brbe is not None:
- logger.debug(1, "buildinfohelper: brbe is %s" % brbe)
- from bldcontrol.models import BuildEnvironment, BuildRequest
- br, be = brbe.split(":")
- buildrequest = BuildRequest.objects.get(pk = br)
+ if buildrequest is not None:
buildrequest.build = build
buildrequest.save()
- build.project_id = buildrequest.project_id
- build.save()
return build
def create_target_objects(self, target_info):
@@ -638,6 +654,7 @@ class BuildInfoHelper(object):
self.has_build_history = has_build_history
self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0]
self.brbe = self.server.runCommand(["getVariable", "TOASTER_BRBE"])[0]
+ self.project = self.server.runCommand(["getVariable", "TOASTER_PROJECT"])[0]
logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self))
@@ -804,7 +821,7 @@ class BuildInfoHelper(object):
assert '_pkgs' in vars(event)
build_information = self._get_build_information()
- build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe)
+ build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe, self.project)
self.internal_state['build'] = build_obj
@@ -1233,7 +1250,7 @@ class BuildInfoHelper(object):
logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent)
self.store_log_event(tempevent)
else:
- logger.error("buildinfohelper: Events not saved: %s" % self.internal_state['backlog'])
+ logger.info("buildinfohelper: All events saved")
del self.internal_state['backlog']
log_information = {}
diff --git a/lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py b/lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py
new file mode 100644
index 0000000..b82cc8d
--- /dev/null
+++ b/lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py
@@ -0,0 +1,368 @@
+# -*- 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):
+
+ # Changing field 'Build.project'
+ db.alter_column(u'orm_build', 'project_id', self.gf('django.db.models.fields.related.ForeignKey')(default=0, to=orm['orm.Project']))
+
+ # Changing field 'Project.bitbake_version'
+ db.alter_column(u'orm_project', 'bitbake_version_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.BitbakeVersion'], null=True))
+
+ # Changing field 'Project.release'
+ db.alter_column(u'orm_project', 'release_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.Release'], null=True))
+
+ # Changing field 'Task.cpu_usage'
+ db.alter_column(u'orm_task', 'cpu_usage', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=8, decimal_places=2))
+
+ # Changing field 'Task.elapsed_time'
+ db.alter_column(u'orm_task', 'elapsed_time', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=8, decimal_places=2))
+
+ def backwards(self, orm):
+
+ # Changing field 'Build.project'
+ db.alter_column(u'orm_build', 'project_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.Project'], null=True))
+
+ # User chose to not deal with backwards NULL issues for 'Project.bitbake_version'
+ raise RuntimeError("Cannot reverse this migration. 'Project.bitbake_version' and its values cannot be restored.")
+
+ # The following code is provided here to aid in writing a correct migration
+ # Changing field 'Project.bitbake_version'
+ db.alter_column(u'orm_project', 'bitbake_version_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.BitbakeVersion']))
+
+ # User chose to not deal with backwards NULL issues for 'Project.release'
+ raise RuntimeError("Cannot reverse this migration. 'Project.release' and its values cannot be restored.")
+
+ # The following code is provided here to aid in writing a correct migration
+ # Changing field 'Project.release'
+ db.alter_column(u'orm_project', 'release_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.Release']))
+
+ # Changing field 'Task.cpu_usage'
+ db.alter_column(u'orm_task', 'cpu_usage', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=2))
+
+ # Changing field 'Task.elapsed_time'
+ db.alter_column(u'orm_task', 'elapsed_time', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=6, decimal_places=2))
+
+ 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']"}),
+ '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.buildartifact': {
+ 'Meta': {'object_name': 'BuildArtifact'},
+ 'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+ 'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+ 'file_size': ('django.db.models.fields.IntegerField', [], {}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+ },
+ 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']", 'null': 'True'}),
+ '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']", 'null': 'True'}),
+ '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': '8', 'decimal_places': '2'}),
+ 'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+ 'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', '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': "'tasks'", '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
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index 29ede65..0f72576 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -81,15 +81,18 @@ class ProjectManager(models.Manager):
def create(self, *args, **kwargs):
raise Exception("Invalid call to Project.objects.create. Use Project.objects.create_project() to create a project")
- def get_or_create(self, *args, **kwargs):
+ def get_or_create(self, **kwargs):
+ # allow project creation for default data
+ if 'pk' in kwargs and kwargs['pk'] == 0:
+ return super(ProjectManager, self).get_or_create(**kwargs)
raise Exception("Invalid call to Project.objects.get_or_create. Use Project.objects.create_project() to create a project")
class Project(models.Model):
search_allowed_fields = ['name', 'short_description', 'release__name', 'release__branch_name']
name = models.CharField(max_length=100)
short_description = models.CharField(max_length=50, blank=True)
- bitbake_version = models.ForeignKey('BitbakeVersion')
- release = models.ForeignKey("Release")
+ bitbake_version = models.ForeignKey('BitbakeVersion', null=True)
+ release = models.ForeignKey("Release", null=True)
created = models.DateTimeField(auto_now_add = True)
updated = models.DateTimeField(auto_now = True)
# This is a horrible hack; since Toaster has no "User" model available when
@@ -223,7 +226,7 @@ class Build(models.Model):
search_allowed_fields = ['machine', 'cooker_log_path', "target__target", "target__target_image_file__file_name"]
- project = models.ForeignKey(Project, null = True)
+ project = models.ForeignKey(Project) # must have a project
machine = models.CharField(max_length=100)
distro = models.CharField(max_length=100)
distro_version = models.CharField(max_length=100)
@@ -435,8 +438,8 @@ class Task(models.Model):
script_type = models.IntegerField(choices=TASK_CODING, default=CODING_NA)
line_number = models.IntegerField(default=0)
disk_io = models.IntegerField(null=True)
- cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, null=True)
- elapsed_time = models.DecimalField(max_digits=6, decimal_places=2, null=True)
+ cpu_usage = models.DecimalField(max_digits=8, decimal_places=2, null=True)
+ elapsed_time = models.DecimalField(max_digits=8, decimal_places=2, null=True)
sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=SSTATE_NA)
message = models.CharField(max_length=240)
logfile = models.FilePathField(max_length=255, blank=True)
diff --git a/lib/toaster/orm/views.py b/lib/toaster/orm/views.py
index 61d14f9..f32fa4d 100644
--- a/lib/toaster/orm/views.py
+++ b/lib/toaster/orm/views.py
@@ -37,7 +37,7 @@ from django.views.decorators.csrf import csrf_exempt
def eventfile(request):
""" Receives a file by POST, and runs toaster-eventreply on this file """
if request.method != "POST":
- return HttpResponseBadRequest("This API only accepts POST requests. Post a file with:\n\ncurl -F eventlog=@bitbake_eventlog.json http[s]://[server-address]/orm/eventfile\n", content_type="text/plain;utf8")
+ return HttpResponseBadRequest("This API only accepts POST requests. Post a file with:\n\ncurl -F eventlog=@bitbake_eventlog.json %s\n" % request.build_absolute_uri(reverse('eventfile')), content_type="text/plain;utf8")
# write temporary file
(handle, abstemppath) = tempfile.mkstemp(dir="/tmp/")
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 05/22] toastergui: table header bring back
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (3 preceding siblings ...)
2015-05-28 14:13 ` [PATCH 04/22] toaster logger: assign projects to all builds Alex DAMIAN
@ 2015-05-28 14:13 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 06/22] toastergui: show relative paths in configvars view Alex DAMIAN
` (16 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:13 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
This patch brings back the table header, which I mistakenly
took out when fixing HTML5 compliance.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/toastergui/static/js/table.js | 2 +-
lib/toaster/toastergui/templates/toastertable.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/toaster/toastergui/static/js/table.js b/lib/toaster/toastergui/static/js/table.js
index 8f5bb2e..97370fd 100644
--- a/lib/toaster/toastergui/static/js/table.js
+++ b/lib/toaster/toastergui/static/js/table.js
@@ -163,7 +163,7 @@ function tableInit(ctx){
if (tableChromeDone === true)
return;
- var tableHeadRow = table.find("thead tr");
+ var tableHeadRow = table.find("thead#tableheader");
var editColMenu = $("#table-chrome-"+ctx.tableName).find(".editcol");
tableHeadRow.html("");
diff --git a/lib/toaster/toastergui/templates/toastertable.html b/lib/toaster/toastergui/templates/toastertable.html
index 598d295..21392bb 100644
--- a/lib/toaster/toastergui/templates/toastertable.html
+++ b/lib/toaster/toastergui/templates/toastertable.html
@@ -92,7 +92,7 @@
<!-- The actual table -->
<table class="table table-bordered table-hover tablesorter" id="{{table_name}}">
- <thead>
+ <thead id="tableheader">
</thead>
<tbody></tbody>
</table>
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 06/22] toastergui: show relative paths in configvars view
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (4 preceding siblings ...)
2015-05-28 14:13 ` [PATCH 05/22] toastergui: table header bring back Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 07/22] toaster: Fix toastertable header Alex DAMIAN
` (15 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Ed Bartosh <ed.bartosh@linux.intel.com>
Reworked filtering of config paths.
Stripped clone paths, topdir and its parent directory from the paths
to config files in configvars view.
[YOCTO #7463]
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
lib/toaster/toastergui/templates/configvars.html | 10 +--
lib/toaster/toastergui/templatetags/projecttags.py | 76 +++++++---------------
lib/toaster/toastergui/views.py | 19 ++++--
3 files changed, 42 insertions(+), 63 deletions(-)
diff --git a/lib/toaster/toastergui/templates/configvars.html b/lib/toaster/toastergui/templates/configvars.html
index 3e4c7e8..42c42a5 100644
--- a/lib/toaster/toastergui/templates/configvars.html
+++ b/lib/toaster/toastergui/templates/configvars.html
@@ -54,9 +54,11 @@
<td class="variable_name"><a data-toggle="modal" href="#variable-{{variable.pk}}">{{variable.variable_name}}</a></td>
<td class="variable_value"><a data-toggle="modal" href="#variable-{{variable.pk}}">{{variable.variable_value|truncatechars:153}}</a></td>
<td class="file"><a data-toggle="modal" href="#variable-{{variable.pk}}">
- {% if variable.vhistory.all %} {% autoescape off %}
- {{variable.vhistory.all | filter_setin_files:file_filter | cut_layer_path_prefix:layer_names}}
- {% endautoescape %} {% endif %}
+ {% if variable.vhistory.all %}
+ {% for path in variable.vhistory.all|filter_setin_files:file_filter %}
+ {{path|cut_path_prefix:dirstostrip}}<p>
+ {% endfor %}
+ {% endif %}
</a></td>
<td class="description">
{% if variable.description %}
@@ -115,7 +117,7 @@
<tbody>
{% for vh in variable.vhistory.all %}
<tr>
- <td>{{forloop.counter}}</td><td>{{vh.file_name|cut_layer_path_prefix:layer_names}}</td><td>{{vh.operation}}</td><td>{{vh.line_number}}</td>
+ <td>{{forloop.counter}}</td><td>{{vh.file_name|cut_path_prefix:dirstostrip}}</td><td>{{vh.operation}}</td><td>{{vh.line_number}}</td>
</tr>
{%endfor%}
</tbody>
diff --git a/lib/toaster/toastergui/templatetags/projecttags.py b/lib/toaster/toastergui/templatetags/projecttags.py
index 54700e3..1bc3bc2 100644
--- a/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/lib/toaster/toastergui/templatetags/projecttags.py
@@ -20,6 +20,7 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from datetime import datetime, timedelta
+from os.path import relpath
import re
from django import template
from django.utils import timezone
@@ -182,47 +183,23 @@ def variable_parent_name(value):
return re.sub('_[a-z].*', '', value)
@register.filter
-def filter_setin_files(file_list,matchstr):
- """ filter/search the 'set in' file lists. Note
- that this output is not autoescaped to allow
- the <p> marks, but this is safe as the data
- is file paths
- """
-
- # no filters, show last file (if any)
- if matchstr == ":":
- if file_list:
- return file_list[len(file_list)-1].file_name
- else:
- return ''
-
- search, filter = matchstr.partition(':')[::2]
- htmlstr=""
- # match only filters
- if search == '':
- for i in range(len(file_list)):
- if re.search(filter, file_list[i].file_name):
- if htmlstr.find(file_list[i].file_name + "<p>") < 0:
- htmlstr += file_list[i].file_name + "<p>"
- return htmlstr
-
- # match only search string, plus always last file
- if filter == "":
- for i in range(len(file_list)-1):
- if re.search(search,file_list[i].file_name):
- if htmlstr.find(file_list[i].file_name + "<p>") < 0:
- htmlstr += file_list[i].file_name + "<p>"
- if htmlstr.find(file_list[len(file_list)-1].file_name) < 0:
- htmlstr += file_list[len(file_list)-1].file_name
- return htmlstr
-
- # match filter or search string
- for i in range(len(file_list)):
- if re.search(filter, file_list[i].file_name) or re.search(search,file_list[i].file_name):
- if htmlstr.find(file_list[i].file_name + "<p>") < 0:
- htmlstr += file_list[i].file_name + "<p>"
- return htmlstr
-
+def filter_setin_files(file_list, matchstr):
+ """Filter/search the 'set in' file lists."""
+ result = []
+ search, filter = matchstr.split(':')
+ for pattern in (search, filter):
+ if pattern:
+ for fobj in file_list:
+ fname = fobj.file_name
+ if fname not in result and re.search(pattern, fname):
+ result.append(fname)
+
+ # no filter, show last file (if any)
+ last = list(file_list)[-1].file_name
+ if not filter and last not in result:
+ result.append(last)
+
+ return result
@register.filter
def string_slice(strvar,slicevar):
@@ -313,16 +290,9 @@ def is_shaid(text):
return False
@register.filter
-def cut_layer_path_prefix(fullpath,layer_names):
- ### if some part of the full local path to a layer matches
- ### an entry in layer_names (sorted desc), return the layer
- ### name relative path.
- for lname in layer_names:
- # import rpdb; rpdb.set_trace()
- # only try layer names that are non-trivial to avoid false matches
- if len(lname) >= 4:
- # match layer name with as a subdir / or for remote layers /_
- if re.search('/' + lname, fullpath) or re.search('/_' + lname, fullpath):
- parts = re.split(lname, fullpath, 1)
- return lname + parts[1]
+def cut_path_prefix(fullpath, prefixes):
+ """Cut path prefix from fullpath."""
+ for prefix in prefixes:
+ if fullpath.startswith(prefix):
+ return relpath(fullpath, prefix)
return fullpath
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index 761bf53..d451c3b 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -28,6 +28,8 @@ from django.shortcuts import render, redirect
from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency
from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact
+from bldcontrol.models import BuildEnvironment, BuildRequest
+from bldcontrol import bbcontroller
from django.views.decorators.cache import cache_control
from django.core.urlresolvers import reverse
from django.core.exceptions import MultipleObjectsReturned
@@ -39,6 +41,7 @@ from datetime import timedelta, datetime, date
from django.utils import formats
from toastergui.templatetags.projecttags import json as jsonfilter
import json
+from os.path import dirname
# all new sessions should come through the landing page;
# determine in which mode we are running in, and redirect appropriately
@@ -1300,11 +1303,6 @@ def configvars(request, build_id):
variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
- layers = Layer.objects.filter(layer_version_layer__projectlayer__project__build=build_id).order_by("-name")
- layer_names = map(lambda layer : layer.name, layers)
- # special case for meta built-in layer
- layer_names.append('meta')
-
# show all matching files (not just the last one)
file_filter= search_term + ":"
if filter_string.find('/conf/') > 0:
@@ -1317,6 +1315,15 @@ def configvars(request, build_id):
file_filter += '/bitbake.conf'
build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
+ clones = []
+ for breq in BuildRequest.objects.filter(build_id=build_id):
+ bc = bbcontroller.getBuildEnvironmentController(pk = breq.environment.id)
+ for brl in breq.brlayer_set.all():
+ localdirname = bc.getGitCloneDirectory(brl.giturl, brl.commit)
+ if not localdirname.startswith("/"):
+ localdirname = os.path.join(bc.be.sourcedir, localdirname)
+ clones.append(localdirname)
+
context = {
'objectname': 'configvars',
'object_search_display':'BitBake variables',
@@ -1327,7 +1334,7 @@ def configvars(request, build_id):
'total_count':queryset_with_search.count(),
'default_orderby' : 'variable_name:+',
'search_term':search_term,
- 'layer_names' : layer_names,
+ 'dirstostrip': clones + [dirname(build_dir), dirname(dirname(build_dir))],
# Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
'tablecols' : [
{'name': 'Variable',
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 07/22] toaster: Fix toastertable header
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (5 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 06/22] toastergui: show relative paths in configvars view Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 08/22] toaster: Add a simplified ToasterTable template Alex DAMIAN
` (14 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
Fixes regression introduced by 'tables updates for HTML5 compliance'
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/toastergui/templates/toastertable.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/toaster/toastergui/templates/toastertable.html b/lib/toaster/toastergui/templates/toastertable.html
index 21392bb..4e6c4ce 100644
--- a/lib/toaster/toastergui/templates/toastertable.html
+++ b/lib/toaster/toastergui/templates/toastertable.html
@@ -93,6 +93,7 @@
<!-- The actual table -->
<table class="table table-bordered table-hover tablesorter" id="{{table_name}}">
<thead id="tableheader">
+ <tr><th></th></tr>
</thead>
<tbody></tbody>
</table>
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 08/22] toaster: Add a simplified ToasterTable template
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (6 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 07/22] toaster: Fix toastertable header Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 09/22] toaster: Port layerdetails to ToasterTables Alex DAMIAN
` (13 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
This is like the normal ToasterTable template but with a number of
features removed such as edit columns and has a lower profile compared
to the "main" tables.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
.../toastergui/templates/toastertable-simple.html | 112 +++++++++++++++++++++
1 file changed, 112 insertions(+)
create mode 100644 lib/toaster/toastergui/templates/toastertable-simple.html
diff --git a/lib/toaster/toastergui/templates/toastertable-simple.html b/lib/toaster/toastergui/templates/toastertable-simple.html
new file mode 100644
index 0000000..98cad64
--- /dev/null
+++ b/lib/toaster/toastergui/templates/toastertable-simple.html
@@ -0,0 +1,112 @@
+
+{% load static %}
+{% load projecttags %}
+
+<script src="{% static 'js/table.js' %}"></script>
+<script src="{% static 'js/layerBtn.js' %}"></script>
+<script>
+ $(document).ready(function() {
+ (function(){
+
+ var ctx = {
+ tableName : "{{table_name}}",
+ url : "{{ xhr_table_url }}",
+ title : "{{title}}",
+ projectLayers : {{projectlayers|json}},
+ };
+
+ try {
+ tableInit(ctx);
+ } catch (e) {
+ document.write("Problem loading table widget: " + e);
+ }
+ })();
+ });
+</script>
+
+<!-- filter modal -->
+<div id="filter-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false">
+ <form id="filter-modal-form" style="margin-bottom: 0px">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+ <h3 id="filter-modal-title"> </h3>
+ </div>
+ <div class="modal-body">
+ <p>Show:</p>
+ <span id="filter-actions"></span>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" type="submit">Apply</button>
+ </div>
+ </form>
+</div>
+<button id="clear-filter-btn" style="display:none"></button>
+
+<div class="row-fluid" id="no-results-{{table_name}}" style="display:none">
+ <div class="alert">
+ <form class="no-results input-append">
+ <input class="input-xlarge" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{{request.GET.search}}"/>
+ <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1">
+ <i class="icon-remove"></i>
+ </a>
+ <button class="btn search-submit-{{table_name}}" >Search</button>
+ <button class="btn btn-link remove-search-btn-{{table_name}}">Show {{title|lower}}
+ </button>
+ </form>
+ </div>
+</div>
+
+
+<div id="table-container-{{table_name}}">
+ <!-- control header -->
+ <div class="row-fluid" id="table-chrome-{{table_name}}">
+ <div class="navbar-search input-append pull-left">
+
+ <input class="input-xlarge" id="search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{{request.GET.search}}"/>
+ <a href="#" style="display:none" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1">
+ <i class="icon-remove"></i>
+ </a>
+ <button class="btn" id="search-submit-{{table_name}}" >Search</button>
+ </div>
+
+ <div class="pull-right">
+
+ <div style="display:inline">
+ <span class="divider-vertical"></span>
+ <span class="help-inline" style="padding-top:5px;">Show rows:</span>
+ <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}">
+ {% with "10 25 50 100 150" as list%}
+ {% for i in list.split %}
+ <option value="{{i}}">{{i}}</option>
+ {% endfor %}
+ {% endwith %}
+ </select>
+ </div>
+ </div>
+ </div>
+
+ <!-- The actual table -->
+ <table class="table table-bordered table-hover tablesorter" id="{{table_name}}">
+ <thead>
+ <tr><th></th></tr>
+ </thead>
+ <tbody></tbody>
+ </table>
+
+ <!-- Pagination controls -->
+ <div class="pagination pagination-centered">
+ <ul id="pagination-{{table_name}}" class="pagination" style="display: block-inline">
+ </ul>
+
+ <div class="pull-right">
+ <span class="help-inline" style="padding-top:5px;">Show rows:</span>
+ <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}">
+ {% with "10 25 50 100 150" as list%}
+ {% for i in list.split %}
+ <option value="{{i}}">{{i}}</option>
+ {% endfor %}
+ {% endwith %}
+ </select>
+ </div>
+ </div>
+</div>
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 09/22] toaster: Port layerdetails to ToasterTables
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (7 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 08/22] toaster: Add a simplified ToasterTable template Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 10/22] toaster: Add an invalidate cache method on data update signal Alex DAMIAN
` (12 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
This ports the layerdetails page to using ToasterTables
Also some whitespace and strict clean ups in the existing layerdetails
js and html template.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/toastergui/static/js/layerdetails.js | 70 ++-
lib/toaster/toastergui/static/js/libtoaster.js | 3 +
lib/toaster/toastergui/static/js/table.js | 77 ++-
lib/toaster/toastergui/tables.py | 87 ++-
lib/toaster/toastergui/templates/layerdetails.html | 594 +++++++--------------
.../toastergui/templates/toastertable-filter.html | 18 +
.../toastergui/templates/toastertable-simple.html | 26 +-
lib/toaster/toastergui/templates/toastertable.html | 50 +-
lib/toaster/toastergui/urls.py | 6 +-
lib/toaster/toastergui/views.py | 49 --
lib/toaster/toastergui/widgets.py | 17 +-
11 files changed, 415 insertions(+), 582 deletions(-)
create mode 100644 lib/toaster/toastergui/templates/toastertable-filter.html
diff --git a/lib/toaster/toastergui/static/js/layerdetails.js b/lib/toaster/toastergui/static/js/layerdetails.js
index 3c4d632..3746ea2 100644
--- a/lib/toaster/toastergui/static/js/layerdetails.js
+++ b/lib/toaster/toastergui/static/js/layerdetails.js
@@ -1,4 +1,4 @@
-"use strict"
+"use strict";
function layerDetailsPageInit (ctx) {
@@ -54,7 +54,7 @@ function layerDetailsPageInit (ctx) {
/* Add dependency layer button click handler */
layerDepBtn.click(function(){
- if (currentLayerDepSelection == undefined)
+ if (currentLayerDepSelection === undefined)
return;
addRemoveDep(currentLayerDepSelection.id, true, function(){
@@ -122,13 +122,6 @@ 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);
- window.location.replace(libtoaster.ctx.projectPageUrl);
- });
-
function defaultAddBtnText(){
var text = " Add the "+ctx.layerVersion.name+" layer to your project";
addRmLayerBtn.text(text);
@@ -155,9 +148,48 @@ function layerDetailsPageInit (ctx) {
}
}
- window.location.hash = "targets";
+ window.location.hash = "recipes";
}
+ $("#recipestable").on('table-done', function(e, total, tableParams){
+ ctx.numTargets = total;
+
+ if (total === 0 && !tableParams.search) {
+ $("#no-recipes-yet").show();
+ } else {
+ $("#no-recipes-yet").hide();
+ }
+
+ $("#targets-tab").removeClass("muted");
+ if (window.location.hash === "#recipes"){
+ /* re run the machinesTabShow to update the text */
+ targetsTabShow();
+ }
+
+ $(".build-target-btn").unbind('click');
+ $(".build-target-btn").click(function(){
+ /* fire a build */
+ var target = $(this).data('target-name');
+ libtoaster.startABuild(ctx.projectBuildUrl, libtoaster.ctx.projectId, target, null, null);
+ window.location.replace(libtoaster.ctx.projectPageUrl);
+ });
+ });
+
+ $("#machinestable").on('table-done', function(e, total, tableParams){
+ ctx.numMachines = total;
+
+ if (total === 0 && !tableParams.search)
+ $("#no-machines-yet").show();
+ else
+ $("#no-machines-yet").hide();
+
+ $("#machines-tab").removeClass("muted");
+ if (window.location.hash === "#machines"){
+ /* re run the machinesTabShow to update the text */
+ machinesTabShow();
+ }
+ });
+
$("#targets-tab").on('show', targetsTabShow);
function machinesTabShow(){
@@ -187,7 +219,7 @@ function layerDetailsPageInit (ctx) {
/* Enables the Build target and Select Machine buttons and switches the
* add/remove button
*/
- function setLayerInCurrentPrj(added, depsList) {
+ function setLayerInCurrentPrj(added) {
ctx.layerVersion.inCurrentPrj = added;
if (added){
@@ -224,18 +256,20 @@ function layerDetailsPageInit (ctx) {
}
}
- $("#dismiss-alert").click(function(){ $(this).parent().hide() });
+ $("#dismiss-alert").click(function(){
+ $(this).parent().fadeOut();
+ });
/* Add or remove this layer from the project */
addRmLayerBtn.click(function() {
- var add = ($(this).data('directive') === "add")
+ var add = ($(this).data('directive') === "add");
libtoaster.addRmLayer(ctx.layerVersion, add, function (layersList){
var alertMsg = $("#alert-msg");
alertMsg.html(libtoaster.makeLayerAddRmAlertMsg(ctx.layerVersion, layersList, add));
- setLayerInCurrentPrj(add, layersList);
+ setLayerInCurrentPrj(add);
$("#alert-area").show();
});
@@ -250,9 +284,9 @@ function layerDetailsPageInit (ctx) {
* from.
*/
var entryElement = mParent.find("input");
- if (entryElement.length == 0)
+ if (entryElement.length === 0)
entryElement = mParent.find("textarea");
- if (entryElement.length == 0) {
+ if (entryElement.length === 0) {
console.warn("Could not find element to get data from for this change");
return;
}
@@ -293,7 +327,7 @@ function layerDetailsPageInit (ctx) {
/* Disable the change button when we have no data in the input */
$("dl input, dl textarea").on("input",function() {
- if ($(this).val().length == 0)
+ if ($(this).val().length === 0)
$(this).parent().children(".change-btn").attr("disabled", "disabled");
else
$(this).parent().children(".change-btn").removeAttr("disabled");
@@ -322,7 +356,7 @@ function layerDetailsPageInit (ctx) {
});
/* Hide the right column if it contains no information */
- if ($("dl.item-info").children(':visible').length == 0) {
+ if ($("dl.item-info").children(':visible').length === 0) {
$("dl.item-info").parent().hide();
}
diff --git a/lib/toaster/toastergui/static/js/libtoaster.js b/lib/toaster/toastergui/static/js/libtoaster.js
index 1ac26d4..99e1f03 100644
--- a/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/lib/toaster/toastergui/static/js/libtoaster.js
@@ -216,6 +216,9 @@ var libtoaster = (function (){
str += "&";
}
+ /* Maintain the current hash */
+ str += window.location.hash;
+
return str;
}
diff --git a/lib/toaster/toastergui/static/js/table.js b/lib/toaster/toastergui/static/js/table.js
index 97370fd..2e35e38 100644
--- a/lib/toaster/toastergui/static/js/table.js
+++ b/lib/toaster/toastergui/static/js/table.js
@@ -64,10 +64,12 @@ function tableInit(ctx){
function updateTable(tableData) {
var tableBody = table.children("tbody");
- var paginationBtns = $('#pagination-'+ctx.tableName);
+ var pagination = $('#pagination-'+ctx.tableName);
+ var paginationBtns = pagination.children('ul');
+ var tableContainer = $("#table-container-"+ctx.tableName);
+ tableContainer.css("visibility", "hidden");
/* To avoid page re-layout flicker when paging set fixed height */
- table.css("visibility", "hidden");
table.css("padding-bottom", table.height());
/* Reset table components */
@@ -83,19 +85,30 @@ function tableInit(ctx){
tableTotal = tableData.total;
if (tableData.total === 0){
- $("#table-container-"+ctx.tableName).hide();
- $("#new-search-input-"+ctx.tableName).val(tableParams.search);
- $("#no-results-"+ctx.tableName).show();
+ tableContainer.hide();
+ /* If we were searching show the new search bar and return */
+ if (tableParams.search){
+ $("#new-search-input-"+ctx.tableName).val(tableParams.search);
+ $("#no-results-"+ctx.tableName).show();
+ }
+ table.trigger("table-done", [tableData.total, tableParams]);
+
return;
+
+ /* We don't want to clutter the place with the table chrome if there
+ * are only a few results */
+ } else if (tableData.total <= 10 &&
+ !tableParams.filter &&
+ !tableParams.search){
+ $("#table-chrome-"+ctx.tableName).hide();
+ pagination.hide();
} else {
- $("#table-container-"+ctx.tableName).show();
+ tableContainer.show();
$("#no-results-"+ctx.tableName).hide();
}
-
setupTableChrome(tableData);
-
/* Add table data rows */
for (var i in tableData.rows){
var row = $("<tr></tr>");
@@ -137,26 +150,31 @@ function tableInit(ctx){
var end = tableParams.page + 2;
var numPages = Math.ceil(tableData.total/tableParams.limit);
- if (tableParams.page < 3)
- end = 5;
+ if (numPages > 1){
+ if (tableParams.page < 3)
+ end = 5;
- for (var page_i=1; page_i <= numPages; page_i++){
- if (page_i >= start && page_i <= end){
- var btn = $('<li><a href="#" class="page">'+page_i+'</a></li>');
+ for (var page_i=1; page_i <= numPages; page_i++){
+ if (page_i >= start && page_i <= end){
+ var btn = $('<li><a href="#" class="page">'+page_i+'</a></li>');
- if (page_i === tableParams.page){
- btn.addClass("active");
- }
+ if (page_i === tableParams.page){
+ btn.addClass("active");
+ }
- /* Add the click handler */
- btn.click(pageButtonClicked);
- paginationBtns.append(btn);
+ /* Add the click handler */
+ btn.click(pageButtonClicked);
+ paginationBtns.append(btn);
+ }
}
}
- table.css("padding-bottom", 0);
+
loadColumnsPreference();
- $("table").css("visibility", "visible");
+ table.css("padding-bottom", 0);
+ tableContainer.css("visibility", "visible");
+
+ table.trigger("table-done", [tableData.total, tableParams]);
}
function setupTableChrome(tableData){
@@ -374,9 +392,9 @@ function tableInit(ctx){
data: params,
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (filterData) {
- var filterActionRadios = $('#filter-actions');
+ var filterActionRadios = $('#filter-actions-'+ctx.tableName);
- $('#filter-modal-title').text(filterData.title);
+ $('#filter-modal-title-'+ctx.tableName).text(filterData.title);
filterActionRadios.text("");
@@ -404,7 +422,7 @@ function tableInit(ctx){
filterActionRadios.append(action);
}
- $('#filter-modal').modal('show');
+ $('#filter-modal-'+ctx.tableName).modal('show');
}
});
}
@@ -417,17 +435,18 @@ function tableInit(ctx){
e.stopPropagation();
});
- $(".pagesize").val(tableParams.limit);
+ $(".pagesize-"+ctx.tableName).val(tableParams.limit);
/* page size selector */
- $(".pagesize").change(function(){
+ $(".pagesize-"+ctx.tableName).change(function(e){
tableParams.limit = Number(this.value);
if ((tableParams.page * tableParams.limit) > tableTotal)
tableParams.page = 1;
loadData(tableParams);
/* sync the other selectors on the page */
- $(".pagesize").val(this.value);
+ $(".pagesize-"+ctx.tableName).val(this.value);
+ e.preventDefault();
});
$("#search-submit-"+ctx.tableName).click(function(e){
@@ -463,12 +482,12 @@ function tableInit(ctx){
e.preventDefault();
});
- $("#clear-filter-btn").click(function(){
+ $("#clear-filter-btn-"+ctx.tableName).click(function(){
tableParams.filter = null;
loadData(tableParams);
});
- $("#filter-modal-form").submit(function(e){
+ $("#filter-modal-form-"+ctx.tableName).submit(function(e){
e.preventDefault();
tableParams.filter = $(this).find("input[type='radio']:checked").val();
diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index e639583..78a7cb0 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -132,7 +132,6 @@ class LayersTable(ToasterTable):
static_data_name="add-del-layers",
static_data_template='{% include "layer_btn.html" %}')
-
class MachinesTable(ToasterTable):
"""Table of Machines in Toaster"""
@@ -178,8 +177,7 @@ class MachinesTable(ToasterTable):
self.add_column(title="Machine file",
hidden=True,
static_data_name="machinefile",
- static_data_template=machine_file_template,
- field_name="name")
+ static_data_template=machine_file_template)
self.add_column(title="Select",
help_text="Sets the selected machine as the project machine. You can only have one machine per project",
@@ -189,6 +187,33 @@ class MachinesTable(ToasterTable):
field_name="layer_version__id")
+class LayerMachinesTable(MachinesTable):
+ """ Smaller version of the Machines table for use in layer details """
+
+ def __init__(self, *args, **kwargs):
+ MachinesTable.__init__(self)
+
+ def setup_queryset(self, *args, **kwargs):
+ MachinesTable.setup_queryset(self, *args, **kwargs)
+
+ self.queryset = self.queryset.filter(layer_version__pk=int(kwargs['layerid']))
+ self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) and Q(layercommit=kwargs['layerid'])).count()
+
+ def setup_columns(self, *args, **kwargs):
+ self.add_column(title="Machine",
+ hideable=False,
+ orderable=True,
+ field_name="name")
+
+ self.add_column(title="Description",
+ field_name="description")
+
+ select_btn_template = '<a href="{% url "project" extra.pid %}#/machineselect={{data.name}}" class="btn btn-block select-machine-btn" {% if extra.in_prj == 0%}disabled="disabled"{%endif%}>Select machine</a>'
+
+ self.add_column(title="Select machine",
+ static_data_name="add-del-layers",
+ static_data_template=select_btn_template)
+
class RecipesTable(ToasterTable):
"""Table of Recipes in Toaster"""
@@ -267,13 +292,57 @@ class RecipesTable(ToasterTable):
static_data_name="add-del-layers",
static_data_template='{% include "recipe_btn.html" %}')
+class LayerRecipesTable(RecipesTable):
+ """ Smaller version of the Machines table for use in layer details """
+
+ def __init__(self, *args, **kwargs):
+ RecipesTable.__init__(self)
+
+ def setup_queryset(self, *args, **kwargs):
+ RecipesTable.setup_queryset(self, *args, **kwargs)
+ self.queryset = self.queryset.filter(layer_version__pk=int(kwargs['layerid']))
+
+ self.static_context_extra['in_prj'] = ProjectLayer.objects.filter(Q(project=kwargs['pid']) and Q(layercommit=kwargs['layerid'])).count()
+
+ def setup_columns(self, *args, **kwargs):
+ self.add_column(title="Recipe",
+ help_text="Information about a single piece of software, including where to download the source, configuration options, how to compile the source files and how to package the compiled output",
+ hideable=False,
+ orderable=True,
+ field_name="name")
+
+ self.add_column(title="Description",
+ field_name="get_description_or_summary")
+
+
+ build_recipe_template ='<button class="btn btn-block build-target-btn" data-target-name="{{data.name}}" {%if extra.in_prj == 0 %}disabled="disabled"{%endif%}>Build recipe</button>'
+
+ self.add_column(title="Build recipe",
+ static_data_name="add-del-layers",
+ static_data_template=build_recipe_template)
+
+
+
+
+
+
+
+
# This needs to be staticaly defined here as django reads the url patterns
# on start up
urlpatterns = (
- url(r'^machines/(?P<cmd>\w+)*', MachinesTable.as_view(),
- name=MachinesTable.__name__.lower()),
- url(r'^layers/(?P<cmd>\w+)*', LayersTable.as_view(),
- name=LayersTable.__name__.lower()),
- url(r'^recipes/(?P<cmd>\w+)*', RecipesTable.as_view(),
- name=RecipesTable.__name__.lower()),
+ url(r'^machines/(?P<cmd>\w+)*', MachinesTable.as_view(),
+ name=MachinesTable.__name__.lower()),
+ url(r'^layers/(?P<cmd>\w+)*', LayersTable.as_view(),
+ name=LayersTable.__name__.lower()),
+ url(r'^recipes/(?P<cmd>\w+)*', RecipesTable.as_view(),
+ name=RecipesTable.__name__.lower()),
+
+ # layer details tables
+ url(r'^layer/(?P<layerid>\d+)/recipes/(?P<cmd>\w+)*',
+ LayerRecipesTable.as_view(),
+ name=LayerRecipesTable.__name__.lower()),
+ url(r'^layer/(?P<layerid>\d+)/machines/(?P<cmd>\w+)*',
+ LayerMachinesTable.as_view(),
+ name=LayerMachinesTable.__name__.lower()),
)
diff --git a/lib/toaster/toastergui/templates/layerdetails.html b/lib/toaster/toastergui/templates/layerdetails.html
index 7b62b64..259a59e 100644
--- a/lib/toaster/toastergui/templates/layerdetails.html
+++ b/lib/toaster/toastergui/templates/layerdetails.html
@@ -36,13 +36,15 @@
projectBuildUrl : "{% url 'xhr_build' %}",
layerDetailsUrl : "{% url 'base_layerdetails' project.id %}",
xhrUpdateLayerUrl : "{% url 'xhr_updatelayer' %}",
- numTargets : {{total_targets}},
- numMachines: {{machines|length}},
layerVersion : {
name : "{{layerversion.layer.name}}",
id : {{layerversion.id}},
commit: "{{layerversion.get_vcs_reference}}",
- inCurrentPrj : {{layer_in_project}},
+ {%if layerversion.id in projectlayers %}
+ inCurrentPrj : true,
+ {% else %}
+ inCurrentPrj : false,
+ {% endif %}
url : "{% url 'layerdetails' project.id layerversion.id %}",
sourceId: {{layerversion.layer_source_id|json}},
}
@@ -57,437 +59,207 @@
});
</script>
- <div class="row-fluid span11">
- <div class="page-header">
- <h1>{{layerversion.layer.name}} <small class="commit"
- {% if layerversion.get_vcs_reference|length > 13 %}
- data-toggle="tooltip" title="{{layerversion.get_vcs_reference}}"
- {% endif %}>
- ({{layerversion.get_vcs_reference|truncatechars:13}})</small></h1>
- </div>
+<div class="row-fluid span11">
+ <div class="page-header">
+ <h1>{{layerversion.layer.name}} <small class="commit"
+ {% if layerversion.get_vcs_reference|length > 13 %}
+ data-toggle="tooltip" title="{{layerversion.get_vcs_reference}}"
+ {% endif %}>
+ ({{layerversion.get_vcs_reference|truncatechars:13}})</small></h1>
</div>
+</div>
- <div class="row-fluid span7 tabbable">
- <div class="alert alert-info lead" id="alert-area" style="display:none">
- <button type="button" class="close" id="dismiss-alert">×</button>
- <span id="alert-msg"></span>
- </div>
- <ul class="nav nav-pills">
- <li class="active">
- <a data-toggle="tab" href="#information" id="details-tab">Layer details</a>
- </li>
- <li>
- <a data-toggle="tab" href="#targets" id="targets-tab">Recipes ({{total_targets}})</a>
- </li>
- <li>
- <a data-toggle="tab" href="#machines" id="machines-tab">Machines ({{total_machines}})</a>
- </li>
- </ul>
- <div class="tab-content">
- <span class="button-place">
- {% if layer_in_project == 0 %}
- <button id="add-remove-layer-btn" data-directive="add" class="btn btn-large btn-block">
- <span class="icon-plus"></span>
- Add the {{layerversion.layer.name}} layer to your project
- </button>
- {% else %}
- <button id="add-remove-layer-btn" data-directive="remove" class="btn btn-block btn-large btn-danger">
- <span class="icon-trash"></span>
- Delete the {{layerversion.layer.name}} layer from your project
- </button>
- {% endif %}
- </span>
+<!-- container for tabs -->
+<div class="row-fluid span7 tabbable">
+ <div class="alert alert-info lead" id="alert-area" style="display:none">
+ <button type="button" class="close" id="dismiss-alert">×</button>
+ <span id="alert-msg"></span>
+ </div>
+ <ul class="nav nav-pills">
+ <li class="active">
+ <a data-toggle="tab" href="#information" id="details-tab">Layer details</a>
+ </li>
+ <li>
+ <a data-toggle="tab" href="#recipes" class="muted" id="targets-tab">Recipes (<span class="table-count-recipestable"></span>)</a>
+ </li>
+ <li>
+ <a data-toggle="tab" href="#machines" class="muted" id="machines-tab">Machines (<span class="table-count-machinestable"></span>)</a>
+ </li>
+ </ul>
+ <div class="tab-content">
+ <span class="button-place">
+ {% if layerversion.id not in projectlayers %}
+ <button id="add-remove-layer-btn" data-directive="add" class="btn btn-large btn-block">
+ <span class="icon-plus"></span>
+ Add the {{layerversion.layer.name}} layer to your project
+ </button>
+ {% else %}
+ <button id="add-remove-layer-btn" data-directive="remove" class="btn btn-block btn-large btn-danger">
+ <span class="icon-trash"></span>
+ Delete the {{layerversion.layer.name}} layer from your project
+ </button>
+ {% endif %}
+ </span>
- <!-- layer details pane -->
- <div id="information" class="tab-pane active">
- <dl class="dl-horizontal">
- <dt class="">
- <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository"></i>
- Repository URL
+ <!-- layer details pane -->
+ <div id="information" class="tab-pane active">
+ <dl class="dl-horizontal">
+ <dt class="">
+ <i class="icon-question-sign get-help" title="Fetch/clone URL of the repository"></i>
+ Repository URL
+ </dt>
+ <dd>
+ <span class="current-value">{{layerversion.layer.vcs_url}}</span>
+ {% if layerversion.get_vcs_link_url %}
+ <a href="{{layerversion.get_vcs_link_url}}/" class="icon-share get-info" target="_blank"></a>
+ {% endif %}
+ <form id="change-repo-form" class="control-group" style="display:none">
+ <div class="input-append">
+ <input type="text" class="input-xlarge" value="{{layerversion.layer.vcs_url}}">
+ <button data-layer-prop="vcs_url" class="btn change-btn" type="button">Save</button>
+ <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
+ </div>
+ </form>
+ <i class="icon-pencil" ></i>
+ </dd>
+ <dt>
+ <i class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></i>
+ Repository subdirectory
+ </dt>
+ <dd>
+ <span class="muted" style="display:none">Not set</span>
+ <span class="current-value">{{layerversion.dirpath}}</span>
+ {% if layerversion.get_vcs_dirpath_link_url %}
+ <a href="{{layerversion.get_vcs_dirpath_link_url}}" class="icon-share get-info" target="_blank"></a>
+ {% endif %}
+ <form id="change-subdir-form" style="display:none;">
+ <div class="input-append">
+ <input type="text" value="{{layerversion.dirpath}}">
+ <button data-layer-prop="dirpath" class="btn change-btn" type="button">Save</button>
+ <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
+ </div>
+ </form>
+ <i id="change-subdir" class="icon-pencil"></i>
+ <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
+ </dd>
+ <dt>
+ <i class="icon-question-sign get-help" title="The Git branch, tag or commit"></i>
+ Revision
</dt>
<dd>
- <span class="current-value">{{layerversion.layer.vcs_url}}</span>
- {% if layerversion.get_vcs_link_url %}
- <a href="{{layerversion.get_vcs_link_url}}/" class="icon-share get-info" target="_blank"></a>
- {% endif %}
- <form id="change-repo-form" class="control-group" style="display:none">
+ <span class="current-value">{{layerversion.get_vcs_reference}}</span>
+ <form style="display:none;">
<div class="input-append">
- <input type="text" class="input-xlarge" value="{{layerversion.layer.vcs_url}}">
- <button data-layer-prop="vcs_url" class="btn change-btn" type="button">Save</button>
+ <input type="text" value="{{layerversion.get_vcs_reference}}">
+ <button data-layer-prop="commit" class="btn change-btn" type="button">Save</button>
<a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
</div>
</form>
- <i class="icon-pencil" ></i>
+ <i class="icon-pencil"></i>
</dd>
<dt>
- <i class="icon-question-sign get-help" title="Subdirectory within the repository where the layer is located, if not in the root (usually only used if the repository contains more than one layer)"></i>
- Repository subdirectory
+ <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i>
+ Layer dependencies
</dt>
<dd>
- <span class="muted" style="display:none">Not set</span>
- <span class="current-value">{{layerversion.dirpath}}</span>
- {% if layerversion.get_vcs_dirpath_link_url %}
- <a href="{{layerversion.get_vcs_dirpath_link_url}}" class="icon-share get-info" target="_blank"></a>
- {% endif %}
- <form id="change-subdir-form" style="display:none;">
- <div class="input-append">
- <input type="text" value="{{layerversion.dirpath}}">
- <button data-layer-prop="dirpath" class="btn change-btn" type="button">Save</button>
- <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
- </div>
- </form>
- <i id="change-subdir" class="icon-pencil"></i>
- <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
- </dd>
- <dt>
- <i class="icon-question-sign get-help" title="The Git branch, tag or commit"></i>
- Revision
- </dt>
- <dd>
- <span class="current-value">{{layerversion.get_vcs_reference}}</span>
- <form style="display:none;">
- <div class="input-append">
- <input type="text" value="{{layerversion.get_vcs_reference}}">
- <button data-layer-prop="commit" class="btn change-btn" type="button">Save</button>
- <a href="#" style="display:none" class="btn btn-link cancel">Cancel</a>
- </div>
- </form>
- <i class="icon-pencil"></i>
- </dd>
- <dt>
- <i class="icon-question-sign get-help" title="Other layers this layer depends upon"></i>
- Layer dependencies
- </dt>
- <dd>
- <ul class="unstyled current-value" id="layer-deps-list">
- {% for ld in layerversion.dependencies.all %}
- <li data-layer-id="{{ld.depends_on.id}}">
- <a data-toggle="tooltip" title="{{ld.depends_on.layer.vcs_url}} | {{ld.depends_on.get_vcs_reference}}" href="{% url 'layerdetails' project.id ld.depends_on.id %}">{{ld.depends_on.layer.name}}</a>
- <span class="icon-trash " data-toggle="tooltip" title="Delete"></span>
- </li>
- {% endfor %}
- </ul>
- <div class="input-append add-deps">
- <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" placeholder="Type a layer name" id="layer-dep-input">
- <a class="btn" id="add-layer-dependency-btn" >
- Add layer
- </a>
- </div>
- <span class="help-block add-deps">You can only add layers Toaster knows about</span>
- </dd>
- </dl>
- </div>
- <!-- targets tab -->
- <div id="targets" class="tab-pane">
- {% if total_targets == 0 %}
- <div class="alert alert-info">
- <p>Toaster does not have recipe information for the <strong> {{layerversion.layer.name}} </strong> layer.</p>
- <p>Toaster learns about layers when you build them. If this layer provides any recipes, they will be listed here after you build the <strong> {{layerversion.layer.name}} </strong> layer.</p>
- </div>
- {% else %}
-
- <div class="row-fluid">
-
- {% if targets.paginator.count == 0 %}
- <div class="alert">
- <h3>No targets found</h3>
- {% endif %}
-
- {# only show the search form if we have more than 10 results #}
- {% if targets.paginator.count > 10 or request.GET.targets_search %}
- {% if targets.paginator.count == 0 %}
- <form class="input-append">
- {% else %}
- <form class="navbar-search input-append pull-left">
- {% endif %}
-
- <input type="text" id="target-search" name="targets_search" placeholder="Search recipes" class="input-xlarge" value="{{request.GET.targets_search}}">
- {% if request.GET.targets_search %}
- <a class="add-on btn target-search-clear">
- <i class="icon-remove"></i>
- </a>
- {% endif %}
- <button type="submit" class="btn">Search</button>
- {% if targets.paginator.count == 0 %}
- <button type="submit" class="btn btn-link target-search-clear">Show all targets</a>
- {% endif %}
- </form>
- {% endif %}
-
- {% if targets.paginator.count == 0 %}
- <!-- end alert -->
- </div>
- <!-- end row-fluid -->
- </div>
- {% else %}
-
- {% if total_targets > 10 %}
- <div class="pull-right">
- <span class="help-inline" style="padding-top:5px;">Show rows:</span>
- <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
- {% with "10 25 50 100 150" as list%}
- {% for i in list.split %}
- {% if request.session.limit == i %}
- <option value="{{i}}" selected>{{i}}</option>
- {% else %}
- <option value="{{i}}">{{i}}</option>
- {% endif %}
- {% endfor %}
- {% endwith %}
- </select>
- </div>
- {% endif %}
- </div>
-
- <table class="table table-bordered table-hover">
- <thead>
- <tr>
- <th>
- <i class="icon-question-sign get-help" title="Information about a single piece of software, including where to download the source, configuration options, how to compile the source files and how to package the compiled output"></i>
- Recipe
- {% if request.GET.targets_search %}
- <span class="badge badge-info">{{targets.paginator.count}}</span>
- {% endif %}
- </th>
- <th>
- <i class="icon-question-sign get-help" title="The recipe version and revision"></i>
- Recipe version
- </th>
- <th class="span4">Summary</th>
- <th class="span2">Build recipe</th>
- </tr>
- </thead>
- <tbody>
- {% for target in targets %}
- <tr>
- <td>
- {{target.name}}
- {% if target.up_id %}
- <a href="{{target.get_layersource_view_url}}" class="icon-share get-info" target="_blank"></a>
- {% endif %}
- </td>
- <td>{{target.version}}</td>
- <td>{{target.summary}}</td>
- <td><button class="btn btn-block build-target-btn" data-target-name="{{target.name}}" {% if layer_in_project == 0 %}disabled="disabled"{% endif %} >Build recipe</button></td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
-
- {% if total_targets > 10 %}
- <!-- Show pagination controls -->
- <div class="pagination">
- <ul>
- {%if targets.has_previous %}
- <li><a href="?tpage={{targets.previous_page_number}}{{request.GET.limit}}#targets">«</a></li>
- {%else%}
- <li class="disabled"><a href="#">«</a></li>
- {%endif%}
- {% for i in targets.page_range %}
- <li {%if i == targets.number %} class="active" {%endif%}><a href="?tpage={{i}}#targets">{{i}}</a></li>
- {% endfor %}
- {%if targets.has_next%}
- <li><a href="?tpage={{targets.next_page_number}}#targets">»</a></li>
- {%else%}
- <li class="disabled"><a href="#">»</a></li>
- {%endif%}
- </ul>
- <div class="pull-right">
- <span class="help-inline" style="padding-bottom:10px;">Show rows:</span>
- <select class="pagesize">
- {% with "10 25 50 100 150" as list%}
- {% for i in list.split %}
- {% if request.session.limit == i %}
- <option value="{{i}}" selected>{{i}}</option>
- {% else %}
- <option value="{{i}}">{{i}}</option>
- {% endif %}
- {% endfor %}
- {% endwith %}
- </select>
- </div>
- </div>
- {% endif %}
- {% endif %}
- {% endif %}
- </div>
-
-
- <div id="machines" class="tab-pane">
- {% if total_machines == 0 %}
- <div class="alert alert-info">
- <p>Toaster does not have machine information for the <strong> {{layerversion.layer.name}} </strong> layer.</p>
- <p>Toaster learns about layers when you build them. If this layer provides any machines, they will be listed here after you build the <strong> {{layerversion.layer.name}} </strong> layer.</p>
+ <ul class="unstyled current-value" id="layer-deps-list">
+ {% for ld in layerversion.dependencies.all %}
+ <li data-layer-id="{{ld.depends_on.id}}">
+ <a data-toggle="tooltip" title="{{ld.depends_on.layer.vcs_url}} | {{ld.depends_on.get_vcs_reference}}" href="{% url 'layerdetails' project.id ld.depends_on.id %}">{{ld.depends_on.layer.name}}</a>
+ <span class="icon-trash " data-toggle="tooltip" title="Delete"></span>
+ </li>
+ {% endfor %}
+ </ul>
+ <div class="input-append add-deps">
+ <input type="text" autocomplete="off" data-minLength="1" data-autocomplete="off" placeholder="Type a layer name" id="layer-dep-input">
+ <a class="btn" id="add-layer-dependency-btn" >
+ Add layer
+ </a>
</div>
- {% else %}
-
- <div class="row-fluid">
-
- {% if machines.paginator.count == 0 %}
- <div class="alert">
- <h3>No machines found</h3>
- {% endif %}
-
- {# only show the search form if we have more than 10 results #}
- {% if machines.paginator.count > 10 or request.GET.machines_search %}
- {% if machines.paginator.count == 0 %}
- <form class="input-append">
- {% else %}
- <form class="navbar-search input-append pull-left">
- {% endif %}
+ <span class="help-block add-deps">You can only add layers Toaster knows about</span>
+ </dd>
+ </dl>
+ </div>
+ <!-- end layerdetails tab -->
+ <!-- targets tab -->
+ <div id="recipes" class="tab-pane">
+ <!-- Recipe table -->
+ <div id="no-recipes-yet" class="alert alert-info" style="display:none">
+ <p>Toaster does not have recipe information for the <strong> {{layerversion.layer.name}} </strong> layer.</p>
+ <p>Toaster learns about layers when you build them. If this layer provides any recipes, they will be listed here after you build the <strong> {{layerversion.layer.name}} </strong> layer.</p>
+ </div>
- <input type="text" id="machine-search" name="machines_search" placeholder="Search machines" class="input-xlarge" value="{{request.GET.machines_search}}">
- {% if request.GET.machines_search %}
- <a class="add-on btn machine-search-clear">
- <i class="icon-remove"></i>
- </a>
- {% endif %}
- <button type="submit" class="btn">Search</button>
- {% if machines.paginator.count == 0 %}
- <button type="submit" class="btn btn-link machine-search-clear">Show all machines</a>
- {% endif %}
- </form>
- {% endif %}
- {% if machines.paginator.count == 0 %}
- <!-- end alert -->
- </div>
- <!-- end row-fluid -->
- </div>
- {% else %}
+ {% url 'layerrecipestable' project.id layerversion.id as xhr_table_url %}
+ {% with "recipestable" as table_name %}
+ {% with "Recipes" as title %}
+ {% include 'toastertable-simple.html' %}
+ {% endwith %}
+ {% endwith %}
+ </div>
- {% if total_machines > 10 %}
- <div class="pull-right">
- <span class="help-inline" style="padding-top:5px;">Show rows:</span>
- <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
- {% with "10 25 50 100 150" as list%}
- {% for i in list.split %}
- {% if request.session.limit == i %}
- <option value="{{i}}" selected>{{i}}</option>
- {% else %}
- <option value="{{i}}">{{i}}</option>
- {% endif %}
- {% endfor %}
- {% endwith %}
- </select>
- </div>
- {% endif %}
+ <div id="machines" class="tab-pane">
- </div>
+ <div id="no-machines-yet" class="alert alert-info" style="display:none">
+ <p>Toaster does not have machine information for the <strong> {{layerversion.layer.name}} </strong> layer.</p>
+ <p>Toaster learns about layers when you build them. If this layer provides any machines, they will be listed here after you build the <strong> {{layerversion.layer.name}} </strong> layer.</p>
+ </div>
- <table class="table table-bordered table-hover">
- <thead>
- <tr>
- <th>
- <i class="icon-question-sign get-help" title="The machine is the hardware for which you are building"></i>
- Machine
- {% if request.GET.machines_search %}
- <span class="badge badge-info">{{machines.paginator.count}}</span>
- {% endif %}
- </th>
- <th>Description</th>
- <th class="span2">Select machine</th>
- </tr>
- </thead>
- <tbody>
- {% for machine in machines %}
- <tr>
- <td>
- {{machine.name}}
- {% if machine.get_vcs_machine_file_link_url %}
- <a href="{{machine.get_vcs_machine_file_link_url}}" target="_blank"><i class="icon-share get-info"></i></a>
- {% endif %}
- </td>
- <td>{{machine.description}}</td>
- <td>
- <a href="{% url 'project' project.id %}#/machineselect={{machine.name}}" class="btn btn-block select-machine-btn" {% if layer_in_project == 0 %}disabled="disabled"{% endif %}>Select machine</a>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- {% if total_machines > 10 %}
- <!-- Show pagination controls -->
- <div class="pagination">
- <ul>
- {%if machines.has_previous %}
- <li><a href="?mpage={{machines.previous_page_number}}{{request.GET.limit}}#machines">«</a></li>
- {%else%}
- <li class="disabled"><a href="#">«</a></li>
- {%endif%}
- {% for i in machines.page_range %}
- <li {%if i == machines.number %} class="active" {%endif%}><a href="?mpage={{i}}#machines">{{i}}</a></li>
- {% endfor %}
- {%if machines.has_next%}
- <li><a href="?mpage={{machines.next_page_number}}#machines">»</a></li>
- {%else%}
- <li class="disabled"><a href="#">»</a></li>
- {%endif%}
- </ul>
- <div class="pull-right">
- <span class="help-inline" style="padding-bottom:10px;">Show rows:</span>
- <select class="pagesize">
- {% with "10 25 50 100 150" as list%}
- {% for i in list.split %}
- {% if request.session.limit == i %}
- <option value="{{i}}" selected>{{i}}</option>
- {% else %}
- <option value="{{i}}">{{i}}</option>
- {% endif %}
- {% endfor %}
- {% endwith %}
- </select>
- </div>
- </div>
- {% endif %}
- {% endif %}
- {% endif %}
- </div>
- </div>
+ <!-- Machines table -->
+ {% url 'layermachinestable' project.id layerversion.id as xhr_table_url %}
+ {% with "machinestable" as table_name %}
+ {% with "Machines" as title %}
+ {% include 'toastertable-simple.html' %}
+ {% endwith %}
+ {% endwith %}
</div>
- <div class="row-fluid span4 well">
- <h2>About {{layerversion.layer.name}}</h2>
- <dl class="item-info">
+ </div> <!-- end tab content -->
+ </div> <!-- end tabable -->
- <dt>
- Summary
- <i class="icon-question-sign get-help" title="One-line description of the layer"></i>
- </dt>
- <dd>
- <span class="muted" style="display:none">Not set</span>
- <span class="current-value">{{layerversion.layer.summary|default_if_none:''}}</span>
- <form style="display:none; margin-bottom:20px">
- <textarea class="span12" rows="2">{% if layerversion.layer.summary %}{{layerversion.layer.summary}}{% endif %}</textarea>
- <button class="btn change-btn" data-layer-prop="summary" type="button">Save</button>
- <a href="#" class="btn btn-link cancel">Cancel</a>
- </form>
- <i class="icon-pencil"></i>
- <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
- </dd>
- <dt>
- Description
- </dt>
- <dd>
- <span class="muted" style="display:none">Not set</span>
- <span class="current-value">{{layerversion.layer.description|default_if_none:''}}</span>
- <form style="display:none; margin-bottom:20px">
- <textarea class="span12" rows="6">{% if layerversion.layer.description %}{{layerversion.layer.description}}{% endif %}</textarea>
- <button class="btn change-btn" data-layer-prop="description" type="button" >Save</button>
- <a href="#" class="btn btn-link cancel">Cancel</a>
- </form>
- <i class="icon-pencil"></i>
- <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
- </dd>
- {% if layerversion.layer.up_id %}
- <dt>Layer index</dt>
- <dd>
- <a href="http://layers.openembedded.org/layerindex/branch/{{layerversion.up_branch.name}}/layer/{{layerversion.layer.name}}"/>layer index link</a>
+ <div class="row-fluid span4 well"> <!-- info side panel -->
+ <h2>About {{layerversion.layer.name}}</h2>
+ <dl class="item-info">
- </dd>
- {% endif %}
+ <dt>
+ Summary
+ <i class="icon-question-sign get-help" title="One-line description of the layer"></i>
+ </dt>
+ <dd>
+ <span class="muted" style="display:none">Not set</span>
+ <span class="current-value">{{layerversion.layer.summary|default_if_none:''}}</span>
+ <form style="display:none; margin-bottom:20px">
+ <textarea class="span12" rows="2">{% if layerversion.layer.summary %}{{layerversion.layer.summary}}{% endif %}</textarea>
+ <button class="btn change-btn" data-layer-prop="summary" type="button">Save</button>
+ <a href="#" class="btn btn-link cancel">Cancel</a>
+ </form>
+ <i class="icon-pencil"></i>
+ <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
+ </dd>
+ <dt>
+ Description
+ </dt>
+ <dd>
+ <span class="muted" style="display:none">Not set</span>
+ <span class="current-value">{{layerversion.layer.description|default_if_none:''}}</span>
+ <form style="display:none; margin-bottom:20px">
+ <textarea class="span12" rows="6">{% if layerversion.layer.description %}{{layerversion.layer.description}}{% endif %}</textarea>
+ <button class="btn change-btn" data-layer-prop="description" type="button" >Save</button>
+ <a href="#" class="btn btn-link cancel">Cancel</a>
+ </form>
+ <i class="icon-pencil"></i>
+ <span class="icon-trash delete-current-value" data-toggle="tooltip" title="Delete"></span>
+ </dd>
+ {% if layerversion.layer.up_id %}
+ <dt>Layer index</dt>
+ <dd>
+ <a href="http://layers.openembedded.org/layerindex/branch/{{layerversion.up_branch.name}}/layer/{{layerversion.layer.name}}">layer index link</a>
+
+ </dd>
+ {% endif %}
- </dl>
- </div>
+ </dl>
+ </div>
-{% endblock %}
+ {% endblock %}
diff --git a/lib/toaster/toastergui/templates/toastertable-filter.html b/lib/toaster/toastergui/templates/toastertable-filter.html
new file mode 100644
index 0000000..7c8dc49
--- /dev/null
+++ b/lib/toaster/toastergui/templates/toastertable-filter.html
@@ -0,0 +1,18 @@
+<!-- filter modal -->
+<div id="filter-modal-{{table_name}}" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false">
+ <form id="filter-modal-form-{{table_name}}" style="margin-bottom: 0px">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+ <h3 id="filter-modal-title-{{table_name}}"> </h3>
+ </div>
+ <div class="modal-body">
+ <p>Show:</p>
+ <span id="filter-actions-{{table_name}}"></span>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" type="submit">Apply</button>
+ </div>
+ </form>
+</div>
+<button id="clear-filter-btn-{{table_name}}" style="display:none"></button>
+<!-- end filter modal -->
diff --git a/lib/toaster/toastergui/templates/toastertable-simple.html b/lib/toaster/toastergui/templates/toastertable-simple.html
index 98cad64..23a7526 100644
--- a/lib/toaster/toastergui/templates/toastertable-simple.html
+++ b/lib/toaster/toastergui/templates/toastertable-simple.html
@@ -24,23 +24,7 @@
});
</script>
-<!-- filter modal -->
-<div id="filter-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false">
- <form id="filter-modal-form" style="margin-bottom: 0px">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
- <h3 id="filter-modal-title"> </h3>
- </div>
- <div class="modal-body">
- <p>Show:</p>
- <span id="filter-actions"></span>
- </div>
- <div class="modal-footer">
- <button class="btn btn-primary" type="submit">Apply</button>
- </div>
- </form>
-</div>
-<button id="clear-filter-btn" style="display:none"></button>
+{% include 'toastertable-filter.html' %}
<div class="row-fluid" id="no-results-{{table_name}}" style="display:none">
<div class="alert">
@@ -55,9 +39,7 @@
</form>
</div>
</div>
-
-
-<div id="table-container-{{table_name}}">
+<div id="table-container-{{table_name}}" style="visibility: hidden">
<!-- control header -->
<div class="row-fluid" id="table-chrome-{{table_name}}">
<div class="navbar-search input-append pull-left">
@@ -94,8 +76,8 @@
</table>
<!-- Pagination controls -->
- <div class="pagination pagination-centered">
- <ul id="pagination-{{table_name}}" class="pagination" style="display: block-inline">
+ <div class="pagination pagination-centered" id="pagination-{{table_name}}">
+ <ul class="pagination" style="display: block-inline">
</ul>
<div class="pull-right">
diff --git a/lib/toaster/toastergui/templates/toastertable.html b/lib/toaster/toastergui/templates/toastertable.html
index 4e6c4ce..5c79ab4 100644
--- a/lib/toaster/toastergui/templates/toastertable.html
+++ b/lib/toaster/toastergui/templates/toastertable.html
@@ -24,37 +24,23 @@
});
</script>
-<!-- filter modal -->
-<div id="filter-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false">
- <form id="filter-modal-form" style="margin-bottom: 0px">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
- <h3 id="filter-modal-title"> </h3>
- </div>
- <div class="modal-body">
- <p>Show:</p>
- <span id="filter-actions"></span>
- </div>
- <div class="modal-footer">
- <button class="btn btn-primary" type="submit">Apply</button>
- </div>
- </form>
-</div>
-<button id="clear-filter-btn" style="display:none"></button>
+{% include 'toastertable-filter.html' %}
-<div class="row-fluid alert" id="no-results-{{table_name}}" style="display:none">
- <form class="no-results input-append">
- <input class="input-xxlarge" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{{request.GET.search}}"/>
- <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1">
- <i class="icon-remove"></i>
- </a>
- <button class="btn search-submit-{{table_name}}" >Search</button>
- <button class="btn btn-link remove-search-btn-{{table_name}}">Show {{title|lower}}
- </button>
- </form>
+<div class="row-fluid" id="no-results-{{table_name}}" style="display:none">
+ <div class="alert">
+ <form class="no-results input-append">
+ <input class="input-xxlarge" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{{request.GET.search}}"/>
+ <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1">
+ <i class="icon-remove"></i>
+ </a>
+ <button class="btn search-submit-{{table_name}}" >Search</button>
+ <button class="btn btn-link remove-search-btn-{{table_name}}">Show {{title|lower}}
+ </button>
+ </form>
+ </div>
</div>
-<div id="table-container-{{table_name}}">
+<div id="table-container-{{table_name}}" style="visibility: hidden">
<!-- control header -->
<div class="navbar" id="table-chrome-{{table_name}}">
<div class="navbar-inner">
@@ -78,7 +64,7 @@
<div style="display:inline">
<span class="divider-vertical"></span>
<span class="help-inline" style="padding-top:5px;">Show rows:</span>
- <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
+ <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}">
{% with "10 25 50 100 150" as list%}
{% for i in list.split %}
<option value="{{i}}">{{i}}</option>
@@ -99,13 +85,13 @@
</table>
<!-- Pagination controls -->
- <div class="pagination pagination-centered">
- <ul id="pagination-{{table_name}}" class="pagination" style="display: block-inline">
+ <div class="pagination pagination-centered" id="pagination-{{table_name}}">
+ <ul class="pagination" style="display: block-inline">
</ul>
<div class="pull-right">
<span class="help-inline" style="padding-top:5px;">Show rows:</span>
- <select style="margin-top:5px;margin-bottom:0px;" class="pagesize">
+ <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}">
{% with "10 25 50 100 150" as list%}
{% for i in list.split %}
<option value="{{i}}">{{i}}</option>
diff --git a/lib/toaster/toastergui/urls.py b/lib/toaster/toastergui/urls.py
index d0c176b..7a1132f 100644
--- a/lib/toaster/toastergui/urls.py
+++ b/lib/toaster/toastergui/urls.py
@@ -83,7 +83,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/(?P<layerid>\d+)$', 'layerdetails', name='layerdetails'),
+ 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;
@@ -117,7 +119,7 @@ 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/(?P<pid>\d+)/', include('toastergui.tables')),
+ url(r'^xhr_tables/project/(?P<pid>\d+)/', include('toastergui.tables')),
# dashboard for failed build requests
url(r'^project/(?P<pid>\d+)/buildrequest/(?P<brid>\d+)$', 'buildrequestdetails', name='buildrequestdetails'),
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index d451c3b..f70cf20 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -2664,52 +2664,6 @@ if toastermain.settings.MANAGED:
}
return render(request, template, context)
-
-
- def layerdetails(request, pid, layerid):
- template = "layerdetails.html"
- limit = 10
-
- if request.GET.has_key("limit"):
- request.session['limit'] = request.GET['limit']
-
- if request.session.has_key('limit'):
- limit = request.session['limit']
-
- layer_version = Layer_Version.objects.get(pk = layerid)
-
- targets_query = Recipe.objects.filter(layer_version=layer_version)
-
- # Targets tab query functionality
- if request.GET.has_key('targets_search'):
- targets_query = targets_query.filter(
- Q(name__icontains=request.GET['targets_search']) |
- Q(summary__icontains=request.GET['targets_search']))
-
- targets = _build_page_range(Paginator(targets_query.order_by("name"), limit), request.GET.get('tpage', 1))
-
- machines_query = Machine.objects.filter(layer_version=layer_version)
-
- # Machines tab query functionality
- if request.GET.has_key('machines_search'):
- machines_query = machines_query.filter(
- Q(name__icontains=request.GET['machines_search']) |
- Q(description__icontains=request.GET['machines_search']))
-
- machines = _build_page_range(Paginator(machines_query.order_by("name"), limit), request.GET.get('mpage', 1))
-
- context = {
- 'project' : Project.objects.get(pk=pid),
- 'layerversion': layer_version,
- 'layer_in_project' : ProjectLayer.objects.filter(project_id=pid,layercommit=layerid).count(),
- 'machines': machines,
- 'targets': targets,
- 'total_targets': Recipe.objects.filter(layer_version=layer_version).count(),
-
- 'total_machines': Machine.objects.filter(layer_version=layer_version).count(),
- }
- return render(request, template, context)
-
def get_project_configvars_context():
# Vars managed outside of this view
vars_managed = {
@@ -3250,9 +3204,6 @@ else:
def importlayer(request):
return render(request, 'landing_not_managed.html')
- def layerdetails(request, layerid):
- return render(request, 'landing_not_managed.html')
-
def projectconf(request, pid):
return render(request, 'landing_not_managed.html')
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index 8d44919..b5dfbbc 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -26,7 +26,7 @@ from django.core import serializers
from django.core.cache import cache
from django.core.paginator import Paginator, EmptyPage
from django.db.models import Q
-from orm.models import Project, ProjectLayer
+from orm.models import Project, ProjectLayer, Layer_Version
from django.template import Context, Template
from django.core.serializers.json import DjangoJSONEncoder
from django.core.exceptions import FieldError
@@ -43,9 +43,13 @@ class ToasterTemplateView(TemplateView):
def get_context_data(self, **kwargs):
context = super(ToasterTemplateView, self).get_context_data(**kwargs)
if 'pid' in kwargs:
- context['project'] = Project.objects.get(pk=kwargs['pid'])
+ context['project'] = Project.objects.get(pk=kwargs['pid'])
+
+ context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
+
+ if 'layerid' in kwargs:
+ context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid'])
- context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
return context
@@ -269,13 +273,6 @@ class ToasterTable(View):
'rows' : [],
}
- # Flatten all the fields we will need into one list
- fields = []
- for col in self.columns:
- if type(col['field_name']) is list:
- fields.extend(col['field_name'])
- else:
- fields.append(col['field_name'])
try:
for row in page.object_list:
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 10/22] toaster: Add an invalidate cache method on data update signal
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (8 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 09/22] toaster: Port layerdetails to ToasterTables Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 11/22] toaster-gui: Show task name for the builds in /builds Alex DAMIAN
` (11 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
Instead of relying on the cache expiring over an amount of time we can
listen to the changed notification on the models to invalidate the
cache. Also fixes overlapping cache names.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/orm/models.py | 12 ++++++++++++
lib/toaster/toastergui/tables.py | 5 -----
lib/toaster/toastergui/widgets.py | 6 ++++--
3 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index 0f72576..fb62a55 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -26,6 +26,7 @@ from django.utils import timezone
from django.core import validators
from django.conf import settings
+import django.db.models.signals
class GitURLValidator(validators.URLValidator):
import re
@@ -1176,3 +1177,14 @@ class LogMessage(models.Model):
message=models.CharField(max_length=240)
pathname = models.FilePathField(max_length=255, blank=True)
lineno = models.IntegerField(null=True)
+
+def invalidate_cache(**kwargs):
+ from django.core.cache import cache
+ try:
+ cache.clear()
+ except Exception as e:
+ print "Problem with cache backend: Failed to clear cache"
+ pass
+
+django.db.models.signals.post_save.connect(invalidate_cache)
+django.db.models.signals.post_delete.connect(invalidate_cache)
diff --git a/lib/toaster/toastergui/tables.py b/lib/toaster/toastergui/tables.py
index 78a7cb0..9a93ff9 100644
--- a/lib/toaster/toastergui/tables.py
+++ b/lib/toaster/toastergui/tables.py
@@ -323,11 +323,6 @@ class LayerRecipesTable(RecipesTable):
-
-
-
-
-
# This needs to be staticaly defined here as django reads the url patterns
# on start up
urlpatterns = (
diff --git a/lib/toaster/toastergui/widgets.py b/lib/toaster/toastergui/widgets.py
index b5dfbbc..8cf6e1b 100644
--- a/lib/toaster/toastergui/widgets.py
+++ b/lib/toaster/toastergui/widgets.py
@@ -245,6 +245,9 @@ class ToasterTable(View):
for key, val in request.GET.iteritems():
cache_name = cache_name + str(key) + str(val)
+ for key, val in kwargs.iteritems():
+ cache_name = cache_name + str(key) + str(val)
+
data = cache.get(cache_name)
if data:
@@ -306,8 +309,7 @@ class ToasterTable(View):
except FieldError:
print "Error: Requested field does not exist"
-
data = json.dumps(data, indent=2, cls=DjangoJSONEncoder)
- cache.set(cache_name, data, 10)
+ cache.set(cache_name, data, 60*30)
return data
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 11/22] toaster-gui: Show task name for the builds in /builds
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (9 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 10/22] toaster: Add an invalidate cache method on data update signal Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 12/22] toastergui: Consider task name when restarting a build " Alex DAMIAN
` (10 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Ed Bartosh <ed.bartosh@linux.intel.com>
Task name was only shown for the latest builds in project view.
Now it should be shown also in builds view.
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
lib/toaster/toastergui/templates/brtargets.html | 20 ++++++++++++++++++++
.../toastergui/templates/managed_mrb_section.html | 12 +++++-------
2 files changed, 25 insertions(+), 7 deletions(-)
create mode 100644 lib/toaster/toastergui/templates/brtargets.html
diff --git a/lib/toaster/toastergui/templates/brtargets.html b/lib/toaster/toastergui/templates/brtargets.html
new file mode 100644
index 0000000..4ebd058
--- /dev/null
+++ b/lib/toaster/toastergui/templates/brtargets.html
@@ -0,0 +1,20 @@
+<span data-toggle="tooltip"
+ {% if buildrequest.brtarget_set.all.count > 1 %}
+ title="Targets:
+ {% for target in buildrequest.brtarget_set.all %}
+ {% if target.task %}
+ {{target.target}}:{{target.task}}
+ {% else %}
+ {{target.target}}
+ {% endif %}
+ {%endfor%}"
+ {%endif%}>
+ {% if buildrequest.brtarget_set.all.0.task %}
+ {{buildrequest.brtarget_set.all.0.target}}:{{buildrequest.brtarget_set.all.0.task}}
+ {% else %}
+ {{buildrequest.brtarget_set.all.0.target}}
+ {% endif %}
+ {% if buildrequest.brtarget_set.all.count > 1 %}
+ (+ {{buildrequest.brtarget_set.all.count|add:"-1"}})
+ {% endif %}
+</span>
diff --git a/lib/toaster/toastergui/templates/managed_mrb_section.html b/lib/toaster/toastergui/templates/managed_mrb_section.html
index a6d4ac6..abcc0bb 100644
--- a/lib/toaster/toastergui/templates/managed_mrb_section.html
+++ b/lib/toaster/toastergui/templates/managed_mrb_section.html
@@ -26,8 +26,7 @@
{%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%}
- </span>
+ {% include "brtargets.html" %}
{%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %}
</a>
{% endif %}
@@ -84,7 +83,7 @@
{% if buildrequest.state == buildrequest.REQ_FAILED %}
<div class="span3 lead">
<a href="{%url 'buildrequestdetails' buildrequest.project.id buildrequest.pk%}" class="error">
- <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+ {% include "brtargets.html" %}
</a>
</div>
<div class="span2 lead">
@@ -113,8 +112,7 @@
{% elif buildrequest.state == buildrequest.REQ_QUEUED %}
<div class="lead span5">
-
- <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+ {% include "brtargets.html" %}
</div>
<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>
@@ -124,7 +122,7 @@
{% elif buildrequest.state == buildrequest.REQ_CREATED %}
<div class="lead span3">
- <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+ {% include "brtargets.html" %}
</div>
<div class="span6" >
<span class="lead">Creating build</span>
@@ -133,7 +131,7 @@
{% elif buildrequest.state == buildrequest.REQ_INPROGRESS %}
<div class="lead span5">
- <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span>
+ {% include "brtargets.html" %}
</div>
<div class="span4 lead">
Checking out layers
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 12/22] toastergui: Consider task name when restarting a build in /builds
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (10 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 11/22] toaster-gui: Show task name for the builds in /builds Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 13/22] toaster: refactor checksettings command Alex DAMIAN
` (9 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Ed Bartosh <ed.bartosh@linux.intel.com>
Previously the same issue was fixed for project view.
'Run again' button now restarts builds using target:task also in builds view.
[YOCTO #7442]
Signed-off-by: Ed Bartosh <ed.bartosh@linux.intel.com>
---
.../toastergui/templates/managed_mrb_section.html | 16 ++++++++++++++--
lib/toaster/toastergui/templates/runagain.html | 7 +++++++
lib/toaster/toastergui/templatetags/projecttags.py | 7 ++++---
3 files changed, 25 insertions(+), 5 deletions(-)
create mode 100644 lib/toaster/toastergui/templates/runagain.html
diff --git a/lib/toaster/toastergui/templates/managed_mrb_section.html b/lib/toaster/toastergui/templates/managed_mrb_section.html
index abcc0bb..c93c2af 100644
--- a/lib/toaster/toastergui/templates/managed_mrb_section.html
+++ b/lib/toaster/toastergui/templates/managed_mrb_section.html
@@ -54,7 +54,17 @@
Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a>
</span>
{% if build.project %}
- <button 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}}, {% url 'project' buildrequest.project.id as bpurl%}{{bpurl|json}}, {{build.get_sorted_target_list|mapselect:'target'|json}})'>Run again</button>
+ <button class="btn
+ {% if build.outcome == build.SUCCEEDED %}
+ btn-success
+ {% elif build.outcome == build.FAILED %}
+ btn-danger
+ {% else %}
+ btn-info
+ {%endif%}
+ pull-right"
+ {% include "runagain.html" %}
+ </button>
{% endif %}
</div>
{%endif%}
@@ -104,8 +114,10 @@
<span class="lead{%if not MANAGED or not buildrequest.project%} pull-right{%endif%}">
Build time: {{ buildrequest.get_duration|sectohms }}
</span>
- <button class="btn btn-danger pull-right" onclick='scheduleBuild({% url 'xhr_projectbuild' buildrequest.project.id as bpi%}{{bpi|json}}, {{buildrequest.project.name|json}}, {% url 'project' buildrequest.project.id as bpurl%}{{bpurl|json}}, {{buildrequest.get_sorted_target_list|mapselect:'target'|json}})'>Run again</button>
+ <button class="btn btn-danger pull-right"
+ {% include "runagain.html" %}
+ </button>
</div>
diff --git a/lib/toaster/toastergui/templates/runagain.html b/lib/toaster/toastergui/templates/runagain.html
new file mode 100644
index 0000000..a78c0dd
--- /dev/null
+++ b/lib/toaster/toastergui/templates/runagain.html
@@ -0,0 +1,7 @@
+{% load projecttags %}
+onclick='scheduleBuild(
+ {% url 'xhr_projectbuild' 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}})
+'>Run again
diff --git a/lib/toaster/toastergui/templatetags/projecttags.py b/lib/toaster/toastergui/templatetags/projecttags.py
index 1bc3bc2..8028ae0 100644
--- a/lib/toaster/toastergui/templatetags/projecttags.py
+++ b/lib/toaster/toastergui/templatetags/projecttags.py
@@ -44,9 +44,10 @@ def sectohms(time):
return "%02d:%02d:%02d" % (hours, int((tdsec - (hours * 3600))/ 60), int(tdsec) % 60)
-@register.filter(name = 'mapselect')
-def mapselect(value, argument):
- return map(lambda x: vars(x)[argument], value)
+@register.filter(name = 'get_tasks')
+def get_tasks(queryset):
+ return list(target + ':' + task if task else target \
+ for target, task in queryset.values_list('target', 'task'))
@register.filter(name = "json")
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 13/22] toaster: refactor checksettings command
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (11 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 12/22] toastergui: Consider task name when restarting a build " Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 14/22] toasterui: replace cookie-based preferences with session data Alex DAMIAN
` (8 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
This patch refactors the checksetting command to prevent
early return from the handle function.
It also adds a check that marks IN PROGRESS builds at startup time
as FAILED. Minor changes to BuildRequest and Build classes
ensure useful string representation for the objects.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
.../management/commands/checksettings.py | 26 ++++++++++++++++++++--
lib/toaster/bldcontrol/models.py | 3 +++
lib/toaster/orm/models.py | 3 +++
3 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/lib/toaster/bldcontrol/management/commands/checksettings.py b/lib/toaster/bldcontrol/management/commands/checksettings.py
index 1ff5c92..3c52446 100644
--- a/lib/toaster/bldcontrol/management/commands/checksettings.py
+++ b/lib/toaster/bldcontrol/management/commands/checksettings.py
@@ -2,7 +2,7 @@ from django.core.management.base import NoArgsCommand, CommandError
from django.db import transaction
from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException
from bldcontrol.models import BuildRequest, BuildEnvironment, BRError
-from orm.models import ToasterSetting
+from orm.models import ToasterSetting, Build
import os
def DN(path):
@@ -61,7 +61,7 @@ class Command(NoArgsCommand):
return DN(self._find_first_path_for_file(DN(self.guesspath), "bblayers.conf", 4))
- def handle(self, **options):
+ def _verify_artifact_storage_dir(self):
# verify that we have a settings for downloading artifacts
while ToasterSetting.objects.filter(name="ARTIFACTS_STORAGE_DIR").count() == 0:
guessedpath = os.getcwd() + "/toaster_build_artifacts/"
@@ -78,7 +78,10 @@ class Command(NoArgsCommand):
else:
raise ose
ToasterSetting.objects.create(name="ARTIFACTS_STORAGE_DIR", value=artifacts_storage_dir)
+ return 0
+
+ def _verify_build_environment(self):
self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__)))))))
# refuse to start if we have no build environments
while BuildEnvironment.objects.count() == 0:
@@ -197,12 +200,16 @@ class Command(NoArgsCommand):
while (_verify_be()):
pass
+ return 0
+ def _verify_default_settings(self):
# verify that default settings are there
if ToasterSetting.objects.filter(name = 'DEFAULT_RELEASE').count() != 1:
ToasterSetting.objects.filter(name = 'DEFAULT_RELEASE').delete()
ToasterSetting.objects.get_or_create(name = 'DEFAULT_RELEASE', value = '')
+ return 0
+ def _verify_builds_in_progress(self):
# 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")
@@ -211,4 +218,19 @@ class Command(NoArgsCommand):
BuildEnvironment.objects.update(lock = BuildEnvironment.LOCK_FREE)
+ # also mark "In Progress builds as failures"
+ from django.utils import timezone
+ Build.objects.filter(outcome = Build.IN_PROGRESS).update(outcome = Build.FAILED, completed_on = timezone.now())
+
return 0
+
+
+
+ def handle(self, **options):
+ retval = 0
+ retval += self._verify_artifact_storage_dir()
+ retval += self._verify_build_environment()
+ retval += self._verify_default_settings()
+ retval += self._verify_builds_in_progress()
+
+ return retval
diff --git a/lib/toaster/bldcontrol/models.py b/lib/toaster/bldcontrol/models.py
index 02cfaf7..b789446 100644
--- a/lib/toaster/bldcontrol/models.py
+++ b/lib/toaster/bldcontrol/models.py
@@ -125,6 +125,9 @@ class BuildRequest(models.Model):
def get_machine(self):
return self.brvariable_set.get(name="MACHINE").value
+ def __str__(self):
+ return "%s %s" % (self.project, self.get_state_display())
+
# These tables specify the settings for running an actual build.
# They MUST be kept in sync with the tables in orm.models.Project*
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index fb62a55..8a9a21e 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -267,6 +267,9 @@ class Build(models.Model):
def toaster_exceptions(self):
return self.logmessage_set.filter(level=LogMessage.EXCEPTION)
+ def __str__(self):
+ return "%d %s %s" % (self.id, self.project, ",".join([t.target for t in self.target_set.all()]))
+
# an Artifact is anything that results from a Build, and may be of interest to the user, and is not stored elsewhere
class BuildArtifact(models.Model):
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 14/22] toasterui: replace cookie-based preferences with session data
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (12 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 13/22] toaster: refactor checksettings command Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 15/22] toastergui: tests for the all-projects API point Alex DAMIAN
` (7 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
We switch from storing the user preferences using cookies
to saving them in the server-side session.
Patch for "count/pagesize" and "orderby" fields in the table-based
pages.
This patch will solve two problems:
* the browser-side race between the GET header data and the cookie data
* page breakages when field names chances in orderby statements.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
.../toastergui/templates/basetable_bottom.html | 4 ---
.../toastergui/templates/basetable_top.html | 9 +-----
.../templates/detail_pagination_bottom.html | 5 ---
.../toastergui/templates/detail_sorted_header.html | 2 +-
lib/toaster/toastergui/views.py | 37 +++++++++++-----------
5 files changed, 20 insertions(+), 37 deletions(-)
diff --git a/lib/toaster/toastergui/templates/basetable_bottom.html b/lib/toaster/toastergui/templates/basetable_bottom.html
index d48ad92..4c28cae 100644
--- a/lib/toaster/toastergui/templates/basetable_bottom.html
+++ b/lib/toaster/toastergui/templates/basetable_bottom.html
@@ -60,8 +60,6 @@
// load cookie for number of entries to be displayed on page
if ({{request.GET.count}} != "") {
pagesize = {{request.GET.count}};
- } else {
- pagesize = $.cookie('_count');
}
$('.pagesize option').prop('selected', false)
@@ -84,8 +82,6 @@
$('.progress, .lead span').tooltip({container:'table', placement:'top'});
$(".pagesize").change(function () {
- // save cookie with pagesize
- $.cookie("_count", $(this).val(), { path : $(location).attr('pathname') });
reload_params({"count":$(this).val()});
});
});
diff --git a/lib/toaster/toastergui/templates/basetable_top.html b/lib/toaster/toastergui/templates/basetable_top.html
index 92a3b50..fcd2f03 100644
--- a/lib/toaster/toastergui/templates/basetable_top.html
+++ b/lib/toaster/toastergui/templates/basetable_top.html
@@ -156,13 +156,6 @@
showhideImmediateTableAction( clname, sh, orderkey );
}
- //
- // saves a cookie with selected order field
- //
- function saveOrderCookie( orderfield ) {
- $.cookie("orderby", orderfield, { path: $(location).attr('pathname') });
- }
-
</script>
<!-- control header -->
@@ -230,7 +223,7 @@
<tr>
{% for tc in tablecols %}<th class="{{tc.dclass}} {{tc.clclass}}">
{%if tc.qhelp%}<i class="icon-question-sign get-help" title="{{tc.qhelp}}"></i>{%endif%}
- {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })" onclick="saveOrderCookie('{{tc.orderfield}}')">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%}
+ {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%}
{%if tc.ordericon%} <i class="icon-caret-{{tc.ordericon}}"></i>{%endif%}
{%if tc.filter%}<div class="btn-group pull-right">
<a href="#filter_{{tc.filter.class}}" role="button" class="btn btn-mini {%if request.GET.filter%}{{tc.filter.options|filtered_icon:request.GET.filter}} {%endif%}" {%if request.GET.filter and tc.filter.options|filtered_tooltip:request.GET.filter %} title="<p>{{tc.filter.options|filtered_tooltip:request.GET.filter}}</p><p><a class='btn btn-small btn-primary' href=javascript:reload_params({'filter':''})>Show all {% if filter_search_display %}{{filter_search_display}}{% else %}{{objectname}}{% endif %}</a></p>" {%endif%} data-toggle="modal"> <i class="icon-filter filtered"></i> </a>
diff --git a/lib/toaster/toastergui/templates/detail_pagination_bottom.html b/lib/toaster/toastergui/templates/detail_pagination_bottom.html
index 355ae90..434facb 100644
--- a/lib/toaster/toastergui/templates/detail_pagination_bottom.html
+++ b/lib/toaster/toastergui/templates/detail_pagination_bottom.html
@@ -41,17 +41,12 @@
// load cookie for number of entries to be displayed on page
if ({{request.GET.count}} != "") {
pagesize = {{request.GET.count}};
- } else {
- pagesize = $.cookie('_count');
}
-
$('.pagesize option').prop('selected', false)
.filter('[value="' + pagesize + '"]')
.attr('selected', true);
$(".pagesize").change(function () {
- // save cookie with pagesize
- $.cookie("_count", $(this).val(), { path : $(location).attr('pathname') });
reload_params({"count":$(this).val()});
});
});
diff --git a/lib/toaster/toastergui/templates/detail_sorted_header.html b/lib/toaster/toastergui/templates/detail_sorted_header.html
index a7917dc..5214444 100644
--- a/lib/toaster/toastergui/templates/detail_sorted_header.html
+++ b/lib/toaster/toastergui/templates/detail_sorted_header.html
@@ -11,7 +11,7 @@
<tr>
{% for tc in tablecols %}<th class="{{tc.dclass}} {{tc.clclass}}">
{%if tc.qhelp%}<i class="icon-question-sign get-help" title="{{tc.qhelp}}"></i>{%endif%}
- {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })" onclick="saveOrderCookie('{{tc.orderfield}}')">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%}
+ {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%}
{%if tc.ordericon%} <i class="icon-caret-{{tc.ordericon}}"></i>{%endif%}
{% if request.GET.search and forloop.first %}
<span class="badge badge-info">{{objects.paginator.count}}</span>
diff --git a/lib/toaster/toastergui/views.py b/lib/toaster/toastergui/views.py
index f70cf20..0e248a7 100755
--- a/lib/toaster/toastergui/views.py
+++ b/lib/toaster/toastergui/views.py
@@ -20,7 +20,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import operator,re
-import HTMLParser
from django.db.models import Q, Sum, Count, Max
from django.db import IntegrityError
@@ -278,11 +277,11 @@ def _get_parameters_values(request, default_count, default_order):
# set cookies for parameters. this is usefull in case parameters are set
# manually from the GET values of the link
-def _save_parameters_cookies(response, pagesize, orderby, request):
- html_parser = HTMLParser.HTMLParser()
- response.set_cookie(key='count', value=pagesize, path=request.path)
- response.set_cookie(key='orderby', value=html_parser.unescape(orderby), path=request.path)
- return response
+def _set_parameters_values(pagesize, orderby, request):
+ from django.core.urlresolvers import resolve
+ current_url = resolve(request.path_info).url_name
+ request.session['%s_count' % current_url] = pagesize
+ request.session['%s_orderby' % current_url] =orderby
# date range: normalize GUI's dd/mm/yyyy to date object
def _normalize_input_date(date_str,default):
@@ -563,7 +562,7 @@ def recipe_packages(request, build_id, recipe_id):
]
}
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def target_common( request, build_id, target_id, variant ):
@@ -737,7 +736,7 @@ eans multiple licenses exist that cover different parts of the source',
context['tablecols'].append(tc_layerDir)
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def target( request, build_id, target_id ):
@@ -1129,7 +1128,7 @@ def tasks_common(request, build_id, variant, task_anchor):
context['tablecols'].append(tc_log)
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def tasks(request, build_id):
@@ -1265,7 +1264,7 @@ def recipes(request, build_id):
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def configuration(request, build_id):
@@ -1378,7 +1377,7 @@ def configvars(request, build_id):
}
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def bpackage(request, build_id):
@@ -1479,7 +1478,7 @@ def bpackage(request, build_id):
context['tablecols'].append(tc_layerDir)
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def bfile(request, build_id, package_id):
@@ -1640,7 +1639,7 @@ def package_built_detail(request, build_id, package_id):
context['disable_sort'] = True;
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def package_built_dependencies(request, build_id, package_id):
@@ -1705,7 +1704,7 @@ def package_included_detail(request, build_id, target_id, package_id):
if paths.all().count() < 2:
context['disable_sort'] = True
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def package_included_dependencies(request, build_id, target_id, package_id):
@@ -1780,7 +1779,7 @@ def package_included_reverse_dependencies(request, build_id, target_id, package_
if objects.all().count() < 2:
context['disable_sort'] = True
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def image_information_dir(request, build_id, target_id, packagefile_id):
@@ -1842,7 +1841,7 @@ if toastermain.settings.MANAGED:
return _redirect_parameters( builds, request.GET, e.response)
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
@@ -2748,7 +2747,7 @@ if toastermain.settings.MANAGED:
return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid)
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
@@ -2967,7 +2966,7 @@ if toastermain.settings.MANAGED:
}
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
def buildrequestdetails(request, pid, brid):
@@ -3171,7 +3170,7 @@ else:
context.update(context_date)
response = render(request, template, context)
- _save_parameters_cookies(response, pagesize, orderby, request)
+ _set_parameters_values(pagesize, orderby, request)
return response
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 15/22] toastergui: tests for the all-projects API point
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (13 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 14/22] toasterui: replace cookie-based preferences with session data Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 16/22] toastergui: add tests for xhr_datatypeahead calls Alex DAMIAN
` (6 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
This patch adds Django tests that verify that the 'all-projects'
page returns a valid HTML page when invoked normally, containing
the project name; and valid JSON containing API-needed fields
if the GET parameter `format` is set to "json"
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/toastergui/tests.py | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 lib/toaster/toastergui/tests.py
diff --git a/lib/toaster/toastergui/tests.py b/lib/toaster/toastergui/tests.py
new file mode 100644
index 0000000..8a78a41
--- /dev/null
+++ b/lib/toaster/toastergui/tests.py
@@ -0,0 +1,36 @@
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from orm.models import Project, Release, BitbakeVersion, Build
+
+class AllProjectsViewTestCase(TestCase):
+ TEST_PROJECT_NAME = "test project"
+
+ def setUp(self):
+ bbv, created = BitbakeVersion.objects.get_or_create(name="test bbv", giturl="/tmp/", branch="master", dirpath="")
+ release, created = Release.objects.get_or_create(name="test release", bitbake_version = bbv)
+ Project.objects.create_project(name=AllProjectsViewTestCase.TEST_PROJECT_NAME, release=release)
+
+ def test_get_base_call_returns_html(self):
+ response = self.client.get(reverse('all-projects'), follow=True)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(response['Content-Type'].startswith('text/html'))
+ self.assertTemplateUsed(response, "projects.html")
+ self.assertTrue(AllProjectsViewTestCase.TEST_PROJECT_NAME in response.content)
+
+ def test_get_json_call_returns_json(self):
+ response = self.client.get(reverse('all-projects'), {"format": "json"}, follow=True)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(response['Content-Type'].startswith('application/json'))
+ try:
+ import json
+ data = json.loads(response.content)
+ except:
+ self.fail("Response %s is not json-loadable" % response.content)
+
+ self.assertTrue("list" in data)
+ 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("projectPageUrl" in data["list"][0])
+ self.assertTrue("xhrProjectEditUrl" in data["list"][0])
+ self.assertTrue("projectBuildUrl" in data["list"][0])
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 16/22] toastergui: add tests for xhr_datatypeahead calls
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (14 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 15/22] toastergui: tests for the all-projects API point Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 17/22] bldcontrol: Fix all failing unit tests Alex DAMIAN
` (5 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
In order to make sure we replace the xhr_datatypeahead calls
with similar APIs, we add tests that validate the data returned
by the xhr_datatypeahead. A copy of these tests will be enabled
for each type of datatypeahead that will be replaced.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/toastergui/tests.py | 50 +++++++++++++++++++++++++++++++++++++----
1 file changed, 46 insertions(+), 4 deletions(-)
diff --git a/lib/toaster/toastergui/tests.py b/lib/toaster/toastergui/tests.py
index 8a78a41..0f10020 100644
--- a/lib/toaster/toastergui/tests.py
+++ b/lib/toaster/toastergui/tests.py
@@ -1,14 +1,18 @@
from django.test import TestCase
from django.core.urlresolvers import reverse
from orm.models import Project, Release, BitbakeVersion, Build
+from orm.models import ReleaseLayerSourcePriority, LayerSource, Layer, Layer_Version
-class AllProjectsViewTestCase(TestCase):
+class ProvisionedProjectTestCase(TestCase):
TEST_PROJECT_NAME = "test project"
def setUp(self):
- bbv, created = BitbakeVersion.objects.get_or_create(name="test bbv", giturl="/tmp/", branch="master", dirpath="")
- release, created = Release.objects.get_or_create(name="test release", bitbake_version = bbv)
- Project.objects.create_project(name=AllProjectsViewTestCase.TEST_PROJECT_NAME, release=release)
+ self.bbv, created = BitbakeVersion.objects.get_or_create(name="test bbv", giturl="/tmp/", branch="master", dirpath="")
+ self.release, created = Release.objects.get_or_create(name="test release", bitbake_version = self.bbv)
+ self.project = Project.objects.create_project(name=AllProjectsViewTestCase.TEST_PROJECT_NAME, release=self.release)
+
+
+class AllProjectsViewTestCase(ProvisionedProjectTestCase):
def test_get_base_call_returns_html(self):
response = self.client.get(reverse('all-projects'), follow=True)
@@ -21,16 +25,54 @@ class AllProjectsViewTestCase(TestCase):
response = self.client.get(reverse('all-projects'), {"format": "json"}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertTrue(response['Content-Type'].startswith('application/json'))
+
try:
import json
data = json.loads(response.content)
except:
self.fail("Response %s is not json-loadable" % response.content)
+ self.assertTrue("error" in data)
+ self.assertEqual(data["error"], "ok")
self.assertTrue("list" in data)
+
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("projectPageUrl" in data["list"][0])
self.assertTrue("xhrProjectEditUrl" in data["list"][0])
self.assertTrue("projectBuildUrl" in data["list"][0])
+
+class ProvisionedLayersProjectTestCase(ProvisionedProjectTestCase):
+ LAYER_NAME = "base-layer"
+ def setUp(self):
+ super(ProvisionedLayersProjectTestCase, self).setUp()
+ self.layersource, created = LayerSource.objects.get_or_create(sourcetype = LayerSource.TYPE_IMPORTED)
+ self.releaselayersourcepriority, created = ReleaseLayerSourcePriority.objects.get_or_create(release = self.release, layer_source = self.layersource)
+ self.layer, created = Layer.objects.get_or_create(name=XHRDataTypeAheadTestCase.LAYER_NAME, layer_source=self.layersource, vcs_url="/tmp/")
+ self.lv, created = Layer_Version.objects.get_or_create(layer = self.layer, project = self.project, layer_source=self.layersource, commit="master")
+
+
+class XHRDataTypeAheadTestCase(ProvisionedLayersProjectTestCase):
+
+ def setUp(self):
+ super(XHRDataTypeAheadTestCase, self).setUp()
+ 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"})
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(response['Content-Type'].startswith('application/json'))
+
+ try:
+ import json
+ data = json.loads(response.content)
+ except:
+ self.fail("Response %s is not json-loadable" % response.content)
+
+ self.assertTrue("error" in data)
+ self.assertEqual(data["error"], "ok")
+ self.assertTrue("list" in data)
+ self.assertTrue(len(data["list"]) > 0)
+
+ self.assertTrue(XHRDataTypeAheadTestCase.LAYER_NAME in map(lambda x: x["name"], data["list"]))
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 17/22] bldcontrol: Fix all failing unit tests
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (15 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 16/22] toastergui: add tests for xhr_datatypeahead calls Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 18/22] orm: Fix all failing unit test Alex DAMIAN
` (4 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
This fixes the unit tests for the bldcontrol it requires the
implementation of a new Exception type so that a known Exception can be
handled. Also fixed is the path to the toaster conf files so that the
test doesn't need to be run from the top level directory and the ability
to specify the values of TTS_SOURCE_DIR and TTS_BUILD_DIR and
TTS_TEST_ADDRESS used for testing.
Edited by Alex Damian to correct the rebasing of the localhostbecontroller.py
file.
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/bldcontrol/localhostbecontroller.py | 2 +-
lib/toaster/bldcontrol/sshbecontroller.py | 5 +-
lib/toaster/bldcontrol/tests.py | 84 ++++++++++++++++---------
3 files changed, 61 insertions(+), 30 deletions(-)
diff --git a/lib/toaster/bldcontrol/localhostbecontroller.py b/lib/toaster/bldcontrol/localhostbecontroller.py
index dfe06f9..bc3566a 100644
--- a/lib/toaster/bldcontrol/localhostbecontroller.py
+++ b/lib/toaster/bldcontrol/localhostbecontroller.py
@@ -117,7 +117,7 @@ class LocalhostBEController(BuildEnvironmentController):
f.seek(0, 2) # jump to the end
toaster_ui_log_filelength = f.tell()
- cmd = "bash -c \"source %s/oe-init-build-env %s 2>&1 >toaster_server.log && bitbake --read conf/toaster-pre.conf --postread conf/toaster.conf --server-only -t xmlrpc -B 0.0.0.0:0 2>&1 >>toaster_server.log \"" % (self.pokydirname, self.be.builddir)
+ cmd = "bash -c \"source %s/oe-init-build-env %s 2>&1 >toaster_server.log && bitbake --read %s/conf/toaster-pre.conf --postread %s/conf/toaster.conf --server-only -t xmlrpc -B 0.0.0.0:0 2>&1 >>toaster_server.log \"" % (self.pokydirname, self.be.builddir, self.be.builddir, self.be.builddir)
port = "-1"
logger.debug("localhostbecontroller: starting builder \n%s\n" % cmd)
diff --git a/lib/toaster/bldcontrol/sshbecontroller.py b/lib/toaster/bldcontrol/sshbecontroller.py
index 11ad08d..29ed0a7 100644
--- a/lib/toaster/bldcontrol/sshbecontroller.py
+++ b/lib/toaster/bldcontrol/sshbecontroller.py
@@ -31,6 +31,9 @@ from toastermain import settings
from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException
+class NotImplementedException(Exception):
+ pass
+
def DN(path):
return "/".join(path.split("/")[0:-1])
@@ -125,7 +128,7 @@ class SSHBEController(BuildEnvironmentController):
# set layers in the layersource
- raise Exception("Not implemented: SSH setLayers")
+ raise NotImplementedException("Not implemented: SSH setLayers")
# 3. configure the build environment, so we have a conf/bblayers.conf
assert self.pokydirname is not None
self._setupBE()
diff --git a/lib/toaster/bldcontrol/tests.py b/lib/toaster/bldcontrol/tests.py
index 5a9d1df..5dbc77f 100644
--- a/lib/toaster/bldcontrol/tests.py
+++ b/lib/toaster/bldcontrol/tests.py
@@ -7,7 +7,7 @@ Replace this with more appropriate tests for your application.
from django.test import TestCase
-from bldcontrol.bbcontroller import BitbakeController
+from bldcontrol.bbcontroller import BitbakeController, BuildSetupException
from bldcontrol.localhostbecontroller import LocalhostBEController
from bldcontrol.sshbecontroller import SSHBEController
from bldcontrol.models import BuildEnvironment, BuildRequest
@@ -15,6 +15,7 @@ from bldcontrol.management.commands.runbuilds import Command
import socket
import subprocess
+import os
# standard poky data hardcoded for testing
BITBAKE_LAYERS = [type('bitbake_info', (object,), { "giturl": "git://git.yoctoproject.org/poky.git", "dirpath": "", "commit": "HEAD"})]
@@ -29,6 +30,17 @@ POKY_LAYERS = [
# we have an abstract test class designed to ensure that the controllers use a single interface
# specific controller tests only need to override the _getBuildEnvironment() method
+test_sourcedir = os.getenv("TTS_SOURCE_DIR")
+test_builddir = os.getenv("TTS_BUILD_DIR")
+test_address = os.getenv("TTS_TEST_ADDRESS", "localhost")
+
+if test_sourcedir == None or test_builddir == None or test_address == None:
+ raise Exception("Please set TTTS_SOURCE_DIR, TTS_BUILD_DIR and TTS_TEST_ADDRESS")
+
+# The bb server will expect a toaster-pre.conf file to exist. If it doesn't exit then we make
+# an empty one here.
+open(test_builddir + 'conf/toaster-pre.conf', 'a').close()
+
class BEControllerTests(object):
def _serverForceStop(self, bc):
@@ -36,28 +48,53 @@ class BEControllerTests(object):
self.assertTrue(err == '', "bitbake server pid %s not stopped" % err)
def test_serverStartAndStop(self):
+ from bldcontrol.sshbecontroller import NotImplementedException
obe = self._getBuildEnvironment()
bc = self._getBEController(obe)
- bc.setLayers(BITBAKE_LAYERS, POKY_LAYERS) # setting layers, skip any layer info
+ try:
+ # setting layers, skip any layer info
+ bc.setLayers(BITBAKE_LAYERS, POKY_LAYERS)
+ except NotImplementedException, e:
+ print "Test skipped due to command not implemented yet"
+ return True
+ # We are ok with the exception as we're handling the git already exists
+ except BuildSetupException:
+ pass
+
+ bc.pokydirname = test_sourcedir
+ bc.islayerset = True
- hostname = self.test_address.split("@")[-1]
+ hostname = test_address.split("@")[-1]
# test start server and stop
- self.assertTrue(socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex((hostname, 8200)), "Port already occupied")
- bc.startBBServer("0:0")
- self.assertFalse(socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex((hostname, 8200)), "Server not answering")
+ bc.startBBServer()
+
+ self.assertFalse(socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex((hostname, int(bc.be.bbport))), "Server not answering")
bc.stopBBServer()
- self.assertTrue(socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex((hostname, 8200)), "Server not stopped")
+ self.assertTrue(socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex((hostname, int(bc.be.bbport))), "Server not stopped")
self._serverForceStop(bc)
def test_getBBController(self):
+ from bldcontrol.sshbecontroller import NotImplementedException
obe = self._getBuildEnvironment()
bc = self._getBEController(obe)
- bc.setLayers(BITBAKE_LAYERS, POKY_LAYERS) # setting layers, skip any layer info
-
- bbc = bc.getBBController("%d:%d" % (-1, obe.pk))
+ layerSet = False
+ try:
+ # setting layers, skip any layer info
+ layerSet = bc.setLayers(BITBAKE_LAYERS, POKY_LAYERS)
+ except NotImplementedException:
+ print "Test skipped due to command not implemented yet"
+ return True
+ # We are ok with the exception as we're handling the git already exists
+ except BuildSetupException:
+ pass
+
+ bc.pokydirname = test_sourcedir
+ bc.islayerset = True
+
+ bbc = bc.getBBController()
self.assertTrue(isinstance(bbc, BitbakeController))
bc.stopBBServer()
@@ -66,19 +103,15 @@ class BEControllerTests(object):
class LocalhostBEControllerTests(TestCase, BEControllerTests):
def __init__(self, *args):
super(LocalhostBEControllerTests, self).__init__(*args)
- # hardcoded for Alex's machine; since the localhost BE is machine-dependent,
- # I found no good way to abstractize this
- self.test_sourcedir = "/home/ddalex/ssd/yocto"
- self.test_builddir = "/home/ddalex/ssd/yocto/build"
- self.test_address = "localhost"
+
def _getBuildEnvironment(self):
return BuildEnvironment.objects.create(
lock = BuildEnvironment.LOCK_FREE,
betype = BuildEnvironment.TYPE_LOCAL,
- address = self.test_address,
- sourcedir = self.test_sourcedir,
- builddir = self.test_builddir )
+ address = test_address,
+ sourcedir = test_sourcedir,
+ builddir = test_builddir )
def _getBEController(self, obe):
return LocalhostBEController(obe)
@@ -86,25 +119,20 @@ class LocalhostBEControllerTests(TestCase, BEControllerTests):
class SSHBEControllerTests(TestCase, BEControllerTests):
def __init__(self, *args):
super(SSHBEControllerTests, self).__init__(*args)
- self.test_address = "ddalex-desktop.local"
- # hardcoded for ddalex-desktop.local machine; since the localhost BE is machine-dependent,
- # I found no good way to abstractize this
- self.test_sourcedir = "/home/ddalex/ssd/yocto"
- self.test_builddir = "/home/ddalex/ssd/yocto/build"
def _getBuildEnvironment(self):
return BuildEnvironment.objects.create(
lock = BuildEnvironment.LOCK_FREE,
betype = BuildEnvironment.TYPE_SSH,
- address = self.test_address,
- sourcedir = self.test_sourcedir,
- builddir = self.test_builddir )
+ address = test_address,
+ sourcedir = test_sourcedir,
+ builddir = test_builddir )
def _getBEController(self, obe):
return SSHBEController(obe)
def test_pathExists(self):
- obe = BuildEnvironment.objects.create(betype = BuildEnvironment.TYPE_SSH, address= self.test_address)
+ obe = BuildEnvironment.objects.create(betype = BuildEnvironment.TYPE_SSH, address= test_address)
sbc = SSHBEController(obe)
self.assertTrue(sbc._pathexists("/"))
self.assertFalse(sbc._pathexists("/.deadbeef"))
@@ -129,7 +157,7 @@ class RunBuildsCommandTests(TestCase):
self.assertRaises(IndexError, command._selectBuildEnvironment)
def test_br_select(self):
- from orm.models import Project, Release, BitbakeVersion
+ from orm.models import Project, Release, BitbakeVersion, Branch
p = Project.objects.create_project("test", Release.objects.get_or_create(name = "HEAD", bitbake_version = BitbakeVersion.objects.get_or_create(name="HEAD", branch=Branch.objects.get_or_create(name="HEAD"))[0])[0])
obr = BuildRequest.objects.create(state = BuildRequest.REQ_QUEUED, project = p)
command = Command()
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 18/22] orm: Fix all failing unit test
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (16 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 17/22] bldcontrol: Fix all failing unit tests Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 19/22] toaster: enable no browser start Alex DAMIAN
` (3 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Michael Wood <michael.g.wood@intel.com>
This fixes all the unit tests for the orm. Also added is the ability to
set a custom Layer index if you want to avoid using the public one by
specifying TTS_LAYER_INDEX
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
lib/toaster/orm/models.py | 5 ++++-
lib/toaster/orm/tests.py | 32 ++++++++++++++++++++------------
2 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/lib/toaster/orm/models.py b/lib/toaster/orm/models.py
index 8a9a21e..198c6f5 100644
--- a/lib/toaster/orm/models.py
+++ b/lib/toaster/orm/models.py
@@ -782,8 +782,11 @@ class LayerIndexLayerSource(LayerSource):
print "EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e))
return
- # update branches; only those that we already have names listed in the Releases table
+ # update branches; only those that we already have names listed in the
+ # Releases table
whitelist_branch_names = map(lambda x: x.branch_name, Release.objects.all())
+ if len(whitelist_branch_names) == 0:
+ raise Exception("Failed to make list of branches to fetch")
print "Fetching branches"
branches_info = _get_json_response(apilinks['branches']
diff --git a/lib/toaster/orm/tests.py b/lib/toaster/orm/tests.py
index 7b1b963..d4d97ee 100644
--- a/lib/toaster/orm/tests.py
+++ b/lib/toaster/orm/tests.py
@@ -1,4 +1,4 @@
-from django.test import TestCase
+from django.test import TestCase, TransactionTestCase
from orm.models import LocalLayerSource, LayerIndexLayerSource, ImportedLayerSource, LayerSource
from orm.models import Branch
@@ -7,6 +7,10 @@ from orm.models import Release, ReleaseLayerSourcePriority, BitbakeVersion
from django.utils import timezone
+import os
+
+# set TTS_LAYER_INDEX to the base url to use a different instance of the layer index
+
# tests to verify inheritance for the LayerSource proxy-inheritance classes
class LayerSourceVerifyInheritanceSaveLoad(TestCase):
def test_object_creation(self):
@@ -29,17 +33,20 @@ class LayerSourceVerifyInheritanceSaveLoad(TestCase):
self.assertRaises(Exception, duplicate)
-# test to verify the layer source update functionality for layerindex. edit to pass the URL to a layerindex application
-class LILSUpdateTestCase(TestCase):
- def test_update(self):
- lils = LayerSource.objects.create(name = "b1", sourcetype = LayerSource.TYPE_LAYERINDEX, apiurl = "http://adamian-desk.local:8080/layerindex/api/")
- lils.update()
+class LILSUpdateTestCase(TransactionTestCase):
+ def setUp(self):
+ # create release
+ bbv = BitbakeVersion.objects.create(name="master", giturl="git://git.openembedded.org/bitbake")
+ release = Release.objects.create(name="default-release", bitbake_version = bbv, branch_name = "master")
- # run second update
- # lils.update()
+ def test_update(self):
+ layer_index_url = os.getenv("TTS_LAYER_INDEX")
+ if layer_index_url == None:
+ print "Using layers.openembedded.org for layer index. override with TTS_LAYER_INDEX enviroment variable"
+ layer_index_url = "http://layers.openembedded.org/"
- # print vars(lils)
- #print map(lambda x: vars(x), Branch.objects.all())
+ lils = LayerSource.objects.create(name = "b1", sourcetype = LayerSource.TYPE_LAYERINDEX, apiurl = layer_index_url + "layerindex/api/")
+ lils.update()
# run asserts
self.assertTrue(lils.branch_set.all().count() > 0, "update() needs to fetch some branches")
@@ -59,6 +66,7 @@ class LayerVersionEquivalenceTestCase(TestCase):
# attach layer source to release
ReleaseLayerSourcePriority.objects.create(release = release, layer_source = ls, priority = 1)
+
# create layer attach
self.layer = Layer.objects.create(name="meta-testlayer", layer_source = ls)
# create branch
@@ -84,7 +92,7 @@ class LayerVersionEquivalenceTestCase(TestCase):
def test_dual_layersource(self):
# if we have two layers with the same name, from different layer sources, we expect both layers in, in increasing priority of the layer source
- ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL)
+ ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL, apiurl="test")
# assign a lower priority for the second layer source
Release.objects.get(name="default-release").releaselayersourcepriority_set.create(layer_source = ls2, priority = 2)
@@ -149,7 +157,7 @@ class ProjectLVSelectionTestCase(TestCase):
def test_dual_layersource(self):
# if we have two layers with the same name, from different layer sources, we expect both layers in, in increasing priority of the layer source
- ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL)
+ ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL, apiurl="testing")
# assign a lower priority for the second layer source
Release.objects.get(name="default-release").releaselayersourcepriority_set.create(layer_source = ls2, priority = 2)
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 19/22] toaster: enable no browser start
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (17 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 18/22] orm: Fix all failing unit test Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 20/22] bitbake/: toaster tts: fix tests Alex DAMIAN
` (2 subsequent siblings)
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
We enable a "nobrowser" parameter that inhibits
the launch of a browser when toaster starts.
This is useful for integration with automated startup scripts
and enables headless testing.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
bin/toaster | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/bin/toaster b/bin/toaster
index 93abcfa..ee19937 100755
--- a/bin/toaster
+++ b/bin/toaster
@@ -154,6 +154,7 @@ NOTOASTERUI=0
WEBSERVER=1
TOASTER_BRBE=""
WEB_PORT="8000"
+NOBROWSER=0
for param in $*; do
case $param in
@@ -163,6 +164,9 @@ for param in $*; do
noweb )
WEBSERVER=0
;;
+ nobrowser )
+ NOBROWSER=1
+ ;;
brbe=* )
TOASTER_BRBE=$'\n'"TOASTER_BRBE=\""${param#*=}"\""
;;
@@ -227,7 +231,7 @@ if [ `basename \"$0\"` = `basename \"${SRCFILE}\"` ]; then
cleanup
exit 1
fi
- if [ $WEBSERVER -gt 0 ]; then
+ if [ $WEBSERVER -gt 0 ] && [ $NOBROWSER -eq 0 ] ; then
echo "Starting browser..."
xdg-open http://127.0.0.1:$WEB_PORT/ >/dev/null 2>&1 &
fi
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 20/22] bitbake/: toaster tts: fix tests
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (18 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 19/22] toaster: enable no browser start Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 21/22] toaster tests: enable url check test Alex DAMIAN
2015-05-28 14:14 ` [PATCH 22/22] toaster: more HTML5 fixes Alex DAMIAN
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/contrib/tts/backlog.txt | 2 --
lib/toaster/contrib/tts/launcher.py | 2 +-
lib/toaster/contrib/tts/runner.py | 2 +-
lib/toaster/contrib/tts/tests.py | 2 +-
4 files changed, 3 insertions(+), 5 deletions(-)
delete mode 100644 lib/toaster/contrib/tts/backlog.txt
diff --git a/lib/toaster/contrib/tts/backlog.txt b/lib/toaster/contrib/tts/backlog.txt
deleted file mode 100644
index 6f92f7a..0000000
--- a/lib/toaster/contrib/tts/backlog.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-michaelw/toaster-frontend-cleanups|PENDING
-adamian/test_0|PENDING
diff --git a/lib/toaster/contrib/tts/launcher.py b/lib/toaster/contrib/tts/launcher.py
index 5b63bc1..90c6d95 100755
--- a/lib/toaster/contrib/tts/launcher.py
+++ b/lib/toaster/contrib/tts/launcher.py
@@ -87,7 +87,7 @@ if __name__ == "__main__":
errtext = None
out = None
try:
- out = shellutils.run_shell_cmd("./runner.py %s" % next_task)
+ out = shellutils.run_shell_cmd("%s %s" % (os.path.join(os.path.dirname(__file__), "runner.py"), next_task))
pass
except ShellCmdException as e:
print("Failed while running the test runner: %s", e)
diff --git a/lib/toaster/contrib/tts/runner.py b/lib/toaster/contrib/tts/runner.py
index d2a6099..f55c493 100755
--- a/lib/toaster/contrib/tts/runner.py
+++ b/lib/toaster/contrib/tts/runner.py
@@ -168,7 +168,7 @@ def read_settings():
# cleanup !
def clean_up(testdir):
# TODO: delete the test dir
- #run_shell_cmd("rm -rf -- '%s'" % testdir)
+ run_shell_cmd("rm -rf -- '%s'" % testdir)
pass
if __name__ == "__main__":
diff --git a/lib/toaster/contrib/tts/tests.py b/lib/toaster/contrib/tts/tests.py
index 3d4cfea..2e630db 100644
--- a/lib/toaster/contrib/tts/tests.py
+++ b/lib/toaster/contrib/tts/tests.py
@@ -44,7 +44,7 @@ class TestPySystemStart(unittest.TestCase):
def test_start_interactive_mode(self):
try:
- run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start && source toaster stop'" % config.testdir, config.testdir)
+ run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start webport=56789 && source toaster stop'" % config.testdir, config.testdir)
except ShellCmdException as e:
self.fail("Failed starting interactive mode: %s" % (e))
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 21/22] toaster tests: enable url check test
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (19 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 20/22] bitbake/: toaster tts: fix tests Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
2015-05-28 14:14 ` [PATCH 22/22] toaster: more HTML5 fixes Alex DAMIAN
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
Integrate the HTML5 validation as a test instead of
calling a separate script. This enables us to get the
HTML5 validation report as part of patch-level
testing.
gitignore the cache directory created by the http client
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/contrib/tts/config.py | 21 ++++++++++++++
lib/toaster/contrib/tts/runner.py | 2 +-
lib/toaster/contrib/tts/tests.py | 55 +++++++++++++++++++++++++++++++++++--
lib/toaster/contrib/tts/urlcheck.py | 41 +++++++++++++--------------
4 files changed, 94 insertions(+), 25 deletions(-)
diff --git a/lib/toaster/contrib/tts/config.py b/lib/toaster/contrib/tts/config.py
index a4ea8cf..c0e0536 100644
--- a/lib/toaster/contrib/tts/config.py
+++ b/lib/toaster/contrib/tts/config.py
@@ -22,6 +22,7 @@
# everything that would be a global variable goes here
import os, sys, logging
+import socket
LOGDIR = "log"
SETTINGS_FILE = os.path.join(os.path.dirname(__file__), "settings.json")
@@ -29,6 +30,26 @@ TEST_DIR_NAME = "tts_testdir"
OWN_PID = os.getpid()
+W3C_VALIDATOR = "http://icarus.local/w3c-validator/check?doctype=HTML5&uri="
+
+#TODO assign port dynamically
+TOASTER_PORT=56789
+
+#we parse the w3c URL to know where to connect
+
+import urlparse
+
+def get_public_ip():
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ p = urlparse.urlparse("http://icarus.local/w3c-validator/check?doctype=HTML5&uri=")
+ s.connect(( p.netloc, 80 if p.port is None else p.port))
+ hn = s.getsockname()[0]
+ s.close()
+ return hn
+
+TOASTER_BASEURL="http://%s:%d/" % (get_public_ip(), TOASTER_PORT)
+
+
OWN_EMAIL_ADDRESS = "Toaster Testing Framework <alexandru.damian@intel.com>"
REPORT_EMAIL_ADDRESS = "alexandru.damian@intel.com"
diff --git a/lib/toaster/contrib/tts/runner.py b/lib/toaster/contrib/tts/runner.py
index f55c493..e7e4dd2 100755
--- a/lib/toaster/contrib/tts/runner.py
+++ b/lib/toaster/contrib/tts/runner.py
@@ -181,7 +181,7 @@ if __name__ == "__main__":
no_failures = 1
try:
if options.testdir is not None and os.path.exists(options.testdir):
- testdir = options.testdir
+ testdir = os.path.abspath(options.testdir)
config.logger.info("No checkout, using %s" % testdir)
else:
need_cleanup = True
diff --git a/lib/toaster/contrib/tts/tests.py b/lib/toaster/contrib/tts/tests.py
index 2e630db..15a9a87 100644
--- a/lib/toaster/contrib/tts/tests.py
+++ b/lib/toaster/contrib/tts/tests.py
@@ -26,6 +26,9 @@
import unittest
from shellutils import *
+import pexpect
+import sys, os, signal, time
+
class TestPyCompilable(unittest.TestCase):
''' Verifies that all Python files are syntactically correct '''
def test_compile_file(self):
@@ -44,14 +47,62 @@ class TestPySystemStart(unittest.TestCase):
def test_start_interactive_mode(self):
try:
- run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start webport=56789 && source toaster stop'" % config.testdir, config.testdir)
+ run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start webport=%d && source toaster stop'" % (config.testdir, config.TOASTER_PORT), config.testdir)
except ShellCmdException as e:
self.fail("Failed starting interactive mode: %s" % (e))
def test_start_managed_mode(self):
try:
- run_shell_cmd("./poky/bitbake/bin/toaster webport=56789 & sleep 10 && curl http://localhost:56789/ && kill -2 %1")
+ run_shell_cmd("%s/bitbake/bin/toaster webport=%d nobrowser & sleep 10 && curl http://localhost:%d/ && kill -2 %1" % (config.testdir, config.TOASTER_PORT, config.TOASTER_PORT), config.testdir)
pass
except ShellCmdException as e:
self.fail("Failed starting managed mode: %s" % (e))
+class TestHTML5Compliance(unittest.TestCase):
+ def setUp(self):
+ self.origdir = os.getcwd()
+ self.crtdir = os.path.dirname(config.testdir)
+ os.chdir(self.crtdir)
+ if not os.path.exists(os.path.join(self.crtdir, "toaster.sqlite")):
+ run_shell_cmd("%s/bitbake/lib/toaster/manage.py syncdb --noinput" % config.testdir)
+ run_shell_cmd("%s/bitbake/lib/toaster/manage.py migrate orm" % config.testdir)
+ run_shell_cmd("%s/bitbake/lib/toaster/manage.py migrate bldcontrol" % config.testdir)
+ run_shell_cmd("%s/bitbake/lib/toaster/manage.py loadconf %s/meta-yocto/conf/toasterconf.json" % (config.testdir, config.testdir))
+
+ setup = pexpect.spawn("%s/bitbake/lib/toaster/manage.py checksettings" % config.testdir)
+ setup.logfile = sys.stdout
+ setup.expect(r".*or type the full path to a different directory: ")
+ setup.sendline('')
+ setup.sendline('')
+ setup.expect(r".*or type the full path to a different directory: ")
+ setup.sendline('')
+ setup.expect(r"Enter your option: ")
+ setup.sendline('0')
+
+ self.child = pexpect.spawn("%s/bitbake/bin/toaster webport=%d nobrowser" % (config.testdir, config.TOASTER_PORT))
+ self.child.logfile=sys.stdout
+ self.child.expect("Toaster is now running. You can stop it with Ctrl-C")
+
+ def test_html5_compliance(self):
+ import urllist, urlcheck
+ results = {}
+ for url in urllist.URLS:
+ results[url] = urlcheck.validate_html5(config.TOASTER_BASEURL + url)
+
+ failed = []
+ for url in results:
+ if results[url][1] != 0:
+ failed.append((url, results[url]))
+
+
+ self.assertTrue(len(failed)== 0, "Not all URLs validate: \n%s " % "\n".join(map(lambda x: "".join(str(x)),failed)))
+
+ #(config.TOASTER_BASEURL + url, status, errors, warnings))
+
+ def tearDown(self):
+ while self.child.isalive():
+ self.child.kill(signal.SIGINT)
+ time.sleep(1)
+ os.chdir(self.origdir)
+# if os.path.exists(os.path.join(self.crtdir, "toaster.sqlite")):
+# os.remove(os.path.join(self.crtdir, "toaster.sqlite"))
diff --git a/lib/toaster/contrib/tts/urlcheck.py b/lib/toaster/contrib/tts/urlcheck.py
index a94af50..86d7caa 100644
--- a/lib/toaster/contrib/tts/urlcheck.py
+++ b/lib/toaster/contrib/tts/urlcheck.py
@@ -7,38 +7,35 @@ import time
import config
import urllist
-# TODO: spawn server here
-BASEURL="http://localhost:8000/"
-
-#def print_browserlog(url):
-# driver = webdriver.Firefox()
-# driver.get(url)
-# body = driver.find_element_by_tag_name("body")
-# body.send_keys(Keys.CONTROL + 't')
-# for i in driver.get_log('browser'):
-# print(i)
-# driver.close()
-
# TODO: turn to a test
-def validate_html(url):
+def validate_html5(url):
h = httplib2.Http(".cache")
+ status = "Failed"
+ errors = -1
+ warnings = -1
+
# TODO: the w3c-validator must be a configurable setting
- urlrequest = "http://icarus.local/w3c-validator/check?doctype=HTML5&uri="+url
+ urlrequest = config.W3C_VALIDATOR+url
try:
resp, content = h.request(urlrequest, "HEAD")
- if resp['x-w3c-validator-status'] == "Abort":
- config.logger.error("FAILed call %s" % url)
- else:
- config.logger.error("url %s is %s\terrors %s warnings %s (check at %s)" % (url, resp['x-w3c-validator-status'], resp['x-w3c-validator-errors'], resp['x-w3c-validator-warnings'], urlrequest))
+ if resp['x-w3c-validator-status'] != "Abort":
+ status = resp['x-w3c-validator-status']
+ errors = int(resp['x-w3c-validator-errors'])
+ warnings = int(resp['x-w3c-validator-warnings'])
except Exception as e:
config.logger.warn("Failed validation call: %s" % e.__str__())
-
- print("done %s" % url)
+ return (status, errors, warnings)
if __name__ == "__main__":
+ print("Testing %s with %s" % (config.TOASTER_BASEURL, config.W3C_VALIDATOR))
+
+ def print_validation(url):
+ status, errors, warnings = validate_html5(url)
+ config.logger.error("url %s is %s\terrors %s warnings %s (check at %s)" % (url, status, errors, warnings, config.W3C_VALIDATOR+url))
+
if len(sys.argv) > 1:
- validate_html(sys.argv[1])
+ print_validation(sys.argv[1])
else:
for url in urllist.URLS:
- validate_html(BASEURL+url)
+ print_validation(config.TOASTER_BASEURL+url)
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH 22/22] toaster: more HTML5 fixes
2015-05-28 14:14 [PATCH 00/22] Toaster patchset Alex DAMIAN
` (20 preceding siblings ...)
2015-05-28 14:14 ` [PATCH 21/22] toaster tests: enable url check test Alex DAMIAN
@ 2015-05-28 14:14 ` Alex DAMIAN
21 siblings, 0 replies; 23+ messages in thread
From: Alex DAMIAN @ 2015-05-28 14:14 UTC (permalink / raw)
To: bitbake-devel
From: Alexandru DAMIAN <alexandru.damian@intel.com>
More HTML5 fixes highlighted by tests on new data.
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
---
lib/toaster/toastergui/templates/builddashboard.html | 2 +-
lib/toaster/toastergui/templates/configvars.html | 2 +-
lib/toaster/toastergui/templates/recipe.html | 2 ++
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/lib/toaster/toastergui/templates/builddashboard.html b/lib/toaster/toastergui/templates/builddashboard.html
index d5fe92a..ad497e6 100644
--- a/lib/toaster/toastergui/templates/builddashboard.html
+++ b/lib/toaster/toastergui/templates/builddashboard.html
@@ -209,6 +209,7 @@
<a class="error" href="{% url "task" build.id exectask.0.id %}">
{{exectask.0.recipe.name}}
<span class="task-name">{{exectask.0.task_name}}</span>
+ </a>
{% if MANAGED and build.project %}
<a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}">
@@ -216,7 +217,6 @@
</a>
{% endif %}
- </a>
{% elif exectask.count > 1%}
<a class="error" href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}}</a>
{% endif %}
diff --git a/lib/toaster/toastergui/templates/configvars.html b/lib/toaster/toastergui/templates/configvars.html
index 42c42a5..cbe3c68 100644
--- a/lib/toaster/toastergui/templates/configvars.html
+++ b/lib/toaster/toastergui/templates/configvars.html
@@ -56,7 +56,7 @@
<td class="file"><a data-toggle="modal" href="#variable-{{variable.pk}}">
{% if variable.vhistory.all %}
{% for path in variable.vhistory.all|filter_setin_files:file_filter %}
- {{path|cut_path_prefix:dirstostrip}}<p>
+ {{path|cut_path_prefix:dirstostrip}}<br/>
{% endfor %}
{% endif %}
</a></td>
diff --git a/lib/toaster/toastergui/templates/recipe.html b/lib/toaster/toastergui/templates/recipe.html
index 2da41c3..dd8753d 100644
--- a/lib/toaster/toastergui/templates/recipe.html
+++ b/lib/toaster/toastergui/templates/recipe.html
@@ -87,6 +87,7 @@
{% else %}
<table class="table table-bordered table-hover">
<thead>
+ <tr>
<th>
<i class="icon-question-sign get-help" title="The running sequence of each task in the build"></i>
Order
@@ -107,6 +108,7 @@
<i class="icon-question-sign get-help" title="This column tells you if a task tried to restore output from the <code>sstate-cache</code> directory or mirrors, and reports the result: Succeeded, Failed or File not in cache"></i>
Cache attempt
</th>
+ </tr>
</thead>
<tbody>
--
1.9.1
^ permalink raw reply related [flat|nested] 23+ messages in thread