Skip to contentSkip to conversation

Toaster patchset

PATCH New Alex DAMIAN 1 participant 23 messages May 28, 2015

lore
Share & tools
lore
#0 lore raw

cover Toaster patchset

From: Alexandru DAMIAN <[email protected]>

Hi,

This is a Toaster patches with various fixes, especially in the testing area.

Can you please pull ?

Cheers,
Alex

The following changes since commit 702b42a47904f2378dd819e7463b3206883c2651:

data: Move warning code to the first loop for performance (2015-05-25 09:00:16 +0100)

are available in the git repository at:

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

Alexandru DAMIAN (12):
toaster: enable server-side sessions for all usages toaster logger: refactor recipe and layer file paths toaster logger: assign projects to all builds
toastergui: table header bring back
toaster: refactor checksettings command
toasterui: replace cookie-based preferences with session data
toastergui: tests for the all-projects API point
toastergui: add tests for xhr_datatypeahead calls
toaster: enable no browser start
bitbake/: toaster tts: fix tests
toaster tests: enable url check test
toaster: more HTML5 fixes

Ed Bartosh (3):
toastergui: show relative paths in configvars view
toaster-gui: Show task name for the builds in /builds
toastergui: Consider task name when restarting a build in /builds

Michael Wood (7):
toaster: Fix build button current project race
toaster: Fix toastertable header
toaster: Add a simplified ToasterTable template
toaster: Port layerdetails to ToasterTables
toaster: Add an invalidate cache method on data update signal
bldcontrol: Fix all failing unit tests
orm: Fix all failing unit test

 bin/toaster                                        |  12 +-
 lib/bb/ui/buildinfohelper.py                       |  84 ++-
 lib/toaster/bldcontrol/localhostbecontroller.py    |   2 +-
 .../management/commands/checksettings.py           |  26 +-
 lib/toaster/bldcontrol/models.py                   |   3 +
 lib/toaster/bldcontrol/sshbecontroller.py          |   5 +-
 lib/toaster/bldcontrol/tests.py                    |  84 ++-
 lib/toaster/contrib/tts/backlog.txt                |   2 -
 lib/toaster/contrib/tts/config.py                  |  21 +
 lib/toaster/contrib/tts/launcher.py                |   2 +-
 lib/toaster/contrib/tts/runner.py                  |   4 +-
 lib/toaster/contrib/tts/tests.py                   |  55 +-
 .../tts/toasteruitest/toaster_automation_test.py   |  14 +-
 lib/toaster/contrib/tts/urlcheck.py                |  41 +-
 ...ion_local_path__add_field_recipe_pathflags__.py | 361 +++++++++++++
 ...ect__chg_field_project_bitbake_version__chg_.py | 368 +++++++++++++
 lib/toaster/orm/models.py                          |  58 +-
 lib/toaster/orm/tests.py                           |  32 +-
 lib/toaster/orm/views.py                           |   2 +-
 lib/toaster/toastergui/static/js/base.js           |   5 +-
 lib/toaster/toastergui/static/js/layerdetails.js   |  70 ++-
 lib/toaster/toastergui/static/js/libtoaster.js     |   3 +
 lib/toaster/toastergui/static/js/table.js          |  79 +--
 lib/toaster/toastergui/tables.py                   |  82 ++-
 .../toastergui/templates/basetable_bottom.html     |   4 -
 .../toastergui/templates/basetable_top.html        |   9 +-
 lib/toaster/toastergui/templates/bpackage.html     |   4 +-
 lib/toaster/toastergui/templates/brtargets.html    |  20 +
 .../toastergui/templates/builddashboard.html       |   2 +-
 .../toastergui/templates/configuration.html        |   2 +-
 lib/toaster/toastergui/templates/configvars.html   |  10 +-
 .../templates/detail_pagination_bottom.html        |   5 -
 .../toastergui/templates/detail_sorted_header.html |   2 +-
 lib/toaster/toastergui/templates/layerdetails.html | 594 +++++++--------------
 .../toastergui/templates/managed_mrb_section.html  |  28 +-
 .../toastergui/templates/package_detail_base.html  |   2 +-
 lib/toaster/toastergui/templates/recipe.html       |   6 +-
 lib/toaster/toastergui/templates/recipes.html      |   4 +-
 lib/toaster/toastergui/templates/runagain.html     |   7 +
 lib/toaster/toastergui/templates/target.html       |   2 +-
 .../toastergui/templates/toastertable-filter.html  |  18 +
 .../toastergui/templates/toastertable-simple.html  |  94 ++++
 lib/toaster/toastergui/templates/toastertable.html |  53 +-
 lib/toaster/toastergui/templatetags/projecttags.py |  83 +--
 lib/toaster/toastergui/tests.py                    |  78 +++
 lib/toaster/toastergui/urls.py                     |   6 +-
 lib/toaster/toastergui/views.py                    | 133 ++---
 lib/toaster/toastergui/widgets.py                  |  23 +-
 lib/toaster/toastermain/settings.py                |  17 +-
 49 files changed, 1778 insertions(+), 843 deletions(-)
 delete mode 100644 lib/toaster/contrib/tts/backlog.txt
 create mode 100644 lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py
 create mode 100644 lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py
 create mode 100644 lib/toaster/toastergui/templates/brtargets.html
 create mode 100644 lib/toaster/toastergui/templates/runagain.html
 create mode 100644 lib/toaster/toastergui/templates/toastertable-filter.html
 create mode 100644 lib/toaster/toastergui/templates/toastertable-simple.html
 create mode 100644 lib/toaster/toastergui/tests.py
Signature · 1 line
1.9.1
#1 lore raw

PATCH 1/22 toaster: Fix build button current project race

From: Michael Wood <[email protected]>

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]

---
 lib/toaster/toastergui/static/js/base.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
lib/toaster/toastergui/static/js/base.js +3 -2
index c4c96c8..ccc23e0 100644
@@ -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)
Signature · 1 line
1.9.1
#2 lore raw

PATCH 2/22 toaster: enable server-side sessions for all usages

From: Alexandru DAMIAN <[email protected]>

In an effort to fix client-side session storage using cookies, we enable server-side session support for all pages.

---
 bin/toaster                         |  6 +++++-
 lib/toaster/toastergui/views.py     |  6 ++++--
 lib/toaster/toastermain/settings.py | 17 ++++++-----------
 3 files changed, 15 insertions(+), 14 deletions(-)
bin/toaster +5 -1
index 18eba8e..93abcfa 100755
@@ -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
lib/toaster/toastergui/views.py +4 -2
index 9217d8a..c16fc22 100755
@@ -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)
 
 
lib/toaster/toastermain/settings.py +6 -11
index 80f26e0..225138b 100644
@@ -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
Signature · 1 line
1.9.1
#3 lore raw

PATCH 3/22 toaster logger: refactor recipe and layer file paths

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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]

---
 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
lib/bb/ui/buildinfohelper.py +32 -15
index 647d663..6812a52 100644
@@ -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():
lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py +7 -7
index c03a937..0cb3934 100755
@@ -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"]:
lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py new file +361 -0

361 lines not on this page · raw message

lib/toaster/orm/models.py +7 -16
index 20b02bb..29ede65 100644
@@ -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/
lib/toaster/toastergui/templates/bpackage.html +2 -2
index 6fbdc7b..1c47354 100644
@@ -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>
lib/toaster/toastergui/templates/configuration.html +1 -1
index d3b34a2..2aa1ae1 100644
@@ -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>
lib/toaster/toastergui/templates/package_detail_base.html +1 -1
index ad75454..e2ec75d 100644
@@ -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 -->
lib/toaster/toastergui/templates/recipe.html +2 -2
index a0fe6d7..2da41c3 100644
@@ -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>
lib/toaster/toastergui/templates/recipes.html +2 -2
index dc2d9e8..8d4494e 100644
@@ -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>
 
lib/toaster/toastergui/templates/target.html +1 -1
index 1309b52..e7febaf 100644
@@ -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>
lib/toaster/toastergui/views.py +11 -11
index c16fc22..761bf53 100755
@@ -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)
 
Signature · 1 line
1.9.1
#4 lore raw

PATCH 4/22 toaster logger: assign projects to all builds

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 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
lib/bb/ui/buildinfohelper.py +27 -10
index 6812a52..3ea842c 100644
@@ -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 = {}
lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py new file +368 -0

369 lines not on this page · raw message

lib/toaster/orm/models.py +9 -6
index 29ede65..0f72576 100644
@@ -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)
lib/toaster/orm/views.py +1 -1
index 61d14f9..f32fa4d 100644
@@ -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/")
Signature · 1 line
1.9.1
#5 lore raw

PATCH 5/22 toastergui: table header bring back

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

This patch brings back the table header, which I mistakenly took out when fixing HTML5 compliance.

---
 lib/toaster/toastergui/static/js/table.js          | 2 +-
 lib/toaster/toastergui/templates/toastertable.html | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
lib/toaster/toastergui/static/js/table.js +1 -1
index 8f5bb2e..97370fd 100644
@@ -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("");
lib/toaster/toastergui/templates/toastertable.html +1 -1
index 598d295..21392bb 100644
@@ -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>
Signature · 1 line
1.9.1
#6 lore raw

PATCH 6/22 toastergui: show relative paths in configvars view

In reply to Alex DAMIAN

From: Ed Bartosh <[email protected]>

Reworked filtering of config paths.

Stripped clone paths, topdir and its parent directory from the paths to config files in configvars view.

[YOCTO #7463]

---
 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(-)
lib/toaster/toastergui/templates/configvars.html +6 -4
index 3e4c7e8..42c42a5 100644
@@ -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>
lib/toaster/toastergui/templatetags/projecttags.py +23 -53

92 lines not on this page · raw message

lib/toaster/toastergui/views.py +13 -6
index 761bf53..d451c3b 100755
@@ -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',
Signature · 1 line
1.9.1
#7 lore raw

PATCH 7/22 toaster: Fix toastertable header

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

Fixes regression introduced by 'tables updates for HTML5 compliance'

---
 lib/toaster/toastergui/templates/toastertable.html | 1 +
 1 file changed, 1 insertion(+)
lib/toaster/toastergui/templates/toastertable.html +1 -0
index 21392bb..4e6c4ce 100644
@@ -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>
Signature · 1 line
1.9.1
#8 lore raw

PATCH 8/22 toaster: Add a simplified ToasterTable template

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

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.

---
 .../toastergui/templates/toastertable-simple.html  | 112 +++++++++++++++++++++
 1 file changed, 112 insertions(+)
 create mode 100644 lib/toaster/toastergui/templates/toastertable-simple.html
lib/toaster/toastergui/templates/toastertable-simple.html new file +112 -0
new file mode 100644
index 0000000..98cad64
@@ -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">&nbsp;</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>
Signature · 1 line
1.9.1
#9 lore raw

PATCH 9/22 toaster: Port layerdetails to ToasterTables

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

This ports the layerdetails page to using ToasterTables Also some whitespace and strict clean ups in the existing layerdetails js and html template.

---
 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
lib/toaster/toastergui/static/js/layerdetails.js +52 -18
index 3c4d632..3746ea2 100644
@@ -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();
   }
 
lib/toaster/toastergui/static/js/libtoaster.js +3 -0
index 1ac26d4..99e1f03 100644
@@ -216,6 +216,9 @@ var libtoaster = (function (){
       str += "&";
     }
 
+    /* Maintain the current hash */
+    str += window.location.hash;
+
     return str;
   }
 
lib/toaster/toastergui/static/js/table.js +48 -29

148 lines not on this page · raw message

lib/toaster/toastergui/tables.py +78 -9

112 lines not on this page · raw message

lib/toaster/toastergui/templates/layerdetails.html +183 -411

633 lines not on this page · raw message

lib/toaster/toastergui/templates/toastertable-filter.html new file +18 -0
new file mode 100644
index 0000000..7c8dc49
@@ -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}}">&nbsp;</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 -->
lib/toaster/toastergui/templates/toastertable-simple.html +4 -22
index 98cad64..23a7526 100644
@@ -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">&nbsp;</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">
lib/toaster/toastergui/templates/toastertable.html +18 -32

75 lines not on this page · raw message

lib/toaster/toastergui/urls.py +4 -2
index d0c176b..7a1132f 100644
@@ -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'),
lib/toaster/toastergui/views.py +0 -49

61 lines not on this page · raw message

lib/toaster/toastergui/widgets.py +7 -10
index 8d44919..b5dfbbc 100644
@@ -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:
Signature · 1 line
1.9.1
#10 lore raw

PATCH 10/22 toaster: Add an invalidate cache method on data update signal

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

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.

---
 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(-)
lib/toaster/orm/models.py +12 -0
index 0f72576..fb62a55 100644
@@ -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)
lib/toaster/toastergui/tables.py +0 -5
index 78a7cb0..9a93ff9 100644
@@ -323,11 +323,6 @@ class LayerRecipesTable(RecipesTable):
 
 
 
-
-
-
-
-
 # This needs to be staticaly defined here as django reads the url patterns
 # on start up
 urlpatterns = (
lib/toaster/toastergui/widgets.py +4 -2
index b5dfbbc..8cf6e1b 100644
@@ -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
Signature · 1 line
1.9.1
#11 lore raw

PATCH 11/22 toaster-gui: Show task name for the builds in /builds

In reply to Alex DAMIAN

From: Ed Bartosh <[email protected]>

Task name was only shown for the latest builds in project view. Now it should be shown also in builds view.

---
 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
lib/toaster/toastergui/templates/brtargets.html new file +20 -0
new file mode 100644
index 0000000..4ebd058
@@ -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>
lib/toaster/toastergui/templates/managed_mrb_section.html +5 -7
index a6d4ac6..abcc0bb 100644
@@ -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
Signature · 1 line
1.9.1
#12 lore raw

PATCH 12/22 toastergui: Consider task name when restarting a build in /builds

In reply to Alex DAMIAN

From: Ed Bartosh <[email protected]>

Previously the same issue was fixed for project view.
'Run again' button now restarts builds using target:task also in builds view.

[YOCTO #7442]

---
 .../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
lib/toaster/toastergui/templates/managed_mrb_section.html +14 -2
index abcc0bb..c93c2af 100644
@@ -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>
 
 
lib/toaster/toastergui/templates/runagain.html new file +7 -0
new file mode 100644
index 0000000..a78c0dd
@@ -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
lib/toaster/toastergui/templatetags/projecttags.py +4 -3
index 1bc3bc2..8028ae0 100644
@@ -44,9 +44,10 @@ def sectohms(time):
     return "%02d:%02d:%02d" % (hours, int((tdsec - (hours * 3600))/ 60), int(tdsec) % 60)
 
 
[email protected](name = 'mapselect')
-def mapselect(value, argument):
-    return map(lambda x: vars(x)[argument], value)
[email protected](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")
Signature · 1 line
1.9.1
#13 lore raw

PATCH 13/22 toaster: refactor checksettings command

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 .../management/commands/checksettings.py           | 26 ++++++++++++++++++++--
 lib/toaster/bldcontrol/models.py                   |  3 +++
 lib/toaster/orm/models.py                          |  3 +++
 3 files changed, 30 insertions(+), 2 deletions(-)
lib/toaster/bldcontrol/management/commands/checksettings.py +24 -2
index 1ff5c92..3c52446 100644
@@ -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
lib/toaster/bldcontrol/models.py +3 -0
index 02cfaf7..b789446 100644
@@ -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*
 
lib/toaster/orm/models.py +3 -0
index fb62a55..8a9a21e 100644
@@ -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):
Signature · 1 line
1.9.1
#14 lore raw

PATCH 14/22 toasterui: replace cookie-based preferences with session data

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 .../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(-)
lib/toaster/toastergui/templates/basetable_bottom.html +0 -4
index d48ad92..4c28cae 100644
@@ -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()});
     });
 });
lib/toaster/toastergui/templates/basetable_top.html +1 -8
index 92a3b50..fcd2f03 100644
@@ -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>
lib/toaster/toastergui/templates/detail_pagination_bottom.html +0 -5
index 355ae90..434facb 100644
@@ -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()});
     });
 });
lib/toaster/toastergui/templates/detail_sorted_header.html +1 -1
index a7917dc..5214444 100644
@@ -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>
lib/toaster/toastergui/views.py +18 -19
index f70cf20..0e248a7 100755
@@ -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
 
 
Signature · 1 line
1.9.1
#15 lore raw

PATCH 15/22 toastergui: tests for the all-projects API point

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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"

---
 lib/toaster/toastergui/tests.py | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 lib/toaster/toastergui/tests.py
lib/toaster/toastergui/tests.py new file +36 -0
new file mode 100644
index 0000000..8a78a41
@@ -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])
Signature · 1 line
1.9.1
#16 lore raw

PATCH 16/22 toastergui: add tests for xhr_datatypeahead calls

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 lib/toaster/toastergui/tests.py | 50 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 46 insertions(+), 4 deletions(-)
lib/toaster/toastergui/tests.py +46 -4
index 8a78a41..0f10020 100644
@@ -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"]))
Signature · 1 line
1.9.1
#17 lore raw

PATCH 17/22 bldcontrol: Fix all failing unit tests

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

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.

---
 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(-)
lib/toaster/bldcontrol/localhostbecontroller.py +1 -1
index dfe06f9..bc3566a 100644
@@ -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)
lib/toaster/bldcontrol/sshbecontroller.py +4 -1
index 11ad08d..29ed0a7 100644
@@ -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()
lib/toaster/bldcontrol/tests.py +56 -28

154 lines not on this page · raw message

Signature · 1 line
1.9.1
#18 lore raw

PATCH 18/22 orm: Fix all failing unit test

In reply to Alex DAMIAN

From: Michael Wood <[email protected]>

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

---
 lib/toaster/orm/models.py |  5 ++++-
 lib/toaster/orm/tests.py  | 32 ++++++++++++++++++++------------
 2 files changed, 24 insertions(+), 13 deletions(-)
lib/toaster/orm/models.py +4 -1
index 8a9a21e..198c6f5 100644
@@ -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']
lib/toaster/orm/tests.py +20 -12
index 7b1b963..d4d97ee 100644
@@ -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)
Signature · 1 line
1.9.1
#19 lore raw

PATCH 19/22 toaster: enable no browser start

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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.

---
 bin/toaster | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
bin/toaster +5 -1
index 93abcfa..ee19937 100755
@@ -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
Signature · 1 line
1.9.1
#20 lore raw

PATCH 20/22 bitbake/: toaster tts: fix tests

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

---
 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
lib/toaster/contrib/tts/backlog.txt deleted +0 -2
deleted file mode 100644
index 6f92f7a..0000000
@@ -1,2 +0,0 @@
-michaelw/toaster-frontend-cleanups|PENDING
-adamian/test_0|PENDING
lib/toaster/contrib/tts/launcher.py +1 -1
index 5b63bc1..90c6d95 100755
@@ -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)
lib/toaster/contrib/tts/runner.py +1 -1
index d2a6099..f55c493 100755
@@ -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__":
lib/toaster/contrib/tts/tests.py +1 -1
index 3d4cfea..2e630db 100644
@@ -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))
 
Signature · 1 line
1.9.1
#21 lore raw

PATCH 21/22 toaster tests: enable url check test

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

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

---
 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(-)
lib/toaster/contrib/tts/config.py +21 -0
index a4ea8cf..c0e0536 100644
@@ -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 <[email protected]>"
 REPORT_EMAIL_ADDRESS = "[email protected]"
 
lib/toaster/contrib/tts/runner.py +1 -1
index f55c493..e7e4dd2 100755
@@ -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
lib/toaster/contrib/tts/tests.py +53 -2

73 lines not on this page · raw message

lib/toaster/contrib/tts/urlcheck.py +19 -22

57 lines not on this page · raw message

Signature · 1 line
1.9.1
#22 lore raw

PATCH 22/22 toaster: more HTML5 fixes

In reply to Alex DAMIAN

From: Alexandru DAMIAN <[email protected]>

More HTML5 fixes highlighted by tests on new data.

---
 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(-)
lib/toaster/toastergui/templates/builddashboard.html +1 -1
index d5fe92a..ad497e6 100644
@@ -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 %}
lib/toaster/toastergui/templates/configvars.html +1 -1
index 42c42a5..cbe3c68 100644
@@ -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>
lib/toaster/toastergui/templates/recipe.html +2 -0
index 2da41c3..dd8753d 100644
@@ -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>
 
Signature · 1 line
1.9.1