All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/34] michaelw/toaster/ic-6
@ 2015-12-08 21:15 Michael Wood
  2015-12-08 21:15 ` [PATCH 01/34] toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe Michael Wood
                   ` (34 more replies)
  0 siblings, 35 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

This is the work to introduce the image customisation feature to Toaster.
As a version 1 this allows basic adding and removing packages of a customised
version of a pre-existing image recipe. To enable this feature run toaster
with the environment var set CUSTOM_IMAGE=1

http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/log/?h=michaelw/toaster/ic-6

Michael Wood (34):
  toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe
  toaster: models fall back to a sensible string for no vcs reference
  toaster: ToasterTables simplify filter function move common part to
    widget
  toaster: tablejs Add an event handler to manually trigger a data
    reload
  toaster: orm Add sum of dependencies size function to
    PackageDependencyManager
  toaster: orm make CustomImageRecipe inherit from Recipe
  toaster: orm: Add db migration for new CustomImageRecipe inheritance
    change
  toaster: orm Add ProjectPackage table
  toaster: orm: Add db migration for new  ProjectPackages table
  toaster: buildinfohelper Add the concept of ProjectPackages
  toaster: orm add CustomImageRecipe generate contents function
  toaster: move CustomImageRecipe generation to API entry point
  toaster: views Add view to download custom recipe
  toaster: tables Add table for Packages and update SelectPackagesTable
  toaster: Continue front end features to custom image recipe page.
  toaster: newcustomimage Move modal dialog out of newcustomimage
    template
  toaster: Add recipe details page
  toaster: toastertable remove title from Show all in table
  toaster: views xhr_customrecipe_packages clean up API
  toaster: toastergui tests Update to reflect changes to
    CustomImageRecipe
  toaster: toastergui tests Add unit test for download custom recipe
  toaster: orm get_project_layer_versions to return layer_version
    objects
  toaster: orm Add convenience method to get all pkgs in a
    CustomImageRecipe
  toaster: libtoaster Add createCustomRecipe method
  toaster: newcustomimage_modal use libtoaster method for new
    CustomRecipe
  toaster: tables add recipe download link to CustomImagesTable
  toaster: tables Change SelectPackagesTable to use ProjectPackage
  toaster: API allow CustomImageRecipe to be updated after creation
  toaster: xhr_customrecipe_id  change to use ProjectPackage
  toaster: xhr_customrecipe_packages add GET info for package response
  toaster: customrecipe Add further front end features using new API
  toaster: toastergui tests Update package test to use ProjectPackage
  toaster: tables SelectPackagesTable rename recipe_id to custrecipeid
  toaster: toastergui tests Add addtional data to the setUp for new
    tables

 bitbake/lib/bb/ui/buildinfohelper.py               | 113 +++++-
 .../toaster/bldcontrol/localhostbecontroller.py    |  29 +-
 ...del_field_customimagerecipe_name__del_field_.py | 433 +++++++++++++++++++++
 ...erecipe_name__del_field_customimagerecipe_id.py | 379 ++++++++++++++++++
 bitbake/lib/toaster/orm/models.py                  |  95 ++++-
 .../toaster/toastergui/static/js/customrecipe.js   | 176 ++++++++-
 .../lib/toaster/toastergui/static/js/libtoaster.js |  27 ++
 .../toaster/toastergui/static/js/newcustomimage.js |  49 ---
 .../toastergui/static/js/newcustomimage_modal.js   |  28 ++
 .../toaster/toastergui/static/js/recipedetails.js  |  52 +++
 bitbake/lib/toaster/toastergui/static/js/table.js  |   7 +
 bitbake/lib/toaster/toastergui/tables.py           | 239 +++++++++---
 bitbake/lib/toaster/toastergui/templates/base.html |   1 +
 .../toaster/toastergui/templates/customrecipe.html | 186 ++++++---
 .../toastergui/templates/newcustomimage.html       |  44 +--
 .../toastergui/templates/newcustomimage_modal.html |  33 ++
 .../toastergui/templates/pkg_add_rm_btn.html       |  35 +-
 .../toastergui/templates/recipedetails.html        | 180 +++++++++
 .../snippets/pkg_dependencies_popover.html         |  14 +
 .../toaster/toastergui/templates/toastertable.html |   2 +-
 bitbake/lib/toaster/toastergui/tests.py            | 145 +++++--
 bitbake/lib/toaster/toastergui/urls.py             |  17 +-
 bitbake/lib/toaster/toastergui/views.py            | 262 ++++++++++---
 bitbake/lib/toaster/toastergui/widgets.py          |   5 +-
 24 files changed, 2171 insertions(+), 380 deletions(-)
 create mode 100644 bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_field_.py
 create mode 100644 bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id.py
 delete mode 100644 bitbake/lib/toaster/toastergui/static/js/newcustomimage.js
 create mode 100644 bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
 create mode 100644 bitbake/lib/toaster/toastergui/static/js/recipedetails.js
 create mode 100644 bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html
 create mode 100644 bitbake/lib/toaster/toastergui/templates/recipedetails.html
 create mode 100644 bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html

-- 
2.1.4



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

* [PATCH 01/34] toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 02/34] toaster: models fall back to a sensible string for no vcs reference Michael Wood
                   ` (33 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Update the reference to the base_recipe. It is now a Recipe object
rather than an intermediate AvailableRecipe object.
Therefore doesn't need an extra traverse down the object hierarchy.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/bldcontrol/localhostbecontroller.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
index 92d9ac5..81b773e 100644
--- a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
+++ b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
@@ -243,7 +243,7 @@ class LocalhostBEController(BuildEnvironmentController):
             # create recipe
             recipe = os.path.join(layerpath, "recipes", "%s.bb" % target.target)
             with open(recipe, "w") as recipef:
-                recipef.write("require %s\n" % customrecipe.base_recipe.recipe.file_path)
+                recipef.write("require %s\n" % customrecipe.base_recipe.file_path)
                 packages = [pkg.name for pkg in customrecipe.packages.all()]
                 if packages:
                     recipef.write('IMAGE_INSTALL = "%s"\n' % ' '.join(packages))
-- 
2.1.4



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

* [PATCH 02/34] toaster: models fall back to a sensible string for no vcs reference
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
  2015-12-08 21:15 ` [PATCH 01/34] toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 03/34] toaster: ToasterTables simplify filter function move common part to widget Michael Wood
                   ` (32 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Fall back to a 'n/a' string for the vcs reference, not all our source
has to be in a vcs and therefore it is legitimate for this to be none.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/orm/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 0174233..a4b0557 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -1186,7 +1186,7 @@ class Layer_Version(models.Model):
             return self.up_branch.name
         if self.commit is not None and len(self.commit) > 0:
             return self.commit
-        return ("Cannot determine the vcs_reference for layer version %s" % vars(self))
+        return 'N/A'
 
     def get_detailspage_url(self, project_id):
         return reverse('layerdetails', args=(project_id, self.pk))
-- 
2.1.4



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

* [PATCH 03/34] toaster: ToasterTables simplify filter function move common part to widget
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
  2015-12-08 21:15 ` [PATCH 01/34] toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe Michael Wood
  2015-12-08 21:15 ` [PATCH 02/34] toaster: models fall back to a sensible string for no vcs reference Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 04/34] toaster: tablejs Add an event handler to manually trigger a data reload Michael Wood
                   ` (31 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Move part of the functionality of the filter functions to the Table
widget. We don't need to implement it in each subclass.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tables.py  | 61 +++++++++++++++++++------------
 bitbake/lib/toaster/toastergui/widgets.py |  5 ++-
 2 files changed, 40 insertions(+), 26 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 74af507..6bbf0d7 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -31,19 +31,11 @@ from django.views.generic import TemplateView
 class ProjectFiltersMixin(object):
     """Common mixin for recipe, machine in project filters"""
 
-    def filter_in_project(self, count_only=False):
-        query = self.queryset.filter(layer_version__in=self.project_layers)
-        if count_only:
-            return query.count()
-
-        self.queryset = query
+    def filter_in_project(self):
+        return self.queryset.filter(layer_version__in=self.project_layers)
 
-    def filter_not_in_project(self, count_only=False):
-        query = self.queryset.exclude(layer_version__in=self.project_layers)
-        if count_only:
-            return query.count()
-
-        self.queryset = query
+    def filter_not_in_project(self):
+        return self.queryset.exclude(layer_version__in=self.project_layers)
 
 class LayersTable(ToasterTable):
     """Table of layers in Toaster"""
@@ -75,19 +67,10 @@ class LayersTable(ToasterTable):
                         ])
 
     def filter_in_project(self, count_only=False):
-        query = self.queryset.filter(projectlayer__in=self.project_layers)
-        if count_only:
-            return query.count()
-
-        self.queryset = query
+        return self.queryset.filter(projectlayer__in=self.project_layers)
 
     def filter_not_in_project(self, count_only=False):
-        query = self.queryset.exclude(projectlayer__in=self.project_layers)
-        if count_only:
-            return query.count()
-
-        self.queryset = query
-
+        return self.queryset.exclude(projectlayer__in=self.project_layers)
 
     def setup_queryset(self, *args, **kwargs):
         prj = Project.objects.get(pk = kwargs['pid'])
@@ -631,4 +614,34 @@ class SelectPackagesTable(ToasterTable):
                         help_text="Use the add and remove buttons to modify "
                         "the package content of you custom image",
                         static_data_name="add_rm_pkg_btn",
-                        static_data_template='{% include "pkg_add_rm_btn.html" %}')
+                        static_data_template='{% include "pkg_add_rm_btn.html" %}',
+                        filter_name="in_current_image"
+                        )
+
+    def setup_filters(self, *args, **kwargs):
+        project = Project.objects.get(pk=kwargs['pid'])
+        self.project_layers = ProjectLayer.objects.filter(project=project)
+
+
+        self.add_filter(title="Filter by added packages",
+                        name="in_current_image",
+                        filter_actions=[
+                            self.make_filter_action(
+                                "in_image",
+                                "Packages in %s" % self.cust_recipe.name,
+                                self.filter_in_image),
+                            self.make_filter_action(
+                                "not_in_image",
+                                "Packages not added to %s" %
+                                self.cust_recipe.name,
+                                self.filter_not_in_image)
+                        ])
+
+    def filter_in_image(self, count_only=False):
+        return self.queryset.filter(
+            pk__in=self.static_context_extra['current_packages'])
+
+
+    def filter_not_in_image(self, count_only=False):
+        return self.queryset.exclude(
+            pk__in=self.static_context_extra['current_packages'])
diff --git a/bitbake/lib/toaster/toastergui/widgets.py b/bitbake/lib/toaster/toastergui/widgets.py
index 6bb3889..aeaa26a 100644
--- a/bitbake/lib/toaster/toastergui/widgets.py
+++ b/bitbake/lib/toaster/toastergui/widgets.py
@@ -113,7 +113,8 @@ class ToasterTable(TemplateView):
                               cls=DjangoJSONEncoder)
         else:
             for actions in self.filters[name]['filter_actions']:
-                actions['count'] = self.filter_actions[actions['name']](count_only=True)
+                actions['count'] = \
+                        self.filter_actions[actions['name']]().count()
 
             # Add the "All" items filter action
             self.filters[name]['filter_actions'].insert(0, {
@@ -222,7 +223,7 @@ class ToasterTable(TemplateView):
             return
 
         try:
-            self.filter_actions[filter_action]()
+            self.queryset = self.filter_actions[filter_action]()
         except KeyError:
             # pass it to the user - programming error here
             raise
-- 
2.1.4



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

* [PATCH 04/34] toaster: tablejs Add an event handler to manually trigger a data reload
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (2 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 03/34] toaster: ToasterTables simplify filter function move common part to widget Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 05/34] toaster: orm Add sum of dependencies size function to PackageDependencyManager Michael Wood
                   ` (30 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Allow users of ToasterTable to manually trigger a refresh of the data.
This can be useful if an action has happened in-page and the data is now
invalid. Such as new data being added or removed from the model.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/static/js/table.js | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/bitbake/lib/toaster/toastergui/static/js/table.js b/bitbake/lib/toaster/toastergui/static/js/table.js
index c69c205..f587788 100644
--- a/bitbake/lib/toaster/toastergui/static/js/table.js
+++ b/bitbake/lib/toaster/toastergui/static/js/table.js
@@ -454,6 +454,13 @@ function tableInit(ctx){
     });
   }
 
+  /* Allow pages to trigger reload event */
+  table.on('reload', function(e, newTableParams){
+    if (newTableParams)
+      loadData(newTableParams);
+    else
+      loadData(tableParams)
+  });
 
   $(".get-help").tooltip({container:'body', html:true, delay:{show:300}});
 
-- 
2.1.4



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

* [PATCH 05/34] toaster: orm Add sum of dependencies size function to PackageDependencyManager
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (3 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 04/34] toaster: tablejs Add an event handler to manually trigger a data reload Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 06/34] toaster: orm make CustomImageRecipe inherit from Recipe Michael Wood
                   ` (29 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Add function that returns the Sum of the size of all the packages which depend on a package. Access get_total_source_deps_size via a packages's dependency
manager.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/orm/models.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index a4b0557..10f1cce 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -20,7 +20,7 @@
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
 from django.db import models, IntegrityError
-from django.db.models import F, Q, Avg, Max
+from django.db.models import F, Q, Avg, Max, Sum
 from django.utils import timezone
 
 from django.core.urlresolvers import reverse
@@ -594,6 +594,12 @@ class Package_DependencyManager(models.Manager):
     def get_query_set(self):
         return super(Package_DependencyManager, self).get_query_set().exclude(package_id = F('depends_on__id'))
 
+    def get_total_source_deps_size(self):
+        """ Returns the total file size of all the packages that depend on
+        thispackage.
+        """
+        return self.all().aggregate(Sum('depends_on__size'))
+
 class Package_Dependency(models.Model):
     TYPE_RDEPENDS = 0
     TYPE_TRDEPENDS = 1
-- 
2.1.4



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

* [PATCH 06/34] toaster: orm make CustomImageRecipe inherit from Recipe
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (4 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 05/34] toaster: orm Add sum of dependencies size function to PackageDependencyManager Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 07/34] toaster: orm: Add db migration for new CustomImageRecipe inheritance change Michael Wood
                   ` (28 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

This allows us to re-use the properties of a recipe for the custom image
recipes as well as re-using the existing templates and logic that deals
with recipe objects.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/orm/models.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 10f1cce..debe6fa 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -1243,16 +1243,11 @@ class ProjectLayer(models.Model):
     class Meta:
         unique_together = (("project", "layercommit"),)
 
-class CustomImageRecipe(models.Model):
+class CustomImageRecipe(Recipe):
     search_allowed_fields = ['name']
-    name = models.CharField(max_length=100)
-    base_recipe = models.ForeignKey(Recipe)
-    packages = models.ManyToManyField(Package)
+    base_recipe = models.ForeignKey(Recipe, related_name='based_on_recipe')
     project = models.ForeignKey(Project)
 
-    class Meta:
-        unique_together = ("name", "project")
-
 class ProjectVariable(models.Model):
     project = models.ForeignKey(Project)
     name = models.CharField(max_length=100)
-- 
2.1.4



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

* [PATCH 07/34] toaster: orm: Add db migration for new CustomImageRecipe inheritance change
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (5 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 06/34] toaster: orm make CustomImageRecipe inherit from Recipe Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 08/34] toaster: orm Add ProjectPackage table Michael Wood
                   ` (27 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 ...erecipe_name__del_field_customimagerecipe_id.py | 379 +++++++++++++++++++++
 1 file changed, 379 insertions(+)
 create mode 100644 bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id.py

diff --git a/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id.py b/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id.py
new file mode 100644
index 0000000..ba7cdbe
--- /dev/null
+++ b/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id.py
@@ -0,0 +1,379 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Removing unique constraint on 'CustomImageRecipe', fields ['name', 'project']
+        db.delete_unique(u'orm_customimagerecipe', ['name', 'project_id'])
+
+        # Deleting field 'CustomImageRecipe.name'
+        db.delete_column(u'orm_customimagerecipe', 'name')
+
+        # Deleting field 'CustomImageRecipe.id'
+        db.delete_column(u'orm_customimagerecipe', u'id')
+
+        # Adding field 'CustomImageRecipe.recipe_ptr'
+        db.add_column(u'orm_customimagerecipe', u'recipe_ptr',
+                      self.gf('django.db.models.fields.related.OneToOneField')(default=0, to=orm['orm.Recipe'], unique=True, primary_key=True),
+                      keep_default=False)
+
+        # Removing M2M table for field packages on 'CustomImageRecipe'
+        db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
+
+
+    def backwards(self, orm):
+        # Adding field 'CustomImageRecipe.name'
+        db.add_column(u'orm_customimagerecipe', 'name',
+                      self.gf('django.db.models.fields.CharField')(default='', max_length=100),
+                      keep_default=False)
+
+        # Adding field 'CustomImageRecipe.id'
+        db.add_column(u'orm_customimagerecipe', u'id',
+                      self.gf('django.db.models.fields.AutoField')(default=0, primary_key=True),
+                      keep_default=False)
+
+        # Deleting field 'CustomImageRecipe.recipe_ptr'
+        db.delete_column(u'orm_customimagerecipe', u'recipe_ptr_id')
+
+        # Adding M2M table for field packages on 'CustomImageRecipe'
+        m2m_table_name = db.shorten_name(u'orm_customimagerecipe_packages')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False)),
+            ('package', models.ForeignKey(orm[u'orm.package'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['customimagerecipe_id', 'package_id'])
+
+        # Adding unique constraint on 'CustomImageRecipe', fields ['name', 'project']
+        db.create_unique(u'orm_customimagerecipe', ['name', 'project_id'])
+
+
+    models = {
+        u'orm.bitbakeversion': {
+            'Meta': {'object_name': 'BitbakeVersion'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'giturl': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.branch': {
+            'Meta': {'unique_together': "(('layer_source', 'name'), ('layer_source', 'up_id'))", 'object_name': 'Branch'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'True', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.build': {
+            'Meta': {'object_name': 'Build'},
+            'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
+            'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+            'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'started_on': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        u'orm.buildartifact': {
+            'Meta': {'object_name': 'BuildArtifact'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        u'orm.customimagerecipe': {
+            'Meta': {'object_name': 'CustomImageRecipe', '_ormbases': [u'orm.Recipe']},
+            'base_recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'based_on_recipe'", 'to': u"orm['orm.Recipe']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            u'recipe_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['orm.Recipe']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        u'orm.helptext': {
+            'Meta': {'object_name': 'HelpText'},
+            'area': ('django.db.models.fields.IntegerField', [], {}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'helptext_build'", 'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        u'orm.layer': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'), ('layer_source', 'name'))", 'object_name': 'Layer'},
+            'description': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_index_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'summary': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'vcs_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_file_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_tree_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'})
+        },
+        u'orm.layer_version': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Layer_Version'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'layer_version_build'", 'null': 'True', 'to': u"orm['orm.Build']"}),
+            'commit': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'layer_version_layer'", 'to': u"orm['orm.Layer']"}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'local_path': ('django.db.models.fields.FilePathField', [], {'default': "'/'", 'max_length': '1024'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Project']", 'null': 'True'}),
+            'up_branch': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Branch']", 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.layersource': {
+            'Meta': {'unique_together': "(('sourcetype', 'apiurl'),)", 'object_name': 'LayerSource'},
+            'apiurl': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '63'}),
+            'sourcetype': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.layerversiondependency': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'LayerVersionDependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependees'", 'to': u"orm['orm.Layer_Version']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependencies'", 'to': u"orm['orm.Layer_Version']"}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.logmessage': {
+            'Meta': {'object_name': 'LogMessage'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'lineno': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'pathname': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Task']", 'null': 'True', 'blank': 'True'})
+        },
+        u'orm.machine': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Machine'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.package': {
+            'Meta': {'object_name': 'Package'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']", 'null': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'installed_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'installed_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Recipe']", 'null': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.package_dependency': {
+            'Meta': {'object_name': 'Package_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_target'", 'to': u"orm['orm.Package']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_source'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']", 'null': 'True'})
+        },
+        u'orm.package_file': {
+            'Meta': {'object_name': 'Package_File'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildfilelist_package'", 'to': u"orm['orm.Package']"}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.project': {
+            'Meta': {'object_name': 'Project'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']", 'null': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']", 'null': 'True'}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+        },
+        u'orm.projectlayer': {
+            'Meta': {'unique_together': "(('project', 'layercommit'),)", 'object_name': 'ProjectLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layercommit': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']", 'null': 'True'}),
+            'optional': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"})
+        },
+        u'orm.projecttarget': {
+            'Meta': {'object_name': 'ProjectTarget'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+        },
+        u'orm.projectvariable': {
+            'Meta': {'object_name': 'ProjectVariable'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.recipe': {
+            'Meta': {'unique_together': "(('layer_version', 'file_path', 'pathflags'),)", 'object_name': 'Recipe'},
+            'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'file_path': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'recipe_layer_version'", 'to': u"orm['orm.Layer_Version']"}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'pathflags': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.recipe_dependency': {
+            'Meta': {'object_name': 'Recipe_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_depends'", 'to': u"orm['orm.Recipe']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_recipe'", 'to': u"orm['orm.Recipe']"})
+        },
+        u'orm.release': {
+            'Meta': {'object_name': 'Release'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
+            'branch_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}),
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'helptext': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.releasedefaultlayer': {
+            'Meta': {'object_name': 'ReleaseDefaultLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.releaselayersourcepriority': {
+            'Meta': {'unique_together': "(('release', 'layer_source'),)", 'object_name': 'ReleaseLayerSourcePriority'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.LayerSource']"}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.target': {
+            'Meta': {'object_name': 'Target'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'license_manifest_path': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+        },
+        u'orm.target_file': {
+            'Meta': {'object_name': 'Target_File'},
+            'directory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'directory_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inodetype': ('django.db.models.fields.IntegerField', [], {}),
+            'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+            'size': ('django.db.models.fields.IntegerField', [], {}),
+            'sym_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'symlink_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_image_file': {
+            'Meta': {'object_name': 'Target_Image_File'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '254'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_installed_package': {
+            'Meta': {'object_name': 'Target_Installed_Package'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildtargetlist_package'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.task': {
+            'Meta': {'ordering': "('order', 'recipe')", 'unique_together': "(('build', 'recipe', 'task_name'),)", 'object_name': 'Task'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_build'", 'to': u"orm['orm.Build']"}),
+            'cpu_usage': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', 'decimal_places': '2'}),
+            'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', 'decimal_places': '2'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'logfile': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
+            'path_to_sstate_obj': ('django.db.models.fields.FilePathField', [], {'max_length': '500', 'blank': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'to': u"orm['orm.Recipe']"}),
+            'script_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'source_url': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'sstate_checksum': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'sstate_result': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'task_executed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'task_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'work_directory': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        u'orm.task_dependency': {
+            'Meta': {'object_name': 'Task_Dependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_depends'", 'to': u"orm['orm.Task']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_task'", 'to': u"orm['orm.Task']"})
+        },
+        u'orm.toastersetting': {
+            'Meta': {'object_name': 'ToasterSetting'},
+            'helptext': ('django.db.models.fields.TextField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '63'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        u'orm.variable': {
+            'Meta': {'object_name': 'Variable'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variable_build'", 'to': u"orm['orm.Build']"}),
+            'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'human_readable_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'variable_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'variable_value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.variablehistory': {
+            'Meta': {'object_name': 'VariableHistory'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'operation': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'variable': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vhistory'", 'to': u"orm['orm.Variable']"})
+        }
+    }
+
+    complete_apps = ['orm']
\ No newline at end of file
-- 
2.1.4



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

* [PATCH 08/34] toaster: orm Add ProjectPackage table
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (6 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 07/34] toaster: orm: Add db migration for new CustomImageRecipe inheritance change Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 09/34] toaster: orm: Add db migration for new ProjectPackages table Michael Wood
                   ` (26 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

This table is used to track all the available packages in the current
project scope. Many of these packages belong to many CustomImageRecipes.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/orm/models.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index debe6fa..2cbbb85 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -588,6 +588,22 @@ class Package(models.Model):
     section = models.CharField(max_length=80, blank=True)
     license = models.CharField(max_length=80, blank=True)
 
+class ProjectPackage(Package):
+    project = models.ForeignKey('Project')
+    # CustomImageRecipe fields to track pacakges appended,
+    # included and excluded from a CustomImageRecipe
+    recipe_includes = models.ManyToManyField('CustomImageRecipe',
+                                             related_name='includes_set',
+                                             null=True)
+    recipe_excludes = models.ManyToManyField('CustomImageRecipe',
+                                             related_name='excludes_set',
+                                             null=True)
+    recipe_appends = models.ManyToManyField('CustomImageRecipe',
+                                            related_name='appends_set',
+                                            null=True)
+
+
+
 class Package_DependencyManager(models.Manager):
     use_for_related_fields = True
 
-- 
2.1.4



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

* [PATCH 09/34] toaster: orm: Add db migration for new ProjectPackages table
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (7 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 08/34] toaster: orm Add ProjectPackage table Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-09 13:47   ` [PATCH v2] " Michael Wood
  2015-12-08 21:15 ` [PATCH 10/34] toaster: buildinfohelper Add the concept of ProjectPackages Michael Wood
                   ` (25 subsequent siblings)
  34 siblings, 1 reply; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 ...del_field_customimagerecipe_name__del_field_.py | 433 +++++++++++++++++++++
 1 file changed, 433 insertions(+)
 create mode 100644 bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_field_.py

diff --git a/bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_field_.py b/bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_field_.py
new file mode 100644
index 0000000..bb9d71c
--- /dev/null
+++ b/bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_field_.py
@@ -0,0 +1,433 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Removing unique constraint on 'CustomImageRecipe', fields ['name', 'project']
+        db.delete_unique(u'orm_customimagerecipe', ['name', 'project_id'])
+
+        # Adding model 'ProjectPackage'
+        db.create_table(u'orm_projectpackage', (
+            (u'package_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['orm.Package'], unique=True, primary_key=True)),
+            ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.Project'])),
+        ))
+        db.send_create_signal(u'orm', ['ProjectPackage'])
+
+        # Adding M2M table for field recipe_includes on 'ProjectPackage'
+        m2m_table_name = db.shorten_name(u'orm_projectpackage_recipe_includes')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('projectpackage', models.ForeignKey(orm[u'orm.projectpackage'], null=False)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['projectpackage_id', 'customimagerecipe_id'])
+
+        # Adding M2M table for field recipe_excludes on 'ProjectPackage'
+        m2m_table_name = db.shorten_name(u'orm_projectpackage_recipe_excludes')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('projectpackage', models.ForeignKey(orm[u'orm.projectpackage'], null=False)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['projectpackage_id', 'customimagerecipe_id'])
+
+        # Adding M2M table for field recipe_appends on 'ProjectPackage'
+        m2m_table_name = db.shorten_name(u'orm_projectpackage_recipe_appends')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('projectpackage', models.ForeignKey(orm[u'orm.projectpackage'], null=False)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['projectpackage_id', 'customimagerecipe_id'])
+
+        # Deleting field 'CustomImageRecipe.name'
+        db.delete_column(u'orm_customimagerecipe', 'name')
+
+        # Deleting field 'CustomImageRecipe.id'
+        db.delete_column(u'orm_customimagerecipe', u'id')
+
+        # Adding field 'CustomImageRecipe.recipe_ptr'
+        db.add_column(u'orm_customimagerecipe', u'recipe_ptr',
+                      self.gf('django.db.models.fields.related.OneToOneField')(default=0, to=orm['orm.Recipe'], unique=True, primary_key=True),
+                      keep_default=False)
+
+        # Removing M2M table for field packages on 'CustomImageRecipe'
+        db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
+
+
+    def backwards(self, orm):
+        # Deleting model 'ProjectPackage'
+        db.delete_table(u'orm_projectpackage')
+
+        # Removing M2M table for field recipe_includes on 'ProjectPackage'
+        db.delete_table(db.shorten_name(u'orm_projectpackage_recipe_includes'))
+
+        # Removing M2M table for field recipe_excludes on 'ProjectPackage'
+        db.delete_table(db.shorten_name(u'orm_projectpackage_recipe_excludes'))
+
+        # Removing M2M table for field recipe_appends on 'ProjectPackage'
+        db.delete_table(db.shorten_name(u'orm_projectpackage_recipe_appends'))
+
+        # Adding field 'CustomImageRecipe.name'
+        db.add_column(u'orm_customimagerecipe', 'name',
+                      self.gf('django.db.models.fields.CharField')(default='', max_length=100),
+                      keep_default=False)
+
+        # Adding field 'CustomImageRecipe.id'
+        db.add_column(u'orm_customimagerecipe', u'id',
+                      self.gf('django.db.models.fields.AutoField')(default=0, primary_key=True),
+                      keep_default=False)
+
+        # Deleting field 'CustomImageRecipe.recipe_ptr'
+        db.delete_column(u'orm_customimagerecipe', u'recipe_ptr_id')
+
+        # Adding M2M table for field packages on 'CustomImageRecipe'
+        m2m_table_name = db.shorten_name(u'orm_customimagerecipe_packages')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False)),
+            ('package', models.ForeignKey(orm[u'orm.package'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['customimagerecipe_id', 'package_id'])
+
+        # Adding unique constraint on 'CustomImageRecipe', fields ['name', 'project']
+        db.create_unique(u'orm_customimagerecipe', ['name', 'project_id'])
+
+
+    models = {
+        u'orm.bitbakeversion': {
+            'Meta': {'object_name': 'BitbakeVersion'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'giturl': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.branch': {
+            'Meta': {'unique_together': "(('layer_source', 'name'), ('layer_source', 'up_id'))", 'object_name': 'Branch'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'True', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.build': {
+            'Meta': {'object_name': 'Build'},
+            'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
+            'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+            'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'started_on': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        u'orm.buildartifact': {
+            'Meta': {'object_name': 'BuildArtifact'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        u'orm.customimagerecipe': {
+            'Meta': {'object_name': 'CustomImageRecipe', '_ormbases': [u'orm.Recipe']},
+            'base_recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'based_on_recipe'", 'to': u"orm['orm.Recipe']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            u'recipe_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['orm.Recipe']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        u'orm.helptext': {
+            'Meta': {'object_name': 'HelpText'},
+            'area': ('django.db.models.fields.IntegerField', [], {}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'helptext_build'", 'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        u'orm.layer': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'), ('layer_source', 'name'))", 'object_name': 'Layer'},
+            'description': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_index_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'summary': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'vcs_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_file_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_tree_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'})
+        },
+        u'orm.layer_version': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Layer_Version'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'layer_version_build'", 'null': 'True', 'to': u"orm['orm.Build']"}),
+            'commit': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'layer_version_layer'", 'to': u"orm['orm.Layer']"}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'local_path': ('django.db.models.fields.FilePathField', [], {'default': "'/'", 'max_length': '1024'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Project']", 'null': 'True'}),
+            'up_branch': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Branch']", 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.layersource': {
+            'Meta': {'unique_together': "(('sourcetype', 'apiurl'),)", 'object_name': 'LayerSource'},
+            'apiurl': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '63'}),
+            'sourcetype': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.layerversiondependency': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'LayerVersionDependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependees'", 'to': u"orm['orm.Layer_Version']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependencies'", 'to': u"orm['orm.Layer_Version']"}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.logmessage': {
+            'Meta': {'object_name': 'LogMessage'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'lineno': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'pathname': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Task']", 'null': 'True', 'blank': 'True'})
+        },
+        u'orm.machine': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Machine'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.package': {
+            'Meta': {'object_name': 'Package'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']", 'null': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'installed_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'installed_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Recipe']", 'null': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.package_dependency': {
+            'Meta': {'object_name': 'Package_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_target'", 'to': u"orm['orm.Package']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_source'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']", 'null': 'True'})
+        },
+        u'orm.package_file': {
+            'Meta': {'object_name': 'Package_File'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildfilelist_package'", 'to': u"orm['orm.Package']"}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.project': {
+            'Meta': {'object_name': 'Project'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']", 'null': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']", 'null': 'True'}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+        },
+        u'orm.projectlayer': {
+            'Meta': {'unique_together': "(('project', 'layercommit'),)", 'object_name': 'ProjectLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layercommit': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']", 'null': 'True'}),
+            'optional': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"})
+        },
+        u'orm.projectpackage': {
+            'Meta': {'object_name': 'ProjectPackage', '_ormbases': [u'orm.Package']},
+            u'package_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['orm.Package']", 'unique': 'True', 'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'recipe_appends': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'appends_set'", 'null': 'True', 'to': u"orm['orm.CustomImageRecipe']"}),
+            'recipe_excludes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'excludes_set'", 'null': 'True', 'to': u"orm['orm.CustomImageRecipe']"}),
+            'recipe_includes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'includes_set'", 'null': 'True', 'to': u"orm['orm.CustomImageRecipe']"})
+        },
+        u'orm.projecttarget': {
+            'Meta': {'object_name': 'ProjectTarget'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+        },
+        u'orm.projectvariable': {
+            'Meta': {'object_name': 'ProjectVariable'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.recipe': {
+            'Meta': {'unique_together': "(('layer_version', 'file_path', 'pathflags'),)", 'object_name': 'Recipe'},
+            'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'file_path': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'recipe_layer_version'", 'to': u"orm['orm.Layer_Version']"}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'pathflags': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.recipe_dependency': {
+            'Meta': {'object_name': 'Recipe_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_depends'", 'to': u"orm['orm.Recipe']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_recipe'", 'to': u"orm['orm.Recipe']"})
+        },
+        u'orm.release': {
+            'Meta': {'object_name': 'Release'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
+            'branch_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}),
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'helptext': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.releasedefaultlayer': {
+            'Meta': {'object_name': 'ReleaseDefaultLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.releaselayersourcepriority': {
+            'Meta': {'unique_together': "(('release', 'layer_source'),)", 'object_name': 'ReleaseLayerSourcePriority'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.LayerSource']"}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.target': {
+            'Meta': {'object_name': 'Target'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'license_manifest_path': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+        },
+        u'orm.target_file': {
+            'Meta': {'object_name': 'Target_File'},
+            'directory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'directory_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inodetype': ('django.db.models.fields.IntegerField', [], {}),
+            'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+            'size': ('django.db.models.fields.IntegerField', [], {}),
+            'sym_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'symlink_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_image_file': {
+            'Meta': {'object_name': 'Target_Image_File'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '254'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_installed_package': {
+            'Meta': {'object_name': 'Target_Installed_Package'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildtargetlist_package'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.task': {
+            'Meta': {'ordering': "('order', 'recipe')", 'unique_together': "(('build', 'recipe', 'task_name'),)", 'object_name': 'Task'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_build'", 'to': u"orm['orm.Build']"}),
+            'cpu_usage': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', 'decimal_places': '2'}),
+            'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', 'decimal_places': '2'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'logfile': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
+            'path_to_sstate_obj': ('django.db.models.fields.FilePathField', [], {'max_length': '500', 'blank': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'to': u"orm['orm.Recipe']"}),
+            'script_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'source_url': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'sstate_checksum': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'sstate_result': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'task_executed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'task_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'work_directory': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        u'orm.task_dependency': {
+            'Meta': {'object_name': 'Task_Dependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_depends'", 'to': u"orm['orm.Task']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_task'", 'to': u"orm['orm.Task']"})
+        },
+        u'orm.toastersetting': {
+            'Meta': {'object_name': 'ToasterSetting'},
+            'helptext': ('django.db.models.fields.TextField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '63'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        u'orm.variable': {
+            'Meta': {'object_name': 'Variable'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variable_build'", 'to': u"orm['orm.Build']"}),
+            'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'human_readable_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'variable_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'variable_value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.variablehistory': {
+            'Meta': {'object_name': 'VariableHistory'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'operation': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'variable': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vhistory'", 'to': u"orm['orm.Variable']"})
+        }
+    }
+
+    complete_apps = ['orm']
\ No newline at end of file
-- 
2.1.4



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

* [PATCH 10/34] toaster: buildinfohelper Add the concept of ProjectPackages
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (8 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 09/34] toaster: orm: Add db migration for new ProjectPackages table Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 11/34] toaster: orm add CustomImageRecipe generate contents function Michael Wood
                   ` (24 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

This adds the concept of ProjectPackage this is similar to the way
layers and recipes work in that we have a set of data which is part of
the build history and a set of data which is part of the configuration
data that toaster uses to guide people in configuring their project. We
create a set of  built_packages for every build but only create a package
for configuration purposes if we don't already have one, so that the
ProjectPackages only ever contains a unique list of packages that are
available to be added and removed from a CustomImageRecipe.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/bb/ui/buildinfohelper.py | 113 ++++++++++++++++++++++++++++++-----
 1 file changed, 97 insertions(+), 16 deletions(-)

diff --git a/bitbake/lib/bb/ui/buildinfohelper.py b/bitbake/lib/bb/ui/buildinfohelper.py
index 1d41bba..8e9a935 100644
--- a/bitbake/lib/bb/ui/buildinfohelper.py
+++ b/bitbake/lib/bb/ui/buildinfohelper.py
@@ -38,7 +38,7 @@ from toaster.orm.models import Target_Image_File, BuildArtifact
 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 Recipe_Dependency, ProjectPackage
 
 from toaster.orm.models import Project
 from bldcontrol.models import BuildEnvironment, BuildRequest
@@ -315,6 +315,11 @@ class ORMWrapper(object):
 
     def get_update_layer_version_object(self, build_obj, layer_obj, layer_version_information):
         if isinstance(layer_obj, Layer_Version):
+            # Special case the toaster-custom-images layer which is created
+            # on the fly so don't update the values which may cause the layer
+            # to be duplicated on a future get_or_create
+            if layer_obj.layer.name == "toaster-custom-images":
+                return layer_obj
             # We already found our layer version for this build so just
             # update it with the new build information
             logger.debug("We found our layer from toaster")
@@ -519,12 +524,14 @@ class ORMWrapper(object):
                         sym_target = filetarget_obj)
 
 
-    def save_target_package_information(self, build_obj, target_obj, packagedict, pkgpnmap, recipes):
+    def save_target_package_information(self, build_obj, target_obj, packagedict, pkgpnmap, recipes, built_package=False):
         assert isinstance(build_obj, Build)
         assert isinstance(target_obj, Target)
 
         errormsg = ""
         for p in packagedict:
+            # Search name swtiches round the installed name vs package name
+            # by default installed name == package name
             searchname = p
             if p not in pkgpnmap:
                 logger.warning("Image packages list contains %p, but is"
@@ -535,11 +542,34 @@ class ORMWrapper(object):
             if 'OPKGN' in pkgpnmap[p].keys():
                 searchname = pkgpnmap[p]['OPKGN']
 
-            packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname )
+            built_recipe = recipes[pkgpnmap[p]['PN']]
+
+            if built_package:
+                packagedict[p]['object'], created = Package.objects.get_or_create( build = build_obj, name = searchname )
+                recipe = built_recipe
+            else:
+                packagedict[p]['object'], created = \
+                        ProjectPackage.objects.get_or_create(name=searchname,
+                                                    project=build_obj.project)
+                if created == False:
+                    continue
+
+                try:
+                    recipe = self._cached_get(Recipe,
+                                              name=built_recipe.name,
+                                              layer_version__build=None,
+                                              file_path=built_recipe.file_path,
+                                              version=built_recipe.version)
+                except (Recipe.DoesNotExist,
+                        Recipe.MultipleObjectsReturned) as e:
+                    logger.info("We did not find one recipe for the"
+                                "configuration data package %s %s" % (p, e))
+                    continue
+
             if created or packagedict[p]['object'].size == -1:    # save the data anyway we can, not just if it was not created here; bug [YOCTO #6887]
                 # fill in everything we can from the runtime-reverse package data
                 try:
-                    packagedict[p]['object'].recipe = recipes[pkgpnmap[p]['PN']]
+                    packagedict[p]['object'].recipe = recipe
                     packagedict[p]['object'].version = pkgpnmap[p]['PV']
                     packagedict[p]['object'].installed_name = p
                     packagedict[p]['object'].revision = pkgpnmap[p]['PR']
@@ -565,7 +595,8 @@ class ORMWrapper(object):
             packagedict[p]['object'].installed_size = packagedict[p]['size']
             packagedict[p]['object'].save()
 
-            Target_Installed_Package.objects.create(target = target_obj, package = packagedict[p]['object'])
+            if built_package:
+                Target_Installed_Package.objects.create(target = target_obj, package = packagedict[p]['object'])
 
         packagedeps_objs = []
         for p in packagedict:
@@ -576,6 +607,21 @@ class ORMWrapper(object):
                     tdeptype = Package_Dependency.TYPE_TRECOMMENDS
 
                 try:
+                 # If this is a built package we are always going to have
+                 # new package objects as it's part of the build history
+                 # which also means new package dependency for each object.
+                 # However if they are project packages we don't want to
+                 # duplicate these so check if they exist or not first
+                    if built_package == False:
+                        try:
+                            Package_Dependency.objects.get(
+                                package=packagedict[p]['object'],
+                                depends_on=packagedict[px]['object'],
+                                dep_type=tdeptype)
+                            continue
+                        except Package_Dependency.DoesNotExist:
+                            pass
+
                     packagedeps_objs.append(Package_Dependency(
                         package = packagedict[p]['object'],
                         depends_on = packagedict[px]['object'],
@@ -626,19 +672,40 @@ class ORMWrapper(object):
         return log_object.save()
 
 
-    def save_build_package_information(self, build_obj, package_info, recipes):
-        assert isinstance(build_obj, Build)
+    def save_build_package_information(self, build_obj, package_info, recipes,
+                                       built_package):
+       # assert isinstance(build_obj, Build)
 
         # create and save the object
         pname = package_info['PKG']
+        built_recipe = recipes[package_info['PN']]
         if 'OPKGN' in package_info.keys():
             pname = package_info['OPKGN']
 
-        bp_object, _ = Package.objects.get_or_create( build = build_obj,
-                                       name = pname )
+        if built_package:
+            bp_object, _ = Package.objects.get_or_create( build = build_obj,
+                                                         name = pname )
+            recipe = built_recipe
+        else:
+            bp_object, created = \
+                    ProjectPackage.objects.get_or_create(name=pname,
+                                                    project=build_obj.project)
+            if created == False:
+                return
+            try:
+                recipe = self._cached_get(Recipe,
+                                          name=built_recipe.name,
+                                          layer_version__build=None,
+                                          file_path=built_recipe.file_path,
+                                          version=built_recipe.version)
+
+            except (Recipe.DoesNotExist, Recipe.MultipleObjectsReturned):
+                logger.debug("We did not find one recipe for the configuration"
+                             "data package %s" % pname)
+                return
 
         bp_object.installed_name = package_info['PKG']
-        bp_object.recipe = recipes[package_info['PN']]
+        bp_object.recipe = recipe
         bp_object.version = package_info['PKGV']
         bp_object.revision = package_info['PKGR']
         bp_object.summary = package_info['SUMMARY']
@@ -658,7 +725,13 @@ class ORMWrapper(object):
             Package_File.objects.bulk_create(packagefile_objects)
 
         def _po_byname(p):
-            pkg, created = Package.objects.get_or_create(build = build_obj, name = p)
+            if built_package:
+                pkg, created = Package.objects.get_or_create(build=build_obj,
+                                                             name=p)
+            else:
+                pkg, created = ProjectPackage.objects.get_or_create(name=p,
+                                                    project=build_obj.project)
+
             if created:
                 pkg.size = -1
                 pkg.save()
@@ -1161,7 +1234,8 @@ class BuildInfoHelper(object):
                 filedata = BuildInfoHelper._get_data_from_event(event)['filedata'][target.target]
 
                 try:
-                    self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'])
+                    self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata, pkgdata, self.internal_state['recipes'], built_package=True)
+                    self.orm_wrapper.save_target_package_information(self.internal_state['build'], target, imgdata.copy(), pkgdata, self.internal_state['recipes'], built_package=False)
                 except KeyError as e:
                     logger.warn("KeyError in save_target_package_information"
                                 "%s ", e)
@@ -1305,10 +1379,17 @@ class BuildInfoHelper(object):
 
     def store_build_package_information(self, event):
         package_info = BuildInfoHelper._get_data_from_event(event)
-        self.orm_wrapper.save_build_package_information(self.internal_state['build'],
-                            package_info,
-                            self.internal_state['recipes'],
-                            )
+        self.orm_wrapper.save_build_package_information(
+            self.internal_state['build'],
+            package_info,
+            self.internal_state['recipes'],
+            built_package=True)
+
+        self.orm_wrapper.save_build_package_information(
+            self.internal_state['build'],
+            package_info,
+            self.internal_state['recipes'],
+            built_package=False)
 
     def _store_build_done(self, errorcode):
         logger.info("Build exited with errorcode %d", errorcode)
-- 
2.1.4



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

* [PATCH 11/34] toaster: orm add CustomImageRecipe generate contents function
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (9 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 10/34] toaster: buildinfohelper Add the concept of ProjectPackages Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 12/34] toaster: move CustomImageRecipe generation to API entry point Michael Wood
                   ` (23 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Add function generate_recipe_file_contents to dump the custom image
recipe instance to a string for use either to push to the user as a
downloaded version of their custom image recipe or to use to generate
the recipe that we build.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/orm/models.py | 49 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 2cbbb85..b4cfe24 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -1264,6 +1264,55 @@ class CustomImageRecipe(Recipe):
     base_recipe = models.ForeignKey(Recipe, related_name='based_on_recipe')
     project = models.ForeignKey(Project)
 
+    def generate_recipe_file_contents(self):
+        """Generate the contents for the recipe file."""
+        # If we have no excluded packages we only need to _append
+        if self.excludes_set.count() == 0:
+            packages_conf = "IMAGE_INSTALL_append= \""
+
+            for pkg in self.appends_set.all():
+                packages_conf += pkg.name+' '
+        else:
+            packages_conf = "IMAGE_INSTALL = \""
+            # We add all the known packages to be built by this recipe apart
+            # from the packagegroups, which would bring the excluded package
+            # back in and locale packages which are dynamic packages which
+            # bitbake will not know about.
+            for pkg in \
+                    self.includes_set.exclude(
+                    Q(pk__in=self.excludes_set.values_list('pk', flat=True)) |
+                    Q(name__icontains="packagegroup") |
+                    Q(name__icontains="locale")):
+                print pkg.name
+                packages_conf += pkg.name+' '
+
+        packages_conf += "\""
+
+        base_recipe = open("%s/%s" %
+                           (self.base_recipe.layer_version.dirpath,
+                            self.base_recipe.file_path), 'r').read()
+
+        info = {"date" : timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
+                "base_recipe" : base_recipe,
+                "recipe_name" : self.name,
+                "base_recipe_name" : self.base_recipe.name,
+                "license" : self.license,
+                "summary" : self.summary,
+                "description" : self.description,
+                "packages_conf" : packages_conf.strip(),
+               }
+
+        recipe_contents = ("# Original recipe %(base_recipe_name)s \n"
+                           "%(base_recipe)s\n\n"
+                           "# Recipe %(recipe_name)s \n"
+                           "# Customisation Generated by Toaster on %(date)s\n"
+                           "SUMMARY = \"%(summary)s\"\n"
+                           "DESCRIPTION = \"%(description)s\"\n"
+                           "LICENSE = \"%(license)s\"\n"
+                           "%(packages_conf)s") % info
+
+        return recipe_contents
+
 class ProjectVariable(models.Model):
     project = models.ForeignKey(Project)
     name = models.CharField(max_length=100)
-- 
2.1.4



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

* [PATCH 12/34] toaster: move CustomImageRecipe generation to API entry point
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (10 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 11/34] toaster: orm add CustomImageRecipe generate contents function Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:15 ` [PATCH 13/34] toaster: views Add view to download custom recipe Michael Wood
                   ` (22 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

Use the CustomImageRecipe generate_recipe_file_contents to generate the
recipe that we build from. Move creation of the dummy layer and recipe
object to the point of recipe creation as we need these objects before
the build time. Also update the methods to add and remove packages to
account for the CustomImageRecipe inheriting from Recipe.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../toaster/bldcontrol/localhostbecontroller.py    | 29 ++++---
 bitbake/lib/toaster/toastergui/views.py            | 90 ++++++++++++++++++----
 2 files changed, 90 insertions(+), 29 deletions(-)

diff --git a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
index 81b773e..508efd4 100644
--- a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
+++ b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
@@ -241,23 +241,22 @@ class LocalhostBEController(BuildEnvironmentController):
                     conf.write('BBPATH .= ":${LAYERDIR}"\nBBFILES += "${LAYERDIR}/recipes/*.bb"\n')
 
             # create recipe
-            recipe = os.path.join(layerpath, "recipes", "%s.bb" % target.target)
-            with open(recipe, "w") as recipef:
-                recipef.write("require %s\n" % customrecipe.base_recipe.file_path)
-                packages = [pkg.name for pkg in customrecipe.packages.all()]
-                if packages:
-                    recipef.write('IMAGE_INSTALL = "%s"\n' % ' '.join(packages))
+            recipe_path = \
+                    os.path.join(layerpath, "recipes", "%s.bb" % target.target)
+            with open(recipe_path, "w") as recipef:
+                recipef.write(customrecipe.generate_recipe_file_contents())
+
+            # Update the layer and recipe objects
+            customrecipe.layer_version.dirpath = layerpath
+            customrecipe.layer_version.save()
+
+            customrecipe.file_path = recipe_path
+            customrecipe.save()
 
             # create *Layer* objects needed for build machinery to work
-            layer = Layer.objects.get_or_create(name="Toaster Custom layer",
-                                                summary="Layer for custom recipes",
-                                                vcs_url="file://%s" % layerpath)[0]
-            breq = target.req
-            lver = Layer_Version.objects.get_or_create(project=breq.project, layer=layer,
-                                                       dirpath=layerpath, build=breq.build)[0]
-            ProjectLayer.objects.get_or_create(project=breq.project, layercommit=lver,
-                                               optional=False)
-            BRLayer.objects.get_or_create(req=breq, name=layer.name, dirpath=layerpath,
+            BRLayer.objects.get_or_create(req=target.req,
+                                          name=layer.name,
+                                          dirpath=layerpath,
                                           giturl="file://%s" % layerpath)
         if os.path.isdir(layerpath):
             layerlist.append(layerpath)
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 243bb09..45fb055 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2649,10 +2649,37 @@ if True:
 
         # create custom recipe
         try:
+            # create layer 'Custom layer' and verion if needed
+            layer = Layer.objects.get_or_create(name="toaster-custom-images",
+                                         summary="Layer for custom recipes",
+                                         vcs_url="file:///toaster_created_layer"
+                                         )[0]
+
+            lver = Layer_Version.objects.get_or_create(
+                project=params['project'],
+                layer=layer,
+                dirpath='toaster_created_layer',
+                build=None)[0]
+
+            # Add a dependency on our layer to the base recipe's layer
+            LayerVersionDependency.objects.get_or_create(layer_version=lver,
+                                       depends_on=params["base"].layer_version)
+
+            # Add it to our current project if needed
+            ProjectLayer.objects.get_or_create(project=params['project'],
+                                               layercommit=lver,
+                                               optional=False)
+
+            # Create the actual recipe
             recipe = CustomImageRecipe.objects.create(
                          name=request.POST["name"],
                          base_recipe=params["base"],
-                         project=params["project"])
+                         project=params["project"],
+                         file_path=request.POST["name"],
+                         license="MIT",
+                         version="0.1",
+                         layer_version=lver)
+
         except Error as err:
             return {"error": "Can't create custom recipe: %s" % err}
 
@@ -2665,20 +2692,24 @@ if True:
             # We don't want these packages to be linked to anything because
             # that underlying data may change e.g. delete a build
             for package in build.package_set.all():
-                # Create the duplicate
-                package.pk = None
-                package.save()
-                # Disassociate the package from the build
-                package.build = None
-                package.save()
-                recipe.packages.add(package)
+                 _copy_packge_to_recipe(recipe, package)
         else:
-            logger.warn("No packages found for this base recipe")
+            logger.debug("No packages found for this base recipe")
 
         return {"error": "ok",
                 "url": reverse('customrecipe', args=(params['project'].pk,
                                                      recipe.id))}
 
+    def _copy_packge_to_recipe(recipe, package):
+        """ copy a package from another recipe """
+        package.pk = None
+        package.save()
+        # Disassociate the package from the build
+        package.build = None
+        package.recipe = recipe
+        package.save()
+        return package
+
     @xhr_response
     def xhr_customrecipe_id(request, recipe_id):
         """
@@ -2741,8 +2772,12 @@ if True:
                              "not found" % recipe_id}
 
         if request.method == 'GET' and not package_id:
+            packages = recipe.package_set.values("id", "name", "version")
+
             return {"error": "ok",
-                    "packages": list(recipe.packages.values_list('id'))}
+                    "packages" : list(packages),
+                    "total" : len(packages)
+                   }
 
         try:
             package = Package.objects.get(id=package_id)
@@ -2751,11 +2786,38 @@ if True:
                              "not found" % package_id}
 
         if request.method == 'PUT':
-            recipe.packages.add(package)
-            return {"error": "ok"}
+            # As these requests are asynchronous we need to make sure we don't
+            # already have the package in the image recipe
+            if recipe.package_set.filter(Q(name=package.name) &
+                                      Q(version=package.version)).count() > 0:
+                return {"error" : "Package %s already in recipe" %
+                        package.name }
+
+            # Make a copy of this package
+            dependencies = _get_package_dependencies(package.pk)
+
+            package = _copy_packge_to_recipe(recipe, package)
+            recipe.package_set.add(package)
+
+            # Filter out dependencies already in the custom image
+            all_in_image = recipe.package_set.all().values_list('name',
+                                                                flat=True)
+            def in_image(pkg):
+                return pkg['name'] not in all_in_image
+
+            dependencies = filter(in_image, dependencies['runtime_deps'])
+            return {"error": "ok",
+                    "new_package" : {"id": package.pk,
+                                     "url": reverse('xhr_customrecipe_packages',
+                                                 args=(recipe.pk, package.pk))
+                                    },
+                    "dependencies_needed" : dependencies,
+                   }
+
         elif request.method == 'DELETE':
-            if package in recipe.packages.all():
-                recipe.packages.remove(package)
+            if package in recipe.package_set.all():
+                # note that we are infact deleting the copy of the package
+                package.delete()
                 return {"error": "ok"}
             else:
                 return {"error": "Package '%s' is not in the recipe '%s'" % \
-- 
2.1.4



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

* [PATCH 13/34] toaster: views Add view to download custom recipe
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (11 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 12/34] toaster: move CustomImageRecipe generation to API entry point Michael Wood
@ 2015-12-08 21:15 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 14/34] toaster: tables Add table for Packages and update SelectPackagesTable Michael Wood
                   ` (21 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:15 UTC (permalink / raw)
  To: toaster

View to provide the custom recipe download feature. The recipe is
generated on-demand to make sure that it is the most current version of
the Custom recipe.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/urls.py  |  3 +++
 bitbake/lib/toaster/toastergui/views.py | 15 ++++++++++++++-
 2 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index a1adbb7..50acb1b 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -134,6 +134,9 @@ urlpatterns = patterns('toastergui.views',
             'customrecipe',
             name="customrecipe"),
 
+        url(r'^project/(?P<pid>\d+)/customrecipe/(?P<recipe_id>\d+)/download$',
+            'customrecipe_download',
+            name="customrecipedownload"),
 
 
         # typeahead api end points
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 45fb055..c10b19f 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -27,7 +27,7 @@ import operator,re
 
 from django.db.models import F, Q, Sum, Count, Max
 from django.db import IntegrityError, Error
-from django.shortcuts import render, redirect
+from django.shortcuts import render, redirect, get_object_or_404
 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
@@ -2749,6 +2749,19 @@ if True:
         else:
             return {"error": "Method %s is not supported" % request.method}
 
+    def customrecipe_download(request, pid, recipe_id):
+        recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
+
+        file_data = recipe.generate_recipe_file_contents()
+
+        response = HttpResponse(file_data, content_type='text/plain')
+        response['Content-Disposition'] = \
+                'attachment; filename="%s_%s.bb"' % (recipe.name,
+                                                     recipe.version)
+
+        return response
+
+
     @xhr_response
     def xhr_customrecipe_packages(request, recipe_id, package_id):
         """
-- 
2.1.4



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

* [PATCH 14/34] toaster: tables Add table for Packages and update SelectPackagesTable
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (12 preceding siblings ...)
  2015-12-08 21:15 ` [PATCH 13/34] toaster: views Add view to download custom recipe Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 15/34] toaster: Continue front end features to custom image recipe page Michael Wood
                   ` (20 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Create a Packages table for use as the image details page.
Change the SelectPackagesTable table to inherit from the Packages table.
Remove the need for a separate view by adding the additional template
context items to the Table's page context.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tables.py           | 149 +++++++++++++++++----
 .../snippets/pkg_dependencies_popover.html         |  14 ++
 bitbake/lib/toaster/toastergui/urls.py             |  10 +-
 bitbake/lib/toaster/toastergui/views.py            |   9 --
 4 files changed, 143 insertions(+), 39 deletions(-)
 create mode 100644 bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html

diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 6bbf0d7..7ce6651 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -21,8 +21,8 @@
 
 from toastergui.widgets import ToasterTable
 from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
-from orm.models import CustomImageRecipe, Package
-from django.db.models import Q, Max
+from orm.models import CustomImageRecipe, Package, Target, Build
+from django.db.models import Q, Max, Sum
 from django.conf.urls import url
 from django.core.urlresolvers import reverse
 from django.views.generic import TemplateView
@@ -440,8 +440,8 @@ class CustomImagesTable(ToasterTable):
     def get_context_data(self, **kwargs):
         context = super(CustomImagesTable, self).get_context_data(**kwargs)
         project = Project.objects.get(pk=kwargs['pid'])
+        # TODO put project into the ToasterTable base class
         context['project'] = project
-        context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project']))
         return context
 
     def setup_queryset(self, *args, **kwargs):
@@ -459,22 +459,31 @@ class CustomImagesTable(ToasterTable):
 
         self.add_column(title="Custom image",
                         hideable=False,
+                        orderable=True,
+                        field_name="name",
                         static_data_name="name",
                         static_data_template=name_link_template)
 
         self.add_column(title="Recipe file",
                         static_data_name='recipe_file',
-                        static_data_template='')
+                        static_data_template='',
+                        field_name='local_path')
+
+        approx_packages_template = '''
+        <a href="{% url 'customrecipe' extra.pid data.id %}">
+          {{data.package_set.all|length}}
+        </a>'''
 
-        approx_packages_template = '<a href="#imagedetails">{{data.packages.all|length}}</a>'
         self.add_column(title="Approx packages",
                         static_data_name='approx_packages',
                         static_data_template=approx_packages_template)
 
 
-        build_btn_template = '''<button data-recipe-name="{{data.name}}"
+        build_btn_template = '''
+        <button data-recipe-name="{{data.name}}"
         class="btn btn-block build-recipe-btn" style="margin-top: 5px;" >
-        Build</button>'''
+        Build
+        </button>'''
 
         self.add_column(title="Build",
                         hideable=False,
@@ -497,12 +506,19 @@ class ImageRecipesTable(RecipesTable):
 
 
     def setup_columns(self, *args, **kwargs):
+
+        name_link_template = '''
+        <a href="{% url 'recipedetails' extra.pid data.pk %}">{{data.name}}</a>
+        '''
+
         self.add_column(title="Image recipe",
                         help_text="When you build an image recipe, you get an "
                                   "image: a root file system you can"
                                   "deploy to a machine",
                         hideable=False,
                         orderable=True,
+                        static_data_name="name",
+                        static_data_template=name_link_template,
                         field_name="name")
 
         super(ImageRecipesTable, self).setup_columns(*args, **kwargs)
@@ -565,8 +581,96 @@ class SoftwareRecipesTable(RecipesTable):
 
         self.add_column(**RecipesTable.build_col)
 
+class PackagesTable(ToasterTable):
+    """ Table to display the packages in a recipe from it's last successful
+    build"""
+
+    def __init__(self, *args, **kwargs):
+        super(PackagesTable, self).__init__(*args, **kwargs)
+        self.title = "Packages included"
+        self.packages = None
+        self.default_orderby = "name"
+
+    def create_package_list(self, recipe, project_id):
+        """Creates a list of packages for the specified recipe by looking for
+        the last SUCCEEDED build of ther recipe"""
+
+        target = Target.objects.filter(Q(target=recipe.name) &
+                                       Q(build__project_id=project_id) &
+                                       Q(build__outcome=Build.SUCCEEDED)
+                                      ).last()
+
+        if target:
+            return target.build.package_set.all()
+
+        # Target/recipe never successfully built so empty queryset
+        return Package.objects.none()
+
+    def get_context_data(self, **kwargs):
+        """Context for rendering the sidebar and other items on the recipe
+        details page """
+        context = super(PackagesTable, self).get_context_data(**kwargs)
+
+        recipe = Recipe.objects.get(pk=kwargs['recipe_id'])
+        project = Project.objects.get(pk=kwargs['pid'])
+
+        in_project = (recipe.layer_version.pk in
+                      project.get_project_layer_versions(pk=True))
+
+        packages = self.create_package_list(recipe, project.pk)
+
+        context.update({'project': project,
+                        'recipe' : recipe,
+                        'packages': packages,
+                        'approx_pkg_size' : packages.aggregate(Sum('size')),
+                        'in_project' : in_project,
+                       })
+
+        return context
+
+    def setup_queryset(self, *args, **kwargs):
+        recipe = Recipe.objects.get(pk=kwargs['recipe_id'])
+
+        self.queryset = self.create_package_list(recipe, kwargs['pid'])
+        self.queryset = self.queryset.order_by('name')
+
+    def setup_columns(self, *args, **kwargs):
+        self.add_column(title="Package",
+                        hideable=False,
+                        orderable=True,
+                        field_name="name")
+
+        self.add_column(title="Package Version",
+                        field_name="version",
+                        hideable=False)
+
+        self.add_column(title="Approx Size",
+                        orderable=True,
+                        static_data_name="size",
+                        static_data_template="{% load projecttags %} \
+                        {{data.size|filtered_filesizeformat}}")
+
+        self.add_column(title="License",
+                        field_name="license",
+                        orderable=True)
+
+
+        self.add_column(title="Dependencies",
+                        static_data_name="dependencies",
+                        static_data_template='\
+                        {% include "snippets/pkg_dependencies_popover.html" %}')
+
+        self.add_column(title="Recipe",
+                        field_name="recipe__name",
+                        orderable=True,
+                        hidden=True)
+
+        self.add_column(title="Recipe version",
+                        field_name="recipe__version",
+                        hidden=True)
+
 
-class SelectPackagesTable(ToasterTable):
+class SelectPackagesTable(PackagesTable):
     """ Table to display the packages to add and remove from an image """
 
     def __init__(self, *args, **kwargs):
@@ -593,24 +697,20 @@ class SelectPackagesTable(ToasterTable):
         self.static_context_extra['current_packages'] = \
                 cust_recipe.packages.values_list('pk', flat=True)
 
-    def setup_columns(self, *args, **kwargs):
-        self.add_column(title="Package",
-                        hideable=False,
-                        orderable=True,
-                        field_name="name")
+    def get_context_data(self, **kwargs):
+        context = super(SelectPackagesTable, self).get_context_data(**kwargs)
+        custom_recipe = CustomImageRecipe.objects.get(pk=kwargs['recipe_id'])
 
-        self.add_column(title="Package Version",
-                        field_name="version")
+        context['recipe'] = custom_recipe
+        context['approx_pkg_size'] =  custom_recipe.package_set.aggregate(Sum('size'))
+        return context
 
-        self.add_column(title="Approx Size",
-                        orderable=True,
-                        static_data_name="size",
-                        static_data_template="{% load projecttags %} \
-                        {{data.size|filtered_filesizeformat}}")
-        self.add_column(title="summary",
-                        field_name="summary")
+
+    def setup_columns(self, *args, **kwargs):
+        super(SelectPackagesTable, self).setup_columns(*args, **kwargs)
 
         self.add_column(title="Add | Remove",
+                        hideable=False,
                         help_text="Use the add and remove buttons to modify "
                         "the package content of you custom image",
                         static_data_name="add_rm_pkg_btn",
@@ -637,11 +737,10 @@ class SelectPackagesTable(ToasterTable):
                                 self.filter_not_in_image)
                         ])
 
-    def filter_in_image(self, count_only=False):
+    def filter_in_image(self):
         return self.queryset.filter(
             pk__in=self.static_context_extra['current_packages'])
 
-
-    def filter_not_in_image(self, count_only=False):
+    def filter_not_in_image(self):
         return self.queryset.exclude(
             pk__in=self.static_context_extra['current_packages'])
diff --git a/bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html b/bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html
new file mode 100644
index 0000000..a08409a
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover.html
@@ -0,0 +1,14 @@
+{# Popover that displays the dependences and sizes of a package 'data' used in the Packages table #}
+{% with data.package_dependencies_source.count as dep_count %}
+{% load projecttags %}
+{% if dep_count %}
+ <a data-content="<ul class='unstyled'>
+  {% for dep in data.package_dependencies_source.all %}
+  <li>{{dep.depends_on.name}} {% if dep.depends_on.size > 0 %}({{dep.depends_on.size|filtered_filesizeformat}}){% endif %}</li>
+    {% endfor %}
+  </ul>" title="" class="btn" data-original-title="
+  <strong>{{data.name}}</strong> dependencies - <strong>{{data.package_dependencies_source.get_total_source_deps_size.depends_on__size__sum|filtered_filesizeformat}}</strong>">
+    {{dep_count}}
+</a>
+{% endif %}
+{% endwith %}
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index 50acb1b..36e5c38 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -103,13 +103,10 @@ urlpatterns = patterns('toastergui.views',
             tables.NewCustomImagesTable.as_view(template_name="newcustomimage.html"),
             name="newcustomimage"),
 
-
         url(r'^project/(?P<pid>\d+)/layers/$',
             tables.LayersTable.as_view(template_name="generic-toastertable-page.html"),
             name="projectlayers"),
 
-
-
         url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)$',
             'layerdetails', name='layerdetails'),
 
@@ -127,17 +124,20 @@ urlpatterns = patterns('toastergui.views',
 
 
         url(r'^project/(?P<pid>\d+)/customrecipe/(?P<recipeid>\d+)/selectpackages/$',
-            tables.SelectPackagesTable.as_view(template_name="generic-toastertable-page.html"), name="recipeselectpackages"),
+            tables.SelectPackagesTable.as_view(), name="recipeselectpackages"),
 
 
         url(r'^project/(?P<pid>\d+)/customrecipe/(?P<recipe_id>\d+)$',
-            'customrecipe',
+            tables.SelectPackagesTable.as_view(template_name="customrecipe.html"),
             name="customrecipe"),
 
         url(r'^project/(?P<pid>\d+)/customrecipe/(?P<recipe_id>\d+)/download$',
             'customrecipe_download',
             name="customrecipedownload"),
 
+        url(r'^project/(?P<pid>\d+)/recipe/(?P<recipe_id>\d+)$',
+            tables.PackagesTable.as_view(template_name="recipedetails.html"),
+            name="recipedetails"),
 
         # typeahead api end points
         url(r'^xhr_typeahead/(?P<pid>\d+)/layers$',
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index c10b19f..53553a5 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2886,15 +2886,6 @@ if True:
 
         return(vars_managed,sorted(vars_fstypes),vars_blacklist)
 
-    def customrecipe(request, pid, recipe_id):
-        project = Project.objects.get(pk=pid)
-        context = {'project' : project,
-                   'projectlayers': [],
-                   'recipe' : CustomImageRecipe.objects.get(pk=recipe_id)
-                  }
-
-        return render(request, "customrecipe.html", context)
-
     @_template_renderer("projectconf.html")
     def projectconf(request, pid):
 
-- 
2.1.4



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

* [PATCH 15/34] toaster: Continue front end features to custom image recipe page.
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (13 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 14/34] toaster: tables Add table for Packages and update SelectPackagesTable Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 16/34] toaster: newcustomimage Move modal dialog out of newcustomimage template Michael Wood
                   ` (19 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Continuation of the work on the custom image recipe page, this brings
in:

- Basic notification of having added/removed a package.
- Connect up Build button
- Download recipe feature
- No packages states
- Project bread crumb
- Display additional recipe metadata
- Update accessors for recipe object inheritance changes

[YOCTO #8082]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../toaster/toastergui/static/js/customrecipe.js   |  34 +++--
 .../toaster/toastergui/templates/customrecipe.html | 146 ++++++++++++---------
 .../toastergui/templates/pkg_add_rm_btn.html       |   4 +-
 3 files changed, 114 insertions(+), 70 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/static/js/customrecipe.js b/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
index 4f6b304..4cd9382 100644
--- a/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
+++ b/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
@@ -3,6 +3,7 @@
 function customRecipePageInit(ctx) {
 
   var urlParams = libtoaster.parseUrlParams();
+  var customiseTable = $("#selectpackagestable");
 
   (function notificationRequest(){
     if (urlParams.hasOwnProperty('notify') && urlParams.notify === 'new'){
@@ -10,7 +11,7 @@ function customRecipePageInit(ctx) {
     }
   })();
 
-  $("#recipeselection").on('table-done', function(e, total, tableParams){
+  customiseTable.on('table-done', function(e, total, tableParams){
     /* Table is done so now setup the click handler for the package buttons */
     $(".add-rm-package-btn").click(function(e){
       e.preventDefault();
@@ -21,30 +22,45 @@ function customRecipePageInit(ctx) {
   function addRemovePackage(pkgBtn, tableParams){
     var pkgBtnData = pkgBtn.data();
     var method;
-    var buttonToShow;
+    var msg = "You have ";
 
     if (pkgBtnData.directive == 'add') {
       method = 'PUT';
-      buttonToShow = '#package-rm-btn-' + pkgBtnData.package;
+      msg += "added 1 package to "+ctx.recipe.name+":";
     } else if (pkgBtnData.directive == 'remove') {
       method = 'DELETE';
-      buttonToShow = '#package-add-btn-' + pkgBtnData.package;
+      msg += "removed 1 package from "+ctx.recipe.name+":";
     } else {
       throw("Unknown package directive: should be add or remove");
     }
 
+    msg += ' <strong>' + pkgBtnData.name + '<strong>';
+
     $.ajax({
         type: method,
         url: pkgBtnData.packageUrl,
         headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
         success: function(data){
-          /* Invalidate the Add | Rm package table's current cache */
+          if (data.error !== 'ok'){
+            console.warn(data.error);
+            return;
+          }
+          /* Reload and Invalidate the Add | Rm package table's current data */
           tableParams.nocache = true;
-          $.get(ctx.tableApiUrl, tableParams);
-          /* Swap the buttons around */
-          pkgBtn.hide();
-          $(buttonToShow).show();
+          customiseTable.trigger('reload', [tableParams]);
+
+          libtoaster.showChangeNotification(msg);
         }
     });
   }
+
+  /* Trigger a build of your custom image */
+  $(".build-custom-image").click(function(){
+    libtoaster.startABuild(libtoaster.ctx.projectBuildsUrl,
+      libtoaster.ctx.projectId,
+      ctx.recipe.name,
+      function(){
+        window.location.replace(libtoaster.ctx.projectBuildsUrl);
+    });
+  });
 }
diff --git a/bitbake/lib/toaster/toastergui/templates/customrecipe.html b/bitbake/lib/toaster/toastergui/templates/customrecipe.html
index 823bbd8..743625c 100644
--- a/bitbake/lib/toaster/toastergui/templates/customrecipe.html
+++ b/bitbake/lib/toaster/toastergui/templates/customrecipe.html
@@ -4,13 +4,29 @@
 {% load static %}
 {% block pagecontent %}
 
-{% include "projecttopbar.html" %}
+<div class="section">
+  <ul class="breadcrumb">
+    <li>
+      <a href="{% url 'project' project.id %}">{{project.name}}</a>
+      <span class="divider">&rarr;</span>
+    </li>
+    <li><a href="{% url 'projectcustomimages' project.id %}">Custom images</a>
+      <span class="divider">&rarr;</span>
+    </li>
+    <li class="active">
+      {{recipe.name}} ({{recipe.layer_version.layer.name}})
+    </li>
+  </ul>
+</div>
 
 <script src="{% static 'js/customrecipe.js' %}"></script>
 <script>
   $(document).ready(function (){
     var ctx = {
-      tableApiUrl: "{% url 'recipeselectpackages' project.id recipe.pk %}?format=json"
+      recipe : {
+        id: {{recipe.pk}},
+        name: "{{recipe.name}}",
+      }
     };
 
     try {
@@ -30,7 +46,7 @@
   <div class="page-header air">
     <h1>
       {{recipe.name}}
-      <small>({{recipe.base_recipe.name}})</small>
+      <small>({{recipe.layer_version.layer.name}})</small>
     </h1>
   </div>
 </div>
@@ -38,44 +54,47 @@
 <div class="row-fluid span11">
   <div class="span8">
     <div class="button-place btn-group" style="width: 100%">
-      <a class="btn btn-large span6" href="#" id="build-custom-image" style="width: 50%">
+      <a class="btn btn-large span6 build-custom-image" href="#" style="width: 50%">
         Build {{recipe.name}}
       </a>
-      <button class="btn btn-large span6" data-toggle="modal" data-target="#download-file" id="download" style="width: 50%">
-      Download recipe file
-    </button>
-  </div>
-  <div id="no-package-results" class="air" style="display:none;">
-    <div class="alert">
-      <h3>No packages found</h3>
-      <p>You might consider <a href="all-software-recipes.html">searching the list of recipes</a> instead. If you find a recipe that matches the name of the package you want:</p>
-      <ol>
-        <li>Add the layer providing the recipe to your project</li>
-        <li>Build the recipe</li>
-        <li>Once the build completes, come back to this page and search for the package</li>
-      </ol>
-      <form class="input-append no-results">
-        <input type="text" class="input-xlarge" value="search query">
-          <a href="#" class="add-on btn">
-            <i class="icon-remove"></i>
-          </a>
-          <button class="btn">Search</button>
-          <button class="btn btn-link" id="show-all">Show all packages</button>
-        </form>
-      </div>
+      <a href="{% url 'customrecipedownload' project.id recipe.id %}" class="btn btn-large span6" style="width: 50%">
+        Download recipe file
+      </a>
     </div>
-    <div id="packages-table">
-      {% url 'recipeselectpackages' project.id recipe.id as xhr_table_url %}
-      {% with 'recipeselection' as table_name %}
-      {% with 'Add | Remove packages' as  title %}
-
-      <h2>{{title}} (<span class="table-count-{{table_name}}"></span>) </h2>
-
-      {% include "toastertable.html" %}
-      {% endwith %}
-      {% endwith %}
+    <div id="no-package-results" class="air" style="display:none;">
+      <div class="alert">
+        <h3>No packages found</h3>
+        <p>You might consider <a href="all-software-recipes.html">searching the list of recipes</a> instead. If you find a recipe that matches the name of the package you want:</p>
+        <ol>
+          <li>Add the layer providing the recipe to your project</li>
+          <li>Build the recipe</li>
+          <li>Once the build completes, come back to this page and search for the package</li>
+        </ol>
+        <form class="input-append no-results">
+          <input type="text" class="input-xlarge" value="search query">
+            <a href="#" class="add-on btn">
+              <i class="icon-remove"></i>
+            </a>
+            <button class="btn">Search</button>
+            <button class="btn btn-link" id="show-all">Show all packages</button>
+          </form>
+        </div>
+      </div>
+      <div id="packages-table">
+        {% if recipe.package_set.count == 0 and last_build == None %}
+        <h2> Add | Remove packages </h2>
+        <div class="alert alert-info air">
+          <p class="lead">Toaster has no package information for {{recipe.name}}. To generate package information, build {{recipe.name}}</p>
+          <button class="btn btn-info btn-large build-custom-recipe" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button>
+        </div>
+        {% else %}
+        {# ToasterTable for Adding remove packages #}
+        {% url 'recipeselectpackages' project.id recipe.id as xhr_table_url %}
+        <h2>{{title}} (<span class="table-count-{{table_name}}">0</span>) </h2>
+        {% include "toastertable.html" %}
+        {% endif %}
+      </div>
     </div>
-  </div>
     <div class="span4 well">
       <h2 style="margin-bottom:20px;">About {{recipe.name}}</h2>
 
@@ -84,59 +103,68 @@
           Approx. packages included
           <i class="icon-question-sign get-help" title="" data-original-title="The number of packages included is based on information from previous builds and from parsing layers, so we can never be sure it is 100% accurate"></i>
         </dt>
-        <dd class="no-packages">{{recipe.packages.count}}</dd>
-        <!-- <dt>
+        <dd class="no-packages">{{recipe.package_set.all.count}}</dd>
+        <dt>
           Approx. package size
           <i class="icon-question-sign get-help" title="" data-original-title="Package size is based on information from previous builds, so we can never be sure it is 100% accurate"></i>
         </dt>
-        <dd>244.3 MB</dd>
+        <dd>{{approx_pkg_size.size__sum|filtered_filesizeformat}}</dd>
+        {% if last_build %}
         <dt>Last build</dt>
         <dd>
           <i class="icon-ok-sign success"></i>
-          <a href="build-dashboard.html">11/06/15 15:22</a>
+          <a href="{% url 'projectbuilds' project.id%}">{{last_build.completed_on|date:"d/m/y H:i"}}</a>
         </dd>
+        {% endif %}
+        <dt>Layer</dt>
+        <dd><a href="{% url 'layerdetails' project.id recipe.layer_version.pk %}">{{recipe.layer_version.layer.name}}</a></dd>
+        <dt>Based on</dt>
+        <dd><a href="{% url 'recipedetails' project.id recipe.base_recipe.pk %}">{{recipe.base_recipe.name}}</a></dd>
+        {% if last_build %}
+        <dt>Last build</dt>
+        <dd>
+          <i class="icon-ok-sign success"></i>
+          <a href="{% url 'projectbuilds' project.id%}">{{last_build.completed_on|date:"d/m/y H:i"}}</a>
+        </dd>
+        {% endif %}
         <dt>Recipe file</dt>
         <dd>
-          <code>custom-image-name.bb</code>
-          <a href="#download-file" data-toggle="modal"><i class="icon-download-alt" title="" data-original-title="Download recipe file"></i></a>
-          </dd> -->
+          <code>{{recipe.name}}_{{recipe.version}}.bb</code>
+          <a href="{% url 'customrecipedownload' project.pk recipe.pk %}"><i class="icon-share" title="" data-original-title="View recipe file"></i></a>
+        </dd> 
         <dt>Layer</dt>
-        <!-- TODO recipe details page -->
-        <dd><a href="{% url 'layerdetails' project.id recipe.base_recipe.layer_version.pk %}">{{recipe.base_recipe.layer_version.layer.name}}</a></dd>
-        <!--<dt>
+        <dd><a href="{% url 'layerdetails' project.id recipe.layer_version.pk %}">{{recipe.layer_version.layer.name}}</a></dd>
+        <dt>
           Summary
         </dt>
         <dd>
-          <span class="muted">Not set</span>
-          <i class="icon-pencil" data-original-title="" title=""></i>
+          {{recipe.summary}}
         </dd>
         <dt>
           Description
         </dt>
         <dd>
-          <span class="muted">Not set</span>
-          <i class="icon-pencil" data-original-title="" title=""></i>
+          {{recipe.description}}
         </dd>
         <dt>Version</dt>
         <dd>
-          1.0
-          <i class="icon-pencil" data-original-title="" title=""></i>
+          {{recipe.version}}
         </dd>
         <dt>Section</dt>
         <dd>
-          base
-          <i class="icon-pencil" data-original-title="" title=""></i>
-          <i class="icon-trash" data-original-title="" title=""></i>
+          {{recipe.section}}
         </dd>
         <dt>License</dt>
         <dd>
-          MIT
+          {{recipe.license}}
           <i class="icon-question-sign get-help" title="" data-original-title="All custom images have their license set to MIT. This is because the license applies only to the recipe (.bb) file, and not to the image itself. To see which licenses apply to the image you must check the license manifest generated with each build"></i>
-          </dd> -->
+          </dd>
       </dl>
+      <!--
       <i class="icon-trash no-tooltip"></i>
       <a href="#" class="error" id="delete">Delete custom image</a>
+        -->
     </div>
-</div>
+  </div>
 
   {% endblock %}
diff --git a/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html b/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html
index b766aea..8723d4e 100644
--- a/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html
+++ b/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html
@@ -1,4 +1,4 @@
-<button class="btn btn-block btn-danger add-rm-package-btn" id="package-rm-btn-{{data.pk}}" data-directive="remove" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" style="
+<button class="btn btn-block btn-danger add-rm-package-btn" data-directive="remove" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style="
   {% if data.pk not in extra.current_packages %}
     display:none
   {% endif %}
@@ -6,7 +6,7 @@
   <i class="icon-trash no-tooltip"></i>
   Remove package
 </a>
-<button class="btn btn-block add-rm-package-btn" data-directive="add" id="package-add-btn-{{data.pk}}" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" style="
+<button class="btn btn-block add-rm-package-btn" data-directive="add" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style="
   {% if data.pk in extra.current_packages %}
     display:none
   {% endif %}
-- 
2.1.4



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

* [PATCH 16/34] toaster: newcustomimage Move modal dialog out of newcustomimage template
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (14 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 15/34] toaster: Continue front end features to custom image recipe page Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 17/34] toaster: Add recipe details page Michael Wood
                   ` (18 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Move the modal template and JS out of the newcustomimage page so that it
can also be used by the image details page.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../toaster/toastergui/static/js/newcustomimage.js | 49 ----------------------
 .../toastergui/static/js/newcustomimage_modal.js   | 48 +++++++++++++++++++++
 .../toastergui/templates/newcustomimage.html       | 44 +------------------
 .../toastergui/templates/newcustomimage_modal.html | 33 +++++++++++++++
 4 files changed, 83 insertions(+), 91 deletions(-)
 delete mode 100644 bitbake/lib/toaster/toastergui/static/js/newcustomimage.js
 create mode 100644 bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
 create mode 100644 bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html

diff --git a/bitbake/lib/toaster/toastergui/static/js/newcustomimage.js b/bitbake/lib/toaster/toastergui/static/js/newcustomimage.js
deleted file mode 100644
index 935b21e..0000000
--- a/bitbake/lib/toaster/toastergui/static/js/newcustomimage.js
+++ /dev/null
@@ -1,49 +0,0 @@
-"use strict";
-
-function newCustomImagePageInit(ctx){
-
-  var newCustomImgBtn = $("#create-new-custom-image-btn");
-  var imgCustomModal = $("#new-custom-image-modal");
-
-  newCustomImgBtn.click(function(e){
-    e.preventDefault();
-
-    var name = imgCustomModal.find('input').val();
-    var baseRecipeId = imgCustomModal.data('recipe');
-
-    if (name.length > 0) {
-      createCustomRecipe(name, baseRecipeId);
-      imgCustomModal.modal('hide');
-    } else {
-      console.warn("TODO No name supplied");
-    }
-  });
-
-  function createCustomRecipe(name, baseRecipeId){
-    var data = {
-      'name' : name,
-      'project' : libtoaster.ctx.projectId,
-      'base' : baseRecipeId,
-    };
-
-    $.ajax({
-        type: "POST",
-        url: ctx.xhrCustomRecipeUrl,
-        data: data,
-        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
-        success: function (ret) {
-          if (ret.error !== "ok") {
-            console.warn(ret.error);
-          } else {
-            window.location.replace(ret.url + '?notify=new');
-          }
-        },
-        error: function (ret) {
-          console.warn("Call failed");
-          console.warn(ret);
-        }
-    });
-  }
-
-
-}
diff --git a/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js b/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
new file mode 100644
index 0000000..71a28f7
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
@@ -0,0 +1,48 @@
+"use strict";
+
+/* Used for the newcustomimage_modal actions */
+function newCustomImageModalInit(ctx){
+
+  var newCustomImgBtn = $("#create-new-custom-image-btn");
+  var imgCustomModal = $("#new-custom-image-modal");
+
+  newCustomImgBtn.click(function(e){
+    e.preventDefault();
+
+    var name = imgCustomModal.find('input').val();
+    var baseRecipeId = imgCustomModal.data('recipe');
+
+    if (name.length > 0) {
+      createCustomRecipe(name, baseRecipeId);
+      imgCustomModal.modal('hide');
+    } else {
+      console.warn("TODO No name supplied");
+    }
+  });
+
+  function createCustomRecipe(name, baseRecipeId){
+    var data = {
+      'name' : name,
+      'project' : libtoaster.ctx.projectId,
+      'base' : baseRecipeId,
+    };
+
+    $.ajax({
+        type: "POST",
+        url: ctx.xhrCustomRecipeUrl,
+        data: data,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (ret) {
+          if (ret.error !== "ok") {
+            console.warn(ret.error);
+          } else {
+            window.location.replace(ret.url + '?notify=new');
+          }
+        },
+        error: function (ret) {
+          console.warn("Call failed");
+          console.warn(ret);
+        }
+    });
+  }
+}
diff --git a/bitbake/lib/toaster/toastergui/templates/newcustomimage.html b/bitbake/lib/toaster/toastergui/templates/newcustomimage.html
index 4487b3e..46aed90 100644
--- a/bitbake/lib/toaster/toastergui/templates/newcustomimage.html
+++ b/bitbake/lib/toaster/toastergui/templates/newcustomimage.html
@@ -4,51 +4,11 @@
 {% load static %}
 {% block pagecontent %}
 
-<script src="{% static 'js/newcustomimage.js' %}"></script>
-<script>
-  $(document).ready(function (){
-    var ctx = {
-      xhrCustomRecipeUrl : "{% url 'xhr_customrecipe' %}",
-    };
-
-    try {
-      newCustomImagePageInit(ctx);
-    } catch (e) {
-      document.write("Sorry, An error has occurred loading this page");
-      console.warn(e);
-    }
-  });
-</script>
-
-</script>
-<div class="modal hide fade in" id="new-custom-image-modal" aria-hidden="false">
-  <div class="modal-header">
-    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-    <h3>Name your custom image</h3>
-  </div>
-  <div class="modal-body">
-    <div class="row-fluid">
-      <span class="help-block span8">Image names must be unique. They should not contain spaces or capital letters, and the only allowed special character is dash (-).<p></p>
-      </span></div>
-    <div class="control-group controls">
-      <input type="text" class="huge span5" placeholder="Type the name, something like 'core-image-myimage'">
-        <span class="help-block" style="display:none">Image names cannot contain spaces or capital letters. The only allowed special character is dash (-)</span>
-        <span class="help-block" style="display: none">An image with this name already exists. Image names must be unique: try a different one.</span>
-      </div>
-    </div>
-    <div class="modal-footer">
-      <a href="#" id="create-new-custom-image-btn" class="btn btn-primary btn-large" data-original-title="" title="">Create custom image</a>
-    </div>
-</div>
-
+{% include "newcustomimage_modal.html" %}
 {% include "projecttopbar.html" %}
 
-
 {% url table_name project.id as xhr_table_url %}
+<h2>{{title}} (<span class="table-count-{{table_name}}">0</span>)</h2>
 {% include "toastertable.html" %}
 
-
-
 {% endblock %}
-
-
diff --git a/bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html b/bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html
new file mode 100644
index 0000000..93cf9d7
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html
@@ -0,0 +1,33 @@
+{% load static %}
+
+<script src="{% static 'js/newcustomimage_modal.js' %}"></script>
+<script>
+  $(document).ready(function (){
+    try {
+      newCustomImageModalInit();
+    } catch (e) {
+      document.write("Sorry, An error has occurred loading this page");
+      console.warn(e);
+    }
+  });
+</script>
+
+<div class="modal hide fade in" id="new-custom-image-modal" aria-hidden="false">
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+    <h3>Name your custom image</h3>
+  </div>
+  <div class="modal-body">
+    <div class="row-fluid">
+      <span class="help-block span8">Image names must be unique. They should not contain spaces or capital letters, and the only allowed special character is dash (-).<p></p>
+      </span></div>
+    <div class="control-group controls">
+      <input type="text" class="huge span5" placeholder="Type the name, something like 'core-image-myimage'">
+        <span class="help-block" style="display:none">Image names cannot contain spaces or capital letters. The only allowed special character is dash (-)</span>
+        <span class="help-block" style="display: none">An image with this name already exists. Image names must be unique: try a different one.</span>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <a href="#" id="create-new-custom-image-btn" class="btn btn-primary btn-large" data-original-title="" title="">Create custom image</a>
+    </div>
+</div>
-- 
2.1.4



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

* [PATCH 17/34] toaster: Add recipe details page
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (15 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 16/34] toaster: newcustomimage Move modal dialog out of newcustomimage template Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 18/34] toaster: toastertable remove title from Show all in table Michael Wood
                   ` (17 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

This page provides the details of recipes, showing their packages and
additional metadata. It also allows you to build the recipe or customise
the recipe if it is an image recipe.

[YOCTO #8070]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../toaster/toastergui/static/js/recipedetails.js  |  52 ++++++
 .../toastergui/templates/recipedetails.html        | 180 +++++++++++++++++++++
 2 files changed, 232 insertions(+)
 create mode 100644 bitbake/lib/toaster/toastergui/static/js/recipedetails.js
 create mode 100644 bitbake/lib/toaster/toastergui/templates/recipedetails.html

diff --git a/bitbake/lib/toaster/toastergui/static/js/recipedetails.js b/bitbake/lib/toaster/toastergui/static/js/recipedetails.js
new file mode 100644
index 0000000..2bfd0a4
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/static/js/recipedetails.js
@@ -0,0 +1,52 @@
+"use strict";
+
+function recipeDetailsPageInit(ctx){
+
+  $(".customise-btn").click(function(e){
+    e.preventDefault();
+    var imgCustomModal = $("#new-custom-image-modal");
+
+    if (imgCustomModal.length === 0)
+      throw("Modal new-custom-image not found");
+
+    imgCustomModal.data('recipe', $(this).data('recipe'));
+    imgCustomModal.modal('show');
+  });
+
+  $("#add-layer-btn").click(function(){
+    var btn = $(this);
+
+    libtoaster.addRmLayer(ctx.recipe.layer_version,
+      true,
+      function (layersList){
+        var msg = libtoaster.makeLayerAddRmAlertMsg(ctx.recipe.layer_version,
+          layersList,
+          true);
+
+        libtoaster.showChangeNotification(msg);
+
+        var toShow = $("#customise-build-btns");
+
+        /* If we have no packages built yet also fade in the build packages
+         * hint message
+         */
+        if (ctx.recipe.totalPackages === 0){
+          toShow = toShow.add("#build-to-get-packages-msg");
+        }
+
+        $("#packages-alert").add(btn).fadeOut(function(){
+          toShow.fadeIn();
+        });
+    });
+  });
+
+  /* Trigger a build of your custom image */
+  $(".build-recipe-btn").click(function(){
+    libtoaster.startABuild(libtoaster.ctx.projectBuildsUrl,
+      libtoaster.ctx.projectId,
+      ctx.recipe.name,
+      function(){
+        window.location.replace(libtoaster.ctx.projectBuildsUrl);
+    });
+  });
+}
diff --git a/bitbake/lib/toaster/toastergui/templates/recipedetails.html b/bitbake/lib/toaster/toastergui/templates/recipedetails.html
new file mode 100644
index 0000000..aed0492
--- /dev/null
+++ b/bitbake/lib/toaster/toastergui/templates/recipedetails.html
@@ -0,0 +1,180 @@
+{% extends "base.html" %}
+{% load projecttags %}
+{% load humanize %}
+{% load static %}
+{% block pagecontent %}
+
+<div class="section">
+  <ul class="breadcrumb">
+    <li class="muted">
+      {{project.name}}
+    </li>
+    <li>
+      <a href="{% url 'project' project.id %}">Configuration</a>
+      <span class="divider">&rarr;</span>
+    </li>
+    <li>
+      {% if recipe.is_image %}
+      <a href="{% url 'projectimagerecipes' project.id %}">Image recipes</a>
+      {% else %}
+      <a href="{% url 'projectsoftwarerecipes' project.id %}">Software recipes</a>
+      {% endif %}
+      <span class="divider">&rarr;</span>
+    </li>
+    <li class="active">
+      {{recipe.name}} ({{recipe.layer_version.layer.name}})
+    </li>
+  </ul>
+</div>
+
+<script src="{% static 'js/recipedetails.js' %}"></script>
+<script>
+  $(document).ready(function (){
+    var ctx = {
+      recipe : {
+        id: {{recipe.pk}},
+        name: "{{recipe.name}}",
+        totalPackages: {{packages.count}},
+        layer_version : {
+         id: {{recipe.layer_version.pk}},
+         name: "{{recipe.layer_version.layer.name}}",
+         layerdetailurl: "{% url 'layerdetails' project.pk recipe.layer_version.pk %}"
+        }
+      }
+    };
+
+    try {
+      recipeDetailsPageInit(ctx);
+    } catch (e) {
+      document.write("Sorry, An error has occurred loading this page");
+      console.warn(e);
+    }
+  });
+</script>
+
+{% include 'newcustomimage_modal.html' %}
+
+<div class="row-fluid span11">
+  <div class="alert alert-success lead" id="image-created-notification" style="margin-top: 15px; display: none">
+    <button type="button" data-dismiss="alert" class="close">x</button>
+    Your custom image <strong>{{recipe.name}}</strong> has been created. You can now add or remove packages as needed.
+  </div>
+  <div class="page-header air">
+    <h1>
+      {{recipe.name}}
+      <small>({{recipe.layer_version.layer.name}})</small>
+    </h1>
+  </div>
+</div>
+
+<div class="row-fluid span11">
+  <div class="span8">
+    <div class="button-place btn-group" id="customise-build-btns"
+        style="width: 100%;
+      {% if not in_project %}
+      display:none;
+      {% endif %}">
+      <button class="btn btn-large span6 build-recipe-btn" style="width: 50%">
+        Build {{recipe.name}}
+      </button>
+      {% if recipe.is_image %}
+      <button class="btn btn-large span6 customise-btn" data-recipe="{{recipe.pk}}" style="width: 50%">
+        Customise {{recipe.name}}
+      </button>
+      {% endif %}
+    </div>
+    <div class="button-place">
+      <button class="btn btn-block btn-large" id="add-layer-btn"
+          style="width:100%;
+          {% if in_project %}
+          display:none;
+          {% endif %}">
+        <i class="icon-plus"></i>
+        Add the {{recipe.layer_version.layer.name}} layer to your project to build or customise this image recipe
+      </button>
+    </div>
+
+    <div id="packages-table">
+      {% if  packages.count %}
+       {% url 'recipepackages' project.id recipe.id as xhr_table_url %}
+       <h2>{{title}} (<span class="table-count-{{table_name}}">0</span>) </h2>
+       {% include "toastertable.html" %}
+      {% else %}
+       <h2>{{title}}</h2>
+      {% endif %}
+
+      <div class="alert alert-info air" id="build-to-get-packages-msg"
+      {# if there are packages and it's in the project don't show this msg #}
+      {% if packages.count or not packages.count and not in_project %}
+       style="display:none"
+      {% endif %} >
+        <p class="lead">Toaster has no package information for {{recipe.name}}. To generate package information, build {{recipe.name}}</p>
+        <button class="btn btn-info btn-large build-recipe-btn" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button>
+      </div>
+
+      <div class="alert alert-info air" id="packages-alert"
+      {% if packages.count or  in_project %}
+      style="display:none"
+      {% endif %}
+      >
+        <p class="lead">Toaster has no package information for {{recipe.name}}
+        </p>
+      </div>
+    </div>
+  </div>
+  <div class="span4 well">
+    <h2 style="margin-bottom:20px;">About {{recipe.name}}</h2>
+    <dl>
+      <dt>
+        Approx. packages included
+        <i class="icon-question-sign get-help" title="" data-original-title="The number of packages included is based on information from previous builds and from parsing layers, so we can never be sure it is 100% accurate"></i>
+      </dt>
+      <dd class="no-packages">{{packages.count}}</dd>
+      <dt>
+        Approx. package size
+        <i class="icon-question-sign get-help" title="" data-original-title="Package size is based on information from previous builds, so we can never be sure it is 100% accurate"></i>
+      </dt>
+      <dd>{{approx_pkg_size.size__sum|filtered_filesizeformat}}</dd>
+      {% if last_build %}
+      <dt>Last build</dt>
+      <dd>
+        <i class="icon-ok-sign success"></i>
+        <a href="{% url 'projectbuilds' project.id%}">{{last_build.completed_on|date:"d/m/y H:i"}}</a>
+      </dd>
+      {% endif %}
+      <dt>Recipe file</dt>
+      <dd>
+        <code>{{recipe.file_path|cut_path_prefix:recipe.layer_version.local_path}}</code>
+        <a href="{{recipe.get_vcs_recipe_file_link_url}}"><i class="icon-share" title="" data-original-title="View recipe file"></i></a>
+      </dd> 
+      <dt>Layer</dt>
+      <dd><a href="{% url 'layerdetails' project.id recipe.layer_version.pk %}">{{recipe.layer_version.layer.name}}</a></dd>
+      <dt>
+        Summary
+      </dt>
+      <dd>
+        {{recipe.summary}}
+      </dd>
+      <dt>
+        Description
+      </dt>
+      <dd>
+        {{recipe.description}}
+      </dd>
+      <dt>Version</dt>
+      <dd>
+        {{recipe.version}}
+      </dd>
+      <dt>Section</dt>
+      <dd>
+        {{recipe.section}}
+      </dd>
+      <dt>License</dt>
+      <dd>
+        {{recipe.license}}
+      </dd>
+    </dl>
+  </div>
+</div>
+
+{% endblock %}
-- 
2.1.4



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

* [PATCH 18/34] toaster: toastertable remove title from Show all in table
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (16 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 17/34] toaster: Add recipe details page Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 19/34] toaster: views xhr_customrecipe_packages clean up API Michael Wood
                   ` (16 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Title is often very long so this is not a great property to use here.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/templates/toastertable.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bitbake/lib/toaster/toastergui/templates/toastertable.html b/bitbake/lib/toaster/toastergui/templates/toastertable.html
index 98a715f..9176db7 100644
--- a/bitbake/lib/toaster/toastergui/templates/toastertable.html
+++ b/bitbake/lib/toaster/toastergui/templates/toastertable.html
@@ -33,7 +33,7 @@
         <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 class="btn btn-link remove-search-btn-{{table_name}}">Show all
       </button>
     </form>
   </div>
-- 
2.1.4



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

* [PATCH 19/34] toaster: views xhr_customrecipe_packages clean up API
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (17 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 18/34] toaster: toastertable remove title from Show all in table Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 20/34] toaster: toastergui tests Update to reflect changes to CustomImageRecipe Michael Wood
                   ` (15 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

- Fix generic variable names such as "object" and "values" when not
  needed.
- Use try catch instead of a queryset filter to return the custom recipe
  object
- Be explicit about the fields returned for the custom recipe info field
- Remove redundant new_package field

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/views.py | 27 +++++++++++++--------------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 53553a5..d569ffe 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2731,20 +2731,23 @@ if True:
             or
             {"error": <error message>}
         """
-        objects = CustomImageRecipe.objects.filter(id=recipe_id)
-        if not objects:
+        try:
+          custom_recipe = CustomImageRecipe.objects.get(id=recipe_id)
+        except CustomImageRecipe.DoesNotExist:
             return {"error": "Custom recipe with id=%s "
                              "not found" % recipe_id}
+
         if request.method == 'GET':
-            values = CustomImageRecipe.objects.filter(id=recipe_id).values()
-            if values:
-                return {"error": "ok", "info": values[0]}
-            else:
-                return {"error": "Custom recipe with id=%s "
-                                 "not found" % recipe_id}
-            return {"error": "ok", "info": objects.values()[0]}
+            info = {"id" : custom_recipe.id,
+                    "name" : custom_recipe.name,
+                    "base_recipe_id": custom_recipe.base_recipe.id,
+                    "project_id": custom_recipe.project.id,
+                   }
+
+            return {"error": "ok", "info": info}
+
         elif request.method == 'DELETE':
-            objects.delete()
+            custom_recipe.delete()
             return {"error": "ok"}
         else:
             return {"error": "Method %s is not supported" % request.method}
@@ -2820,10 +2823,6 @@ if True:
 
             dependencies = filter(in_image, dependencies['runtime_deps'])
             return {"error": "ok",
-                    "new_package" : {"id": package.pk,
-                                     "url": reverse('xhr_customrecipe_packages',
-                                                 args=(recipe.pk, package.pk))
-                                    },
                     "dependencies_needed" : dependencies,
                    }
 
-- 
2.1.4



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

* [PATCH 20/34] toaster: toastergui tests Update to reflect changes to CustomImageRecipe
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (18 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 19/34] toaster: views xhr_customrecipe_packages clean up API Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 21/34] toaster: toastergui tests Add unit test for download custom recipe Michael Wood
                   ` (14 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Now that CustomImageRecipe inherits from Recipe make sure that the
accessors and the required values for Recipe are now setup correctly.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tests.py | 41 +++++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 12 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/tests.py b/bitbake/lib/toaster/toastergui/tests.py
index c927fe1..f31be81 100644
--- a/bitbake/lib/toaster/toastergui/tests.py
+++ b/bitbake/lib/toaster/toastergui/tests.py
@@ -115,10 +115,16 @@ class ViewTests(TestCase):
 
         ProjectLayer.objects.create(project=self.project, layercommit=self.lver)
 
+        lver_custom = Layer_Version.objects.create(layer=layer,
+                                                   project=self.project,
+                                                   layer_source=layersrc,
+                                                   commit="mymaster",
+                                                   up_branch=branch)
 
         self.customr = CustomImageRecipe.objects.create(\
                            name="custom recipe", project=self.project,
-                           base_recipe=self.recipe1)
+                           base_recipe=self.recipe1,
+                           layer_version=lver_custom)
 
         CustomImageRecipe.objects.create(name="z custom recipe",
                                          project=self.project,
@@ -349,7 +355,9 @@ class ViewTests(TestCase):
         name = "to be deleted"
         recipe = CustomImageRecipe.objects.create(\
                      name=name, project=self.project,
-                     base_recipe=self.recipe1)
+                     base_recipe=self.recipe1,
+                     file_path="/tmp/testing",
+                     layer_version=self.customr.layer_version)
         url = reverse('xhr_customrecipe_id', args=(recipe.id,))
         response = self.client.delete(url)
         self.assertEqual(response.status_code, 200)
@@ -362,20 +370,28 @@ class ViewTests(TestCase):
 
     def test_xhr_custom_packages(self):
         """Test adding and deleting package to a custom recipe"""
-        url = reverse('xhr_customrecipe_packages',
-                      args=(self.customr.id, self.package.id))
-        # add self.package1 to recipe
-        response = self.client.put(url)
+        # add self.package to recipe
+        response = self.client.put(reverse('xhr_customrecipe_packages',
+                                           args=(self.customr.id,
+                                                 self.package.id)))
+
         self.assertEqual(response.status_code, 200)
-        self.assertEqual(json.loads(response.content), {"error": "ok"})
-        self.assertEqual(self.customr.packages.all()[0].id, self.package.id)
+        self.assertEqual(json.loads(response.content),
+                         {"error": "ok",
+                          "dependencies_needed": []})
+        self.assertEqual(self.customr.package_set.first().name,
+                         self.package.name)
         # delete it
-        response = self.client.delete(url)
+        del_url = reverse('xhr_customrecipe_packages',
+                          args=(self.customr.id,
+                                self.customr.package_set.first().id))
+
+        response = self.client.delete(del_url)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(json.loads(response.content), {"error": "ok"})
-        self.assertFalse(self.customr.packages.all())
+        self.assertFalse(self.customr.package_set.all())
         # delete it again to test error condition
-        response = self.client.delete(url)
+        response = self.client.delete(del_url)
         self.assertEqual(response.status_code, 200)
         self.assertNotEqual(json.loads(response.content)["error"], "ok")
 
@@ -403,7 +419,8 @@ class ViewTests(TestCase):
         row2 = next(x for x in rows if x['name'] == self.recipe2.name)
 
         self.assertEqual(response.status_code, 200, 'should be 200 OK status')
-        self.assertTrue(row2, 'should be 2 recipes')
+        # self.recipe1 + self.recipe2 + self.customr = 3
+        self.assertEqual(len(rows), 3, 'should be 3 recipes')
 
         # check other columns have been populated correctly
         self.assertEqual(row1['name'], self.recipe1.name)
-- 
2.1.4



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

* [PATCH 21/34] toaster: toastergui tests Add unit test for download custom recipe
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (19 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 20/34] toaster: toastergui tests Update to reflect changes to CustomImageRecipe Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 22/34] toaster: orm get_project_layer_versions to return layer_version objects Michael Wood
                   ` (13 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Add unit test to test downloading end point and the basic content of the
custom recipe that is generated.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tests.py | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/bitbake/lib/toaster/toastergui/tests.py b/bitbake/lib/toaster/toastergui/tests.py
index f31be81..3683b51 100644
--- a/bitbake/lib/toaster/toastergui/tests.py
+++ b/bitbake/lib/toaster/toastergui/tests.py
@@ -79,6 +79,7 @@ class ViewTests(TestCase):
                                                  project=self.project,
                                                  layer_source=layersrc,
                                                  commit="master",
+                                                 dirpath="/tmp/",
                                                  up_branch=branch)
 
         lver_two = Layer_Version.objects.create(layer=layer_two,
@@ -95,6 +96,8 @@ class ViewTests(TestCase):
                               section="h section",
                               layer_version=lver_two)
 
+        # Create a dummy recipe file for the custom image generation to read
+        open("/tmp/my_recipe.bb", 'wa').close()
         self.recipe1 = Recipe.objects.create(layer_source=layersrc,
                                              name="base-recipe",
                                              version="1.2",
@@ -102,7 +105,8 @@ class ViewTests(TestCase):
                                              description="recipe",
                                              section="A section",
                                              license="Apache",
-                                             layer_version=self.lver)
+                                             layer_version=self.lver,
+                                             file_path="my_recipe.bb")
 
         Machine.objects.create(layer_version=self.lver, name="wisk",
                                description="wisking machine")
@@ -407,6 +411,14 @@ class ViewTests(TestCase):
                 self.assertNotEqual(json.loads(response.content),
                                     {"error": "ok"})
 
+    def test_download_custom_recipe(self):
+        response = self.client.get(reverse('customrecipedownload',
+                                           args=(self.project.id,
+                                                 self.customr.id)))
+
+        self.assertEqual(response.status_code, 200)
+
+
     def test_software_recipes_table(self):
         """Test structure returned for Software RecipesTable"""
         table = SoftwareRecipesTable()
-- 
2.1.4



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

* [PATCH 22/34] toaster: orm get_project_layer_versions to return layer_version objects
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (20 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 21/34] toaster: toastergui tests Add unit test for download custom recipe Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 23/34] toaster: orm Add convenience method to get all pkgs in a CustomImageRecipe Michael Wood
                   ` (12 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Instead of returning layercommits return the actual Layer_Version objects
for the layercommit as these are the useful objects which contain the
metadata.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/orm/models.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index b4cfe24..3c64212 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -216,12 +216,13 @@ class Project(models.Model):
 
     def get_project_layer_versions(self, pk=False):
         """ Returns the Layer_Versions currently added to this project """
-        layer_versions = self.projectlayer_set.all().values('layercommit')
+        layer_versions = self.projectlayer_set.all().values_list('layercommit',
+                                                                 flat=True)
 
         if pk is False:
-            return layer_versions
+            return Layer_Version.objects.filter(pk__in=layer_versions)
         else:
-            return layer_versions.values_list('layercommit__pk', flat=True)
+            return layer_versions
 
 
     def get_available_machines(self):
-- 
2.1.4



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

* [PATCH 23/34] toaster: orm Add convenience method to get all pkgs in a CustomImageRecipe
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (21 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 22/34] toaster: orm get_project_layer_versions to return layer_version objects Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 24/34] toaster: libtoaster Add createCustomRecipe method Michael Wood
                   ` (11 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Returns a queryset of the all the packages that we expect to have in a
CustomImageRecipe.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/orm/models.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 3c64212..b292cd9 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -1265,6 +1265,12 @@ class CustomImageRecipe(Recipe):
     base_recipe = models.ForeignKey(Recipe, related_name='based_on_recipe')
     project = models.ForeignKey(Project)
 
+    def get_all_packages(self):
+        """Get the included packages and any appended packages"""
+        return ProjectPackage.objects.filter((Q(recipe_appends=self) |
+                                              Q(recipe_includes=self)) &
+                                             ~Q(recipe_excludes=self))
+
     def generate_recipe_file_contents(self):
         """Generate the contents for the recipe file."""
         # If we have no excluded packages we only need to _append
-- 
2.1.4



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

* [PATCH 24/34] toaster: libtoaster Add createCustomRecipe method
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (22 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 23/34] toaster: orm Add convenience method to get all pkgs in a CustomImageRecipe Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 25/34] toaster: newcustomimage_modal use libtoaster method for new CustomRecipe Michael Wood
                   ` (10 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

This adds the function to call the ReSt API to create a custom image
recipe.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../lib/toaster/toastergui/static/js/libtoaster.js | 27 ++++++++++++++++++++++
 bitbake/lib/toaster/toastergui/templates/base.html |  1 +
 2 files changed, 28 insertions(+)

diff --git a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
index c04f7ab..d7f0394 100644
--- a/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
@@ -332,6 +332,32 @@ var libtoaster = (function (){
     $("#change-notification, #change-notification *").fadeIn();
   }
 
+  function _createCustomRecipe(name, baseRecipeId, doneCb){
+    var data = {
+      'name' : name,
+      'project' : libtoaster.ctx.projectId,
+      'base' : baseRecipeId,
+    };
+
+    $.ajax({
+        type: "POST",
+        url: libtoaster.ctx.xhrCustomRecipeUrl,
+        data: data,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function (ret) {
+          if (doneCb){
+            doneCb(ret);
+          } else if (ret.error !== "ok") {
+            console.warn(ret.error);
+          }
+        },
+        error: function (ret) {
+          console.warn("Call failed");
+          console.warn(ret);
+        }
+    });
+  }
+
 
   return {
     reload_params : reload_params,
@@ -347,6 +373,7 @@ var libtoaster = (function (){
     addRmLayer : _addRmLayer,
     makeLayerAddRmAlertMsg : _makeLayerAddRmAlertMsg,
     showChangeNotification : _showChangeNotification,
+    createCustomRecipe: _createCustomRecipe,
   };
 })();
 
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html
index e56bb34..6994bcc 100644
--- a/bitbake/lib/toaster/toastergui/templates/base.html
+++ b/bitbake/lib/toaster/toastergui/templates/base.html
@@ -45,6 +45,7 @@
         machinesTypeAheadUrl: {% url 'xhr_machinestypeahead' project.id as paturl%}{{paturl|json}},
 
         projectBuildsUrl: {% url 'projectbuilds' project.id as pburl %}{{pburl|json}},
+        xhrCustomRecipeUrl : "{% url 'xhr_customrecipe' %}",
         projectId : {{project.id}},
         {% else %}
         projectId : undefined,
-- 
2.1.4



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

* [PATCH 25/34] toaster: newcustomimage_modal use libtoaster method for new CustomRecipe
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (23 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 24/34] toaster: libtoaster Add createCustomRecipe method Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 26/34] toaster: tables add recipe download link to CustomImagesTable Michael Wood
                   ` (9 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Use libtoaster.createCustomRecipe rather than own implementation of this
function.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../toastergui/static/js/newcustomimage_modal.js   | 36 +++++-----------------
 1 file changed, 8 insertions(+), 28 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js b/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
index 71a28f7..16e42b3 100644
--- a/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
+++ b/bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
@@ -1,7 +1,7 @@
 "use strict";
 
 /* Used for the newcustomimage_modal actions */
-function newCustomImageModalInit(ctx){
+function newCustomImageModalInit(){
 
   var newCustomImgBtn = $("#create-new-custom-image-btn");
   var imgCustomModal = $("#new-custom-image-modal");
@@ -13,36 +13,16 @@ function newCustomImageModalInit(ctx){
     var baseRecipeId = imgCustomModal.data('recipe');
 
     if (name.length > 0) {
-      createCustomRecipe(name, baseRecipeId);
       imgCustomModal.modal('hide');
+      libtoaster.createCustomRecipe(name, baseRecipeId, function(ret) {
+        if (ret.error !== "ok") {
+          console.warn(ret.error);
+        } else {
+          window.location.replace(ret.url + '?notify=new');
+        }
+      });
     } else {
       console.warn("TODO No name supplied");
     }
   });
-
-  function createCustomRecipe(name, baseRecipeId){
-    var data = {
-      'name' : name,
-      'project' : libtoaster.ctx.projectId,
-      'base' : baseRecipeId,
-    };
-
-    $.ajax({
-        type: "POST",
-        url: ctx.xhrCustomRecipeUrl,
-        data: data,
-        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
-        success: function (ret) {
-          if (ret.error !== "ok") {
-            console.warn(ret.error);
-          } else {
-            window.location.replace(ret.url + '?notify=new');
-          }
-        },
-        error: function (ret) {
-          console.warn("Call failed");
-          console.warn(ret);
-        }
-    });
-  }
 }
-- 
2.1.4



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

* [PATCH 26/34] toaster: tables add recipe download link to CustomImagesTable
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (24 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 25/34] toaster: newcustomimage_modal use libtoaster method for new CustomRecipe Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 27/34] toaster: tables Change SelectPackagesTable to use ProjectPackage Michael Wood
                   ` (8 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Add the download recipe link and fix the package count field.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tables.py | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 7ce6651..8147c1b 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -464,14 +464,20 @@ class CustomImagesTable(ToasterTable):
                         static_data_name="name",
                         static_data_template=name_link_template)
 
+        recipe_file_template = '''
+        <code>{{data.name}}_{{data.version}}.bb</code>
+        <a href="{% url 'customrecipedownload' extra.pid data.pk %}">
+        <i class="icon-download-alt" data-original-title="Download recipe
+        file"></i>
+        </a>'''
+
         self.add_column(title="Recipe file",
-                        static_data_name='recipe_file',
-                        static_data_template='',
-                        field_name='local_path')
+                        static_data_name='recipe_file_download',
+                        static_data_template=recipe_file_template)
 
         approx_packages_template = '''
         <a href="{% url 'customrecipe' extra.pid data.id %}">
-          {{data.package_set.all|length}}
+          {{data.get_all_packages.count}}
         </a>'''
 
         self.add_column(title="Approx packages",
-- 
2.1.4



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

* [PATCH 27/34] toaster: tables Change SelectPackagesTable to use ProjectPackage
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (25 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 26/34] toaster: tables add recipe download link to CustomImagesTable Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 28/34] toaster: API allow CustomImageRecipe to be updated after creation Michael Wood
                   ` (7 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

This changes the SelectPackagesTable to use the ProjectPackage table
instead of very large expensive queries to retrieve a list of currently
available packages for the project.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tables.py | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 8147c1b..9476887 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -22,6 +22,7 @@
 from toastergui.widgets import ToasterTable
 from orm.models import Recipe, ProjectLayer, Layer_Version, Machine, Project
 from orm.models import CustomImageRecipe, Package, Target, Build
+from orm.models import ProjectPackage
 from django.db.models import Q, Max, Sum
 from django.conf.urls import url
 from django.core.urlresolvers import reverse
@@ -677,7 +678,7 @@ class PackagesTable(ToasterTable):
 
 
 class SelectPackagesTable(PackagesTable):
-    """ Table to display the packages to add and remove from an image """
+    """ Table to display packages to add and remove from a custom recipe"""
 
     def __init__(self, *args, **kwargs):
         super(SelectPackagesTable, self).__init__(*args, **kwargs)
@@ -687,15 +688,20 @@ class SelectPackagesTable(PackagesTable):
         cust_recipe = CustomImageRecipe.objects.get(pk=kwargs['recipeid'])
         prj = Project.objects.get(pk = kwargs['pid'])
 
-        current_packages = cust_recipe.packages.all()
+        current_packages = self.cust_recipe.get_all_packages()
 
-        # Get all the packages that are in the custom image
-        # Get all the packages built by builds in the current project
-        # but not those ones that are already in the custom image
-        self.queryset = Package.objects.filter(
-                            Q(pk__in=current_packages) |
-                            (Q(build__project=prj) &
-                            ~Q(name__in=current_packages.values_list('name'))))
+        current_recipes = prj.get_available_recipes()
+
+        # Exclude ghost packages and ones which have locale in the name
+        # This is a work around locale packages being dynamically created
+        # and therefore not recognised as packages by bitbake.
+        # We also only show packages which recipes->layers are in the project
+        self.queryset = ProjectPackage.objects.filter(
+                Q(project=prj) &
+                ~Q(recipe=None) &
+                Q(recipe__in=current_recipes) &
+                ~Q(name__icontains="locale") &
+                ~Q(name__icontains="packagegroup"))
 
         self.queryset = self.queryset.order_by('name')
 
@@ -708,7 +714,8 @@ class SelectPackagesTable(PackagesTable):
         custom_recipe = CustomImageRecipe.objects.get(pk=kwargs['recipe_id'])
 
         context['recipe'] = custom_recipe
-        context['approx_pkg_size'] =  custom_recipe.package_set.aggregate(Sum('size'))
+        context['approx_pkg_size'] = \
+                        custom_recipe.get_all_packages().aggregate(Sum('size'))
         return context
 
 
-- 
2.1.4



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

* [PATCH 28/34] toaster: API allow CustomImageRecipe to be updated after creation
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (26 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 27/34] toaster: tables Change SelectPackagesTable to use ProjectPackage Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 29/34] toaster: xhr_customrecipe_id change to use ProjectPackage Michael Wood
                   ` (6 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

When we create a CustomImageRecipe we create a Layer_Version and
Recipe for that Recipe to be in, we only need one Layer_Version for our
Recipes so if that Layer_Version is updated by building it we need
a slightly more custom version of get_or_create to take into account the
fields which we expect can change but still mean that the object we want is
valid and doesn't need to be created.

In the Recipe case this is when we're updating an existing
CustomImageRecipe as we allow people to create a recipe even when the
based on recipe hasn't been built so we need to update it once a build
has happened.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/views.py | 56 ++++++++++++++++++++-------------
 1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index d569ffe..270229f 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2650,20 +2650,27 @@ if True:
         # create custom recipe
         try:
             # create layer 'Custom layer' and verion if needed
-            layer = Layer.objects.get_or_create(name="toaster-custom-images",
-                                         summary="Layer for custom recipes",
-                                         vcs_url="file:///toaster_created_layer"
-                                         )[0]
-
-            lver = Layer_Version.objects.get_or_create(
-                project=params['project'],
-                layer=layer,
-                dirpath='toaster_created_layer',
-                build=None)[0]
+            layer = Layer.objects.get_or_create(
+                name="toaster-custom-images",
+                summary="Layer for custom recipes",
+                vcs_url="file:///toaster_created_layer")[0]
+
+            # Check if we have a layer version already
+            # We don't use get_or_create here because the dirpath will change
+            # and is a required field
+            lver = Layer_Version.objects.filter(Q(project=params['project']) &
+                                                Q(layer=layer) &
+                                                Q(build=None)).last()
+            if lver == None:
+                lver, created = Layer_Version.objects.get_or_create(
+                    project=params['project'],
+                    layer=layer,
+                    dirpath="toaster_created_layer")
 
             # Add a dependency on our layer to the base recipe's layer
-            LayerVersionDependency.objects.get_or_create(layer_version=lver,
-                                       depends_on=params["base"].layer_version)
+            LayerVersionDependency.objects.get_or_create(
+                layer_version=lver,
+                depends_on=params["base"].layer_version)
 
             # Add it to our current project if needed
             ProjectLayer.objects.get_or_create(project=params['project'],
@@ -2671,14 +2678,21 @@ if True:
                                                optional=False)
 
             # Create the actual recipe
-            recipe = CustomImageRecipe.objects.create(
-                         name=request.POST["name"],
-                         base_recipe=params["base"],
-                         project=params["project"],
-                         file_path=request.POST["name"],
-                         license="MIT",
-                         version="0.1",
-                         layer_version=lver)
+            recipe, created = CustomImageRecipe.objects.get_or_create(
+                name=request.POST["name"],
+                base_recipe=params["base"],
+                project=params["project"],
+                layer_version=lver,
+                is_image=True)
+
+            # If we created the object then setup these fields. They may get
+            # overwritten later on and cause the get_or_create to create a
+            # duplicate if they've changed.
+            if created:
+                recipe.file_path = request.POST["name"]
+                recipe.license = "MIT"
+                recipe.version = "0.1"
+                recipe.save()
 
         except Error as err:
             return {"error": "Can't create custom recipe: %s" % err}
@@ -2732,7 +2746,7 @@ if True:
             {"error": <error message>}
         """
         try:
-          custom_recipe = CustomImageRecipe.objects.get(id=recipe_id)
+            custom_recipe = CustomImageRecipe.objects.get(id=recipe_id)
         except CustomImageRecipe.DoesNotExist:
             return {"error": "Custom recipe with id=%s "
                              "not found" % recipe_id}
-- 
2.1.4



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

* [PATCH 29/34] toaster: xhr_customrecipe_id change to use ProjectPackage
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (27 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 28/34] toaster: API allow CustomImageRecipe to be updated after creation Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 30/34] toaster: xhr_customrecipe_packages add GET info for package response Michael Wood
                   ` (5 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Instead of doing a shallow copy of the package into the
CustomImageRecipe when we add packages we can now use the ProjectPackage
as a M2M field on the Package to CustomImageRecipe. Also switch to using
Target_Installed_Package as the method to retrieve the package list from
the build.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/views.py | 54 ++++++++++++++++++++++-----------
 1 file changed, 36 insertions(+), 18 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 270229f..50d5afe 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -30,7 +30,7 @@ from django.db import IntegrityError, Error
 from django.shortcuts import render, redirect, get_object_or_404
 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 orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact, ProjectPackage
 from orm.models import BitbakeVersion, CustomImageRecipe
 from bldcontrol import bbcontroller
 from django.views.decorators.cache import cache_control
@@ -2698,31 +2698,49 @@ if True:
             return {"error": "Can't create custom recipe: %s" % err}
 
         # Find the package list from the last build of this recipe/target
-        build = Build.objects.filter(target__target=params['base'].name,
-                    project=params['project']).last()
-
-        if build:
+        target = Target.objects.filter(Q(build__outcome=Build.SUCCEEDED) &
+                                       Q(build__project=params['project']) &
+                                       (Q(target=params['base'].name) |
+                                        Q(target=recipe.name))).last()
+        if target:
             # Copy in every package
             # We don't want these packages to be linked to anything because
             # that underlying data may change e.g. delete a build
-            for package in build.package_set.all():
-                 _copy_packge_to_recipe(recipe, package)
-        else:
-            logger.debug("No packages found for this base recipe")
+            for tpackage in target.target_installed_package_set.all():
+                try:
+                    built_package = tpackage.package
+                    # The package had no recipe information so is a ghost
+                    # package skip it
+                    if built_package.recipe == None:
+                        continue;
+
+                    config_package = ProjectPackage.objects.get(
+                        name=built_package.name,
+                        version=built_package.version,
+                        project=params['project'])
+
+                    recipe.includes_set.add(config_package)
+                except Exception as e:
+                    logger.warning("Error adding package %s %s" %
+                                   (tpackage.package.name, e))
+                    pass
 
         return {"error": "ok",
+                "packages" : recipe.get_all_packages().count(),
                 "url": reverse('customrecipe', args=(params['project'].pk,
                                                      recipe.id))}
 
-    def _copy_packge_to_recipe(recipe, package):
-        """ copy a package from another recipe """
-        package.pk = None
-        package.save()
-        # Disassociate the package from the build
-        package.build = None
-        package.recipe = recipe
-        package.save()
-        return package
+    def _get_config_packge(built_package, project):
+        """ Find a package from the project configuration data based on a built
+        package (i.e. one produced from a build)
+        """
+        if built_package.recipe == None:
+            raise Exception("The package had no recipe information so it has "
+                            "been skipped as it appears to be a ghost package")
+        # Find the package from the configuration data
+        return ProjectPackage.objects.get(name=built_package.name,
+                                          version=built_package.version,
+                                          project=project)
 
     @xhr_response
     def xhr_customrecipe_id(request, recipe_id):
-- 
2.1.4



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

* [PATCH 30/34] toaster: xhr_customrecipe_packages add GET info for package response
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (28 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 29/34] toaster: xhr_customrecipe_id change to use ProjectPackage Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 31/34] toaster: customrecipe Add further front end features using new API Michael Wood
                   ` (4 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Add response for GET to the xhr_customrecipe_packages ReST API

/xhr_customrecipe/<recipe_id>/packages/<package_id>
Thie response includes the id, name, version and dependency information
for the package.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/views.py | 141 ++++++++++++++++++++++----------
 1 file changed, 99 insertions(+), 42 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 50d5afe..37a182a 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -2802,11 +2802,12 @@ if True:
         """
         ReST API to add/remove packages to/from custom recipe.
 
-        Entry point: /xhr_customrecipe/<recipe_id>/packages/
+        Entry point: /xhr_customrecipe/<recipe_id>/packages/<package_id>
 
         Methods:
             PUT - Add package to the recipe
             DELETE - Delete package from the recipe
+            GET - Get package information
 
         Returns:
             {"error": "ok"}
@@ -2819,53 +2820,109 @@ if True:
             return {"error": "Custom recipe with id=%s "
                              "not found" % recipe_id}
 
-        if request.method == 'GET' and not package_id:
-            packages = recipe.package_set.values("id", "name", "version")
-
-            return {"error": "ok",
-                    "packages" : list(packages),
-                    "total" : len(packages)
-                   }
+        if package_id:
+            try:
+                package = ProjectPackage.objects.get(id=package_id)
+            except Package.DoesNotExist:
+                return {"error": "Package with id=%s "
+                        "not found" % package_id}
 
-        try:
-            package = Package.objects.get(id=package_id)
-        except Package.DoesNotExist:
-            return {"error": "Package with id=%s "
-                             "not found" % package_id}
+        if request.method == 'GET':
+            if not package_id:
+                packages = recipe.get_all_packages().values("id",
+                                                            "name",
+                                                            "version")
+
+                return {"error": "ok",
+                        "packages" : list(packages),
+                        "total" : len(packages)
+                       }
+            else:
+                all_current_packages = recipe.get_all_packages()
+                # TODO Django 1.8 will allow us to map these to nicer keys
+                # using annotate(myfieldname=F(field__name
+
+                # TODO currently we ignore packgegroups as we don't have a
+                # way to deal with them yet.
+
+                # Dependencies for package which aren't satisfied by the
+                # current packages in the custom image recipe
+                deps = package.package_dependencies_source.values(
+                    "depends_on__name",
+                    "depends_on__pk",
+                    "depends_on__size").exclude(
+                        Q(depends_on__in=all_current_packages) |
+                        Q(package__name__icontains="packagegroup")
+                    )
+
+                # Reverse dependencies which are needed by packages that are
+                # in the image
+                reverse_deps = package.package_dependencies_target.values(
+                    "package__name",
+                    "package__pk",
+                    "package__size").exclude(
+                        ~Q(package__pk__in=all_current_packages) |
+                        Q(package__name__icontains="packagegroup")
+                    )
+
+                total_size_deps = 0
+                total_size_reverse_deps = 0
+
+                for dep in deps:
+                    dep['depends_on__size_formatted'] = \
+                            filtered_filesizeformat(dep['depends_on__size'])
+                    total_size_deps += dep['depends_on__size']
+
+                for dep in reverse_deps:
+                    dep['package__size_formatted'] = \
+                            filtered_filesizeformat(dep['package__size'])
+                    total_size_reverse_deps += dep['package__size']
+
+
+                return {"error": "ok",
+                        "id": package.pk,
+                        "name": package.name,
+                        "version": package.version,
+                        "unsatisfied_dependencies": list(deps),
+                        "unsatisfied_dependencies_size": total_size_deps,
+                        "unsatisfied_dependencies_size_formatted":
+                        filtered_filesizeformat(total_size_deps),
+                        "reverse_dependencies": list(reverse_deps),
+                        "reverse_dependencies_size": total_size_reverse_deps,
+                        "reverse_dependencies_size_formatted":
+                        filtered_filesizeformat(total_size_reverse_deps)}
+
+        included_packages = recipe.includes_set.values_list('pk', flat=True)
 
         if request.method == 'PUT':
-            # As these requests are asynchronous we need to make sure we don't
-            # already have the package in the image recipe
-            if recipe.package_set.filter(Q(name=package.name) &
-                                      Q(version=package.version)).count() > 0:
-                return {"error" : "Package %s already in recipe" %
-                        package.name }
-
-            # Make a copy of this package
-            dependencies = _get_package_dependencies(package.pk)
-
-            package = _copy_packge_to_recipe(recipe, package)
-            recipe.package_set.add(package)
-
-            # Filter out dependencies already in the custom image
-            all_in_image = recipe.package_set.all().values_list('name',
-                                                                flat=True)
-            def in_image(pkg):
-                return pkg['name'] not in all_in_image
-
-            dependencies = filter(in_image, dependencies['runtime_deps'])
-            return {"error": "ok",
-                    "dependencies_needed" : dependencies,
-                   }
+            # If we're adding back a package which used to be included in this
+            # image all we need to do is remove it from the excludes
+            if package.pk in included_packages:
+                try:
+                   recipe.excludes_set.remove(package)
+                   return {"error": "ok"}
+                except Package.DoesNotExist:
+                   return {"error":
+                           "Package %s not found in excludes but was in "
+                           "included list" % package.name}
+
+            else:
+                recipe.appends_set.add(package)
+
+            return {"error": "ok"}
 
         elif request.method == 'DELETE':
-            if package in recipe.package_set.all():
-                # note that we are infact deleting the copy of the package
-                package.delete()
+            try:
+                # If we're deleting a package which is included we need to
+                # Add it to the excludes list.
+                if package.pk in included_packages:
+                    recipe.excludes_set.add(package)
+                else:
+                    recipe.appends_set.remove(package)
                 return {"error": "ok"}
-            else:
-                return {"error": "Package '%s' is not in the recipe '%s'" % \
-                                 (package.name, recipe.name)}
+            except CustomImageRecipe.DoesNotExist:
+                return {"error": "Tried to remove package that wasn't present"}
+
         else:
             return {"error": "Method %s is not supported" % request.method}
 
-- 
2.1.4



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

* [PATCH 31/34] toaster: customrecipe Add further front end features using new API
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (29 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 30/34] toaster: xhr_customrecipe_packages add GET info for package response Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 32/34] toaster: toastergui tests Update package test to use ProjectPackage Michael Wood
                   ` (3 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

This adds some basic package dependency hint modals when you add and
remove a package. It also makes sure that if the CustomImageRecipe has
no current included packages that we go and check this with the server
to see if a relevant build has taken place which will provide this
information.

[YOCTO #8082]

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../toaster/toastergui/static/js/customrecipe.js   | 148 +++++++++++++++++++--
 .../toaster/toastergui/templates/customrecipe.html |  46 ++++++-
 .../toastergui/templates/pkg_add_rm_btn.html       |  35 ++---
 3 files changed, 196 insertions(+), 33 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/static/js/customrecipe.js b/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
index 4cd9382..2d43ec2 100644
--- a/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
+++ b/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
@@ -4,32 +4,145 @@ function customRecipePageInit(ctx) {
 
   var urlParams = libtoaster.parseUrlParams();
   var customiseTable = $("#selectpackagestable");
+  var addPkgDepsModalBtn = $("#add-package-deps-modal-btn");
+  var rmdPkgReverseDepsModalBtn = $("#rm-package-reverse-deps-modal-btn");
 
-  (function notificationRequest(){
-    if (urlParams.hasOwnProperty('notify') && urlParams.notify === 'new'){
-      $("#image-created-notification").show();
-    }
-  })();
+  if (urlParams.hasOwnProperty('notify') && urlParams.notify === 'new'){
+    $("#image-created-notification").show();
+  }
+
+  /* If the total packages included in this image is 0 we need to
+   * try updating this recipe
+   */
+  if (ctx.recipe.includedPackagesCount === 0){
+    libtoaster.createCustomRecipe(ctx.recipe.name,
+      ctx.recipe.baseRecipeId,
+      function(ret){
+        /* TODO rather than reload the page just show the table and hide err */
+        if (ret.packages > 0){
+          window.location.reload();
+        } else {
+          console.warn("Tried again and no packages found for this recipe");
+        }
+    });
+  }
 
-  customiseTable.on('table-done', function(e, total, tableParams){
+  customiseTable.on('table-done', function(e, total){
     /* Table is done so now setup the click handler for the package buttons */
     $(".add-rm-package-btn").click(function(e){
       e.preventDefault();
-      addRemovePackage($(this), tableParams);
+      var pkgBtnData = $(this).data();
+
+       checkPackageDeps(pkgBtnData, function(pkgData){
+         if (pkgBtnData.directive === 'add'){
+           /* If we're adding a package we may need to show the modal to advise
+            * on dependencies for this package.
+            */
+           if (pkgData.unsatisfied_dependencies.length === 0){
+             addRemovePackage(pkgBtnData);
+           } else {
+             showPackageDepsModal(pkgBtnData, pkgData);
+           }
+         } else if (pkgBtnData.directive === 'remove') {
+           if (pkgData.reverse_dependencies.length === 0){
+             addRemovePackage(pkgBtnData);
+           } else {
+             showPackageReverseDepsModal(pkgBtnData, pkgData);
+           }
+           }
+        });
+    });
+  });
+
+  function checkPackageDeps(pkgBtnData, doneCb){
+    $.ajax({
+        type: 'GET',
+        url: pkgBtnData.packageUrl,
+        headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
+        success: function(data){
+          if (data.error !== 'ok'){
+            console.warn(data.error);
+            return;
+          }
+          doneCb(data);
+        }
     });
+  }
+
+  function showPackageDepsModal(pkgBtnData, pkgData){
+    var modal = $("#package-deps-modal");
+    var depsList = modal.find("#package-add-dep-list");
+    var deps = pkgData.unsatisfied_dependencies;
+
+    modal.find(".package-to-add-name").text(pkgBtnData.name);
+
+    depsList.text("");
+
+    for (var i in deps){
+      var li = $('<li></li>').text(deps[i].depends_on__name);
+      li.append($('<span></span>').text(" ("+
+            deps[i].depends_on__size_formatted+")"));
+      depsList.append(li);
+    }
+
+    modal.find("#package-deps-total-size").text(
+      pkgData.unsatisfied_dependencies_size_formatted);
+
+    addPkgDepsModalBtn.data(pkgBtnData);
+    modal.modal('show');
+  }
+
+  addPkgDepsModalBtn.click(function(e){
+    e.preventDefault();
+
+    addRemovePackage($(this).data(), null);
+  });
+
+  function showPackageReverseDepsModal(pkgBtnData, pkgData){
+    var modal = $("#package-reverse-deps-modal");
+    var depsList = modal.find("#package-reverse-dep-list");
+    var deps = pkgData.reverse_dependencies;
+
+    modal.find(".package-to-rm-name").text(pkgBtnData.name);
+
+    depsList.text("");
+
+    for (var i in deps){
+      var li = $('<li></li>').text(deps[i].package__name);
+      li.append($('<span></span>').text(" ("+
+            deps[i].package__size_formatted+")"));
+      depsList.append(li);
+    }
+
+    modal.find("#package-reverse-deps-total-size").text(
+      pkgData.reverse_dependencies_size_formatted);
+
+    rmdPkgReverseDepsModalBtn.data(pkgBtnData);
+    modal.modal('show');
+  }
+
+  rmdPkgReverseDepsModalBtn.click(function(e){
+    e.preventDefault();
+
+    addRemovePackage($(this).data(), null);
   });
 
-  function addRemovePackage(pkgBtn, tableParams){
-    var pkgBtnData = pkgBtn.data();
+
+  function addRemovePackage(pkgBtnData, tableParams){
     var method;
     var msg = "You have ";
 
-    if (pkgBtnData.directive == 'add') {
+    var btnCell = $("#package-btn-cell-"+pkgBtnData.package);
+    var inlineNotify = btnCell.children(".inline-notification");
+
+    if (pkgBtnData.directive === 'add') {
       method = 'PUT';
       msg += "added 1 package to "+ctx.recipe.name+":";
-    } else if (pkgBtnData.directive == 'remove') {
+      inlineNotify.text("1 package added");
+    } else if (pkgBtnData.directive === 'remove') {
       method = 'DELETE';
       msg += "removed 1 package from "+ctx.recipe.name+":";
+      inlineNotify.text("1 package removed");
     } else {
       throw("Unknown package directive: should be add or remove");
     }
@@ -45,11 +158,18 @@ function customRecipePageInit(ctx) {
             console.warn(data.error);
             return;
           }
-          /* Reload and Invalidate the Add | Rm package table's current data */
-          tableParams.nocache = true;
-          customiseTable.trigger('reload', [tableParams]);
 
           libtoaster.showChangeNotification(msg);
+
+          /* Also do the in-cell notification */
+          btnCell.children("button").fadeOut().promise().done(function(){
+            inlineNotify.fadeIn().delay(500).fadeOut(function(){
+              if (pkgBtnData.directive === 'add')
+                btnCell.children("button[data-directive=remove]").fadeIn();
+              else
+                btnCell.children("button[data-directive=add]").fadeIn();
+            });
+          });
         }
     });
   }
diff --git a/bitbake/lib/toaster/toastergui/templates/customrecipe.html b/bitbake/lib/toaster/toastergui/templates/customrecipe.html
index 743625c..37e0689 100644
--- a/bitbake/lib/toaster/toastergui/templates/customrecipe.html
+++ b/bitbake/lib/toaster/toastergui/templates/customrecipe.html
@@ -26,6 +26,8 @@
       recipe : {
         id: {{recipe.pk}},
         name: "{{recipe.name}}",
+        includedPackagesCount: {{recipe.includes_set.count}},
+        baseRecipeId: {{recipe.base_recipe.pk}},
       }
     };
 
@@ -37,6 +39,44 @@
     }
   });
 </script>
+<!-- package dependencies modal -->
+<div style="display:none" id="package-deps-modal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false">
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+    <h3><span class="package-to-add-name"></span> dependencies</h3>
+  </div>
+  <div class="modal-body">
+    <p>Based on information from a previous build it is likely that adding <strong class="package-to-add-name"></strong> will also add the following packages to your custom image:</p>
+    <ul id="package-add-dep-list">
+    </ul>
+  </div>
+  <div class="modal-footer">
+    <p class="help-block text-left">Total package size: <strong id="package-deps-total-size"></strong></p>
+    <button id="add-package-deps-modal-btn" type="submit" class="btn btn-primary" data-dismiss="modal">Add package</button>
+    <button class="btn" data-dismiss="modal">Cancel</button>
+  </div>
+</div>
+<!-- end package dependencies modal -->
+
+<!-- package reverse dependencies modal -->
+<div style="display:none" id="package-reverse-deps-modal" class="modal hide fade in" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="false">
+  <div class="modal-header">
+    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
+    <h3><span class="package-to-rm-name"></span> reverse dependencies</h3>
+  </div>
+  <div class="modal-body">
+    <p>Based on information from a previous build it is likely that <strong class="package-to-rm-name"></strong> may be added again as the following packages directly depend on it for your custom image:</p>
+    <ul id="package-reverse-dep-list">
+    </ul>
+  </div>
+  <div class="modal-footer">
+    <p class="help-block text-left">Total package size: <strong id="package-reverse-deps-total-size"></strong></p>
+    <button id="rm-package-reverse-deps-modal-btn" type="submit" class="btn btn-primary" data-dismiss="modal">Remove package</button>
+    <button class="btn" data-dismiss="modal">Cancel</button>
+  </div>
+</div>
+<!-- end package dependencies modal -->
+
 
 <div class="row-fluid span11">
   <div class="alert alert-success lead" id="image-created-notification" style="margin-top: 15px; display: none">
@@ -81,11 +121,11 @@
         </div>
       </div>
       <div id="packages-table">
-        {% if recipe.package_set.count == 0 and last_build == None %}
+        {% if recipe.get_all_packages.count == 0 and last_build == None %}
         <h2> Add | Remove packages </h2>
         <div class="alert alert-info air">
           <p class="lead">Toaster has no package information for {{recipe.name}}. To generate package information, build {{recipe.name}}</p>
-          <button class="btn btn-info btn-large build-custom-recipe" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button>
+          <button class="btn btn-info btn-large build-custom-image" style="margin:20px 0 10px 0;">Build {{recipe.name}}</button>
         </div>
         {% else %}
         {# ToasterTable for Adding remove packages #}
@@ -103,7 +143,7 @@
           Approx. packages included
           <i class="icon-question-sign get-help" title="" data-original-title="The number of packages included is based on information from previous builds and from parsing layers, so we can never be sure it is 100% accurate"></i>
         </dt>
-        <dd class="no-packages">{{recipe.package_set.all.count}}</dd>
+        <dd class="no-packages">{{recipe.get_all_packages.count}}</dd>
         <dt>
           Approx. package size
           <i class="icon-question-sign get-help" title="" data-original-title="Package size is based on information from previous builds, so we can never be sure it is 100% accurate"></i>
diff --git a/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html b/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html
index 8723d4e..493456f 100644
--- a/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html
+++ b/bitbake/lib/toaster/toastergui/templates/pkg_add_rm_btn.html
@@ -1,16 +1,19 @@
-<button class="btn btn-block btn-danger add-rm-package-btn" data-directive="remove" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style="
-  {% if data.pk not in extra.current_packages %}
-    display:none
-  {% endif %}
-  ">
-  <i class="icon-trash no-tooltip"></i>
-  Remove package
-</a>
-<button class="btn btn-block add-rm-package-btn" data-directive="add" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style="
-  {% if data.pk in extra.current_packages %}
-    display:none
-  {% endif %}
-    ">
-<i class="icon-plus"></i>
- Add package
-</button>
+<div id="package-btn-cell-{{data.pk}}">
+  <div style="display: none; font-size: 11px; line-height: 1.3;" class="tooltip-inner inline-notification"></div>
+  <button class="btn btn-block btn-danger add-rm-package-btn" data-directive="remove" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style="
+      {% if data.pk not in extra.current_packages %}
+      display:none
+      {% endif %}
+      ">
+    <i class="icon-trash no-tooltip"></i>
+    Remove package
+  </a>
+  <button class="btn btn-block add-rm-package-btn" data-directive="add" data-package="{{data.pk}}" data-package-url="{% url 'xhr_customrecipe_packages' extra.recipe_id data.pk %}" data-name="{{data.name}}" style="
+      {% if data.pk in extra.current_packages %}
+      display:none
+      {% endif %}
+      ">
+    <i class="icon-plus"></i>
+    Add package
+  </button>
+</div>
-- 
2.1.4



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

* [PATCH 32/34] toaster: toastergui tests Update package test to use ProjectPackage
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (30 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 31/34] toaster: customrecipe Add further front end features using new API Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 33/34] toaster: tables SelectPackagesTable rename recipe_id to custrecipeid Michael Wood
                   ` (2 subsequent siblings)
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Update test for adding and removing a package from a CustomImageRecipe
so that it uses the ProjectPackage and correct fields for the packages
included. Change the test for error condition to use an invalid package
id as ManyToMany remove() on package that isn't in the relationship does
not throw an error.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tests.py | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/tests.py b/bitbake/lib/toaster/toastergui/tests.py
index 3683b51..d0e4676 100644
--- a/bitbake/lib/toaster/toastergui/tests.py
+++ b/bitbake/lib/toaster/toastergui/tests.py
@@ -30,7 +30,7 @@ from orm.models import Project, Release, BitbakeVersion, Package, LogMessage
 from orm.models import ReleaseLayerSourcePriority, LayerSource, Layer, Build
 from orm.models import Layer_Version, Recipe, Machine, ProjectLayer, Target
 from orm.models import CustomImageRecipe, ProjectVariable
-from orm.models import Branch
+from orm.models import Branch, ProjectPackage
 
 import toastermain
 import inspect
@@ -141,6 +141,11 @@ class ViewTests(TestCase):
 
         Package.objects.create(name='zpkg1', recipe=self.recipe1, build=build)
 
+        self.project_package = ProjectPackage.objects.create(
+            name="ppkg1",
+            recipe=self.recipe1,
+            project=self.project)
+
         # recipe with project for testing AvailableRecipe table
         self.recipe2 = Recipe.objects.create(layer_source=layersrc,
                                              name="fancy-recipe",
@@ -377,24 +382,27 @@ class ViewTests(TestCase):
         # add self.package to recipe
         response = self.client.put(reverse('xhr_customrecipe_packages',
                                            args=(self.customr.id,
-                                                 self.package.id)))
+                                                 self.project_package.id)))
 
         self.assertEqual(response.status_code, 200)
         self.assertEqual(json.loads(response.content),
-                         {"error": "ok",
-                          "dependencies_needed": []})
-        self.assertEqual(self.customr.package_set.first().name,
-                         self.package.name)
+                         {"error": "ok"})
+        self.assertEqual(self.customr.appends_set.first().name,
+                         self.project_package.name)
         # delete it
         del_url = reverse('xhr_customrecipe_packages',
                           args=(self.customr.id,
-                                self.customr.package_set.first().id))
+                                self.customr.appends_set.first().id))
 
         response = self.client.delete(del_url)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(json.loads(response.content), {"error": "ok"})
-        self.assertFalse(self.customr.package_set.all())
-        # delete it again to test error condition
+        self.assertFalse(self.customr.includes_set.all())
+        # delete invalid package to test error condition
+        del_url = reverse('xhr_customrecipe_packages',
+                          args=(self.customr.id,
+                                99999))
+
         response = self.client.delete(del_url)
         self.assertEqual(response.status_code, 200)
         self.assertNotEqual(json.loads(response.content)["error"], "ok")
-- 
2.1.4



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

* [PATCH 33/34] toaster: tables SelectPackagesTable rename recipe_id to custrecipeid
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (31 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 32/34] toaster: toastergui tests Update package test to use ProjectPackage Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-08 21:16 ` [PATCH 34/34] toaster: toastergui tests Add addtional data to the setUp for new tables Michael Wood
  2015-12-09 12:13 ` [PATCH 00/34] michaelw/toaster/ic-6 Barros Pena, Belen
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Rename the recipe_id to custrecipeid to avoid confusion about which type
of object we're going to be accessing. This means that in the unit tests
for tables we can pass a different kwargs for custom recipes vs normal
recipes.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tables.py | 12 ++++++++----
 bitbake/lib/toaster/toastergui/urls.py   |  4 ++--
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/tables.py b/bitbake/lib/toaster/toastergui/tables.py
index 9476887..2b3684b 100644
--- a/bitbake/lib/toaster/toastergui/tables.py
+++ b/bitbake/lib/toaster/toastergui/tables.py
@@ -685,7 +685,8 @@ class SelectPackagesTable(PackagesTable):
         self.title = "Add | Remove packages"
 
     def setup_queryset(self, *args, **kwargs):
-        cust_recipe = CustomImageRecipe.objects.get(pk=kwargs['recipeid'])
+        self.cust_recipe =\
+            CustomImageRecipe.objects.get(pk=kwargs['custrecipeid'])
         prj = Project.objects.get(pk = kwargs['pid'])
 
         current_packages = self.cust_recipe.get_all_packages()
@@ -705,13 +706,16 @@ class SelectPackagesTable(PackagesTable):
 
         self.queryset = self.queryset.order_by('name')
 
-        self.static_context_extra['recipe_id'] = kwargs['recipeid']
+        self.static_context_extra['recipe_id'] = kwargs['custrecipeid']
         self.static_context_extra['current_packages'] = \
-                cust_recipe.packages.values_list('pk', flat=True)
+                current_packages.values_list('pk', flat=True)
 
     def get_context_data(self, **kwargs):
+        # to reuse the Super class map the custrecipeid to the recipe_id
+        kwargs['recipe_id'] = kwargs['custrecipeid']
         context = super(SelectPackagesTable, self).get_context_data(**kwargs)
-        custom_recipe = CustomImageRecipe.objects.get(pk=kwargs['recipe_id'])
+        custom_recipe = \
+            CustomImageRecipe.objects.get(pk=kwargs['custrecipeid'])
 
         context['recipe'] = custom_recipe
         context['approx_pkg_size'] = \
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py
index 36e5c38..57baa4d 100644
--- a/bitbake/lib/toaster/toastergui/urls.py
+++ b/bitbake/lib/toaster/toastergui/urls.py
@@ -123,11 +123,11 @@ urlpatterns = patterns('toastergui.views',
             name=tables.LayerMachinesTable.__name__.lower()),
 
 
-        url(r'^project/(?P<pid>\d+)/customrecipe/(?P<recipeid>\d+)/selectpackages/$',
+        url(r'^project/(?P<pid>\d+)/customrecipe/(?P<custrecipeid>\d+)/selectpackages/$',
             tables.SelectPackagesTable.as_view(), name="recipeselectpackages"),
 
 
-        url(r'^project/(?P<pid>\d+)/customrecipe/(?P<recipe_id>\d+)$',
+        url(r'^project/(?P<pid>\d+)/customrecipe/(?P<custrecipeid>\d+)$',
             tables.SelectPackagesTable.as_view(template_name="customrecipe.html"),
             name="customrecipe"),
 
-- 
2.1.4



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

* [PATCH 34/34] toaster: toastergui tests Add addtional data to the setUp for new tables
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (32 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 33/34] toaster: tables SelectPackagesTable rename recipe_id to custrecipeid Michael Wood
@ 2015-12-08 21:16 ` Michael Wood
  2015-12-09 12:13 ` [PATCH 00/34] michaelw/toaster/ic-6 Barros Pena, Belen
  34 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-08 21:16 UTC (permalink / raw)
  To: toaster

Add additional data to the setUp to be able to test all the tables for
Image Customisation. Also add the name of the table being tested to the
num of rows assertion.

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 bitbake/lib/toaster/toastergui/tests.py | 92 ++++++++++++++++++++++++---------
 1 file changed, 67 insertions(+), 25 deletions(-)

diff --git a/bitbake/lib/toaster/toastergui/tests.py b/bitbake/lib/toaster/toastergui/tests.py
index d0e4676..0ec7a03 100644
--- a/bitbake/lib/toaster/toastergui/tests.py
+++ b/bitbake/lib/toaster/toastergui/tests.py
@@ -60,7 +60,8 @@ class ViewTests(TestCase):
 
         build = Build.objects.create(project=self.project,
                                      started_on=now,
-                                     completed_on=now)
+                                     completed_on=now,
+                                     outcome=Build.SUCCEEDED)
 
         layersrc = LayerSource.objects.create(sourcetype=LayerSource.TYPE_IMPORTED)
         self.priority = ReleaseLayerSourcePriority.objects.create(release=release,
@@ -128,23 +129,34 @@ class ViewTests(TestCase):
         self.customr = CustomImageRecipe.objects.create(\
                            name="custom recipe", project=self.project,
                            base_recipe=self.recipe1,
+                           file_path="custr",
                            layer_version=lver_custom)
 
-        CustomImageRecipe.objects.create(name="z custom recipe",
-                                         project=self.project,
-                                         base_recipe=self.recipe1)
-
         self.package = Package.objects.create(name='pkg1',
                                               size=999,
                                               recipe=self.recipe1,
+                                              license="HHH",
                                               build=build)
 
-        Package.objects.create(name='zpkg1', recipe=self.recipe1, build=build)
+        Package.objects.create(name='A pkg1',
+                               size=777,
+                               recipe=self.recipe1,
+                               build=build)
+
+        Package.objects.create(name='zpkg1',
+                               recipe=self.recipe1,
+                               build=build,
+                               size=4,
+                               license="ZZ")
 
         self.project_package = ProjectPackage.objects.create(
-            name="ppkg1",
+            name="A pkg",
             recipe=self.recipe1,
-            project=self.project)
+            project=self.project,
+            size=10,
+            license="AAA")
+
+        self.customr.appends_set.add(self.project_package)
 
         # recipe with project for testing AvailableRecipe table
         self.recipe2 = Recipe.objects.create(layer_source=layersrc,
@@ -157,6 +169,20 @@ class ViewTests(TestCase):
                                              section="Z section",
                                              file_path='/home/foo')
 
+        # additional package for the sorting for the SelectPackagesTable
+        project_package_two = ProjectPackage.objects.create(name="ZZ pkg",
+                                                        size=5,
+                                                        recipe=self.recipe2,
+                                                        project=self.project)
+
+        self.customr.appends_set.add(project_package_two)
+
+        Package.objects.create(name='one1',
+                               recipe=self.recipe2,
+                               build=build,
+                               size=2,
+                               license="L")
+
         Recipe.objects.create(layer_source=layersrc,
                               is_image=True,
                               name="Test image one",
@@ -168,17 +194,27 @@ class ViewTests(TestCase):
                               file_path="/one/",
                               layer_version=self.lver)
 
-        Recipe.objects.create(layer_source=layersrc,
-                              is_image=True,
-                              name="Z Test image two",
-                              version="1.3",
-                              summary="two image recipe",
-                              description="recipe two",
-                              section="B",
-                              license="Z",
-                              file_path="/two/",
-                              layer_version=lver_two)
+        zrecipe = Recipe.objects.create(layer_source=layersrc,
+                                        is_image=True,
+                                        name="Z Test image two",
+                                        version="1.3",
+                                        summary="two image recipe",
+                                        description="recipe two",
+                                        section="B",
+                                        license="Z",
+                                        file_path="/two/",
+                                        layer_version=lver_two)
 
+        CustomImageRecipe.objects.create(name="z custom recipe",
+                                         project=self.project,
+                                         base_recipe=zrecipe,
+                                         file_path="zzzz",
+                                         layer_version=lver_custom)
+
+        # Packages in PackagesTable requre that the recipe has been built so
+        # we need to create a target and build pair
+        target = Target.objects.create(target=self.recipe1.name,
+                                       build=build)
 
 
 
@@ -390,14 +426,17 @@ class ViewTests(TestCase):
         self.assertEqual(self.customr.appends_set.first().name,
                          self.project_package.name)
         # delete it
+        to_delete = self.customr.appends_set.first().pk
         del_url = reverse('xhr_customrecipe_packages',
-                          args=(self.customr.id,
-                                self.customr.appends_set.first().id))
+                          args=(self.customr.id, to_delete))
 
         response = self.client.delete(del_url)
         self.assertEqual(response.status_code, 200)
         self.assertEqual(json.loads(response.content), {"error": "ok"})
-        self.assertFalse(self.customr.includes_set.all())
+        all_packages = self.customr.get_all_packages().values_list('pk',
+                                                                   flat=True)
+
+        self.assertFalse(to_delete in all_packages)
         # delete invalid package to test error condition
         del_url = reverse('xhr_customrecipe_packages',
                           args=(self.customr.id,
@@ -439,8 +478,8 @@ class ViewTests(TestCase):
         row2 = next(x for x in rows if x['name'] == self.recipe2.name)
 
         self.assertEqual(response.status_code, 200, 'should be 200 OK status')
-        # self.recipe1 + self.recipe2 + self.customr = 3
-        self.assertEqual(len(rows), 3, 'should be 3 recipes')
+        # All recipes in the setUp
+        self.assertEqual(len(rows), 5, 'should be 5 recipes')
 
         # check other columns have been populated correctly
         self.assertEqual(row1['name'], self.recipe1.name)
@@ -458,6 +497,7 @@ class ViewTests(TestCase):
 
     def test_toaster_tables(self):
         """Test all ToasterTables instances"""
+        current_recipes = self.project.get_available_recipes()
 
         def get_data(table, options={}):
             """Send a request and parse the json response"""
@@ -468,7 +508,9 @@ class ViewTests(TestCase):
             response = table.get(request,
                                  pid=self.project.id,
                                  layerid=self.lver.pk,
-                                 recipeid=self.recipe1.pk)
+                                 recipeid=self.recipe1.pk,
+                                 recipe_id=self.recipe1.pk,
+                                 custrecipeid=self.customr.pk)
             return json.loads(response.content)
 
         # Get a list of classes in tables module
@@ -487,7 +529,7 @@ class ViewTests(TestCase):
             all_data = get_data(table)
 
             self.assertTrue(len(all_data['rows']) > 1,
-                            "Cannot test on a table with < 1 row")
+                            "Cannot test on a %s table with < 1 row" % name)
 
             if table.default_orderby:
                 row_one = all_data['rows'][0][table.default_orderby.strip("-")]
-- 
2.1.4



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

* Re: [PATCH 00/34] michaelw/toaster/ic-6
  2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
                   ` (33 preceding siblings ...)
  2015-12-08 21:16 ` [PATCH 34/34] toaster: toastergui tests Add addtional data to the setUp for new tables Michael Wood
@ 2015-12-09 12:13 ` Barros Pena, Belen
  2015-12-09 13:42   ` Michael Wood
  34 siblings, 1 reply; 39+ messages in thread
From: Barros Pena, Belen @ 2015-12-09 12:13 UTC (permalink / raw)
  To: toaster



On 08/12/2015 21:15, "toaster-bounces@yoctoproject.org on behalf of
Michael Wood" <toaster-bounces@yoctoproject.org on behalf of
michael.g.wood@intel.com> wrote:

>This is the work to introduce the image customisation feature to Toaster.
>As a version 1 this allows basic adding and removing packages of a
>customised
>version of a pre-existing image recipe. To enable this feature run toaster
>with the environment var set CUSTOM_IMAGE=1
>
>http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/log/?h=michaelw/toa
>ster/ic-6

Tried this, but Toaster didn't want to start. I got the same problem when
starting from an existing database, when starting from a clean database
and with a completely clean clone. I might be doing something silly: not
sure.

Output below:

(venv)yocto@icarus:~/ic6/build$ . ../bitbake/bin/toaster

The system will start.
Syncing...
Creating tables ...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table django_admin_log
Creating table south_migrationhistory
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)


Synced:
 > django.contrib.auth
 > django.contrib.contenttypes
 > django.contrib.messages
 > django.contrib.sessions
 > django.contrib.admin
 > django.contrib.staticfiles
 > django.contrib.humanize
 > south


Not synced (use migrations):
 - bldcontrol
 - orm
(use ./manage.py migrate to migrate these)
Running migrations for orm:
 - Migrating forwards to
0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id
.
 > orm:0001_initial
 > orm:0002_auto__add_field_build_timespent
 > orm:0003_timespent
 - Migration 'orm:0003_timespent' is marked for no-dry-run.
 > orm:0004_auto__add_field_package_installed_name
 > 
orm:0005_auto__add_target_image_file__add_target_file__add_field_variablehi
stor
 > 
orm:0006_auto__add_field_target_image_size__add_field_target_license_manife
st_p
 > orm:0007_auto__add_helptext
 > 
orm:0008_auto__chg_field_variablehistory_operation__chg_field_recipe_descri
ptio
 > 
orm:0009_auto__add_projectvariable__add_projectlayer__add_projecttarget__ad
d_pr
 > 
orm:0010_auto__add_field_project_branch__add_field_project_short_descriptio
n__a
 > orm:0011_auto__add_field_projectlayer_dirpath
 > 
orm:0012_auto__add_field_projectlayer_optional__add_field_projecttarget_tas
k
 > 
orm:0013_auto__add_release__add_layerversiondependency__add_unique_layerver
sion
 > 
orm:0014_auto__chg_field_package_summary__chg_field_layer_summary__chg_fiel
d_re
 > 
orm:0015_auto__add_field_layer_vcs_web_url__add_field_layer_vcs_web_tree_ba
se_u
 > 
orm:0016_auto__add_field_release_helptext__chg_field_release_branch__add_in
dex_
 > 
orm:0017_auto__del_toastersettingdefaultlayer__add_releaselayersourcepriori
ty__
 > orm:0018_auto__add_field_layer_version_project
 > orm:0019_auto__add_buildartifact
 > 
orm:0020_auto__add_field_layer_version_local_path__add_field_recipe_pathfla
gs__
 > 
orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version__
chg_
 - Migration 
'orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version_
_chg_' is marked for no-dry-run.
 > 
orm:0022_auto__add_field_target_task__add_field_layer_version_local_path__d
el_f
 > 
orm:0023_auto__del_field_build_warnings_no__del_field_build_errors_no__del_
fiel
 > orm:0024_auto__add_field_recipe_is_image
 > orm:0025_auto__add_field_project_is_default
 > orm:0026_set_default_project
 - Migration 'orm:0026_set_default_project' is marked for no-dry-run.
 > 
orm:0027_auto__add_customimagerecipe__add_unique_customimagerecipe_name_pro
ject
 > orm:0028_auto__chg_field_logmessage_message
 > 
orm:0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_fi
eld_
 > 
orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
e_id
FATAL ERROR - The following SQL query failed: DROP TABLE
"orm_customimagerecipe_packages";
The error was: no such table: orm_customimagerecipe_packages
 ! Error found during real run of migration! Aborting.


 ! Since you have a database that does not support running
 ! schema-altering statements in transactions, we have had
 ! to leave it in an interim state between migrations.


! You *might* be able to recover with:   = CREATE TABLE
"orm_customimagerecipe_packages" ("id" integer NOT NULL PRIMARY KEY,
"customimagerecipe_id" integer NOT NULL, "package_id" integer NOT NULL) []
   = CREATE UNIQUE INDEX
"orm_customimagerecipe_packages_customimagerecipe_id__package_id" ON
"orm_customimagerecipe_packages"("customimagerecipe_id", "package_id"); []
   = CREATE UNIQUE INDEX "orm_customimagerecipe_name__project_id" ON
"orm_customimagerecipe"("name", "project_id"); []


 ! The South developers regret this has happened, and would
 ! like to gently persuade you to consider a slightly
 ! easier-to-deal-with DBMS (one that supports DDL transactions)
 ! NOTE: The error which caused the migration to fail is further up.
Error in migration:
orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
e_id
Traceback (most recent call last):
  File "../bitbake/bin/../lib/toaster/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/__init__.py", line 399, in execute_from_command_line
    utility.execute()
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/__init__.py", line 392, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/base.py", line 242, in run_from_argv
    self.execute(*args, **options.__dict__)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/management
/commands/migrate.py", line 111, in handle
    ignore_ghosts = ignore_ghosts,
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
__init__.py", line 220, in migrate_app
    success = migrator.migrate_many(target, workplan, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 254, in migrate_many
    result = migrator.__class__.migrate_many(migrator, target, migrations,
database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 329, in migrate_many
    result = self.migrate(migration, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 133, in migrate
    result = self.run(migration, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 114, in run
    return self.run_migration(migration, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 84, in run_migration
    migration_function()
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 60, in <lambda>
    return (lambda: direction(orm))
  File 
"/home/yocto/ic6/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_cu
stomimagerecipe_name__del_field_customimagerecipe_id.py", line 26, in
forwards
    db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/sqlite3
.py", line 272, in delete_table
    generic.DatabaseOperations.delete_table(self, table_name, False)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
.py", line 47, in _cache_clear
    return func(self, table, *args, **opts)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
.py", line 388, in delete_table
    self.execute('DROP TABLE %s;' % params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
.py", line 282, in execute
    cursor.execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/util.py", line 69, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/util.py", line 53, in execute
    return self.cursor.execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/utils.
py", line 99, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/util.py", line 53, in execute
    return self.cursor.execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/sqlite3/base.py", line 450, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table:
orm_customimagerecipe_packages
Running migrations for orm:
 - Migrating forwards to
0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id
.
 > 
orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
e_id
FATAL ERROR - The following SQL query failed: DROP TABLE
"orm_customimagerecipe_packages";
The error was: no such table: orm_customimagerecipe_packages
 ! Error found during real run of migration! Aborting.


 ! Since you have a database that does not support running
 ! schema-altering statements in transactions, we have had
 ! to leave it in an interim state between migrations.


! You *might* be able to recover with:   = CREATE TABLE
"orm_customimagerecipe_packages" ("id" integer NOT NULL PRIMARY KEY,
"customimagerecipe_id" integer NOT NULL, "package_id" integer NOT NULL) []
   = CREATE UNIQUE INDEX
"orm_customimagerecipe_packages_customimagerecipe_id__package_id" ON
"orm_customimagerecipe_packages"("customimagerecipe_id", "package_id"); []
   = CREATE UNIQUE INDEX "orm_customimagerecipe_name__project_id" ON
"orm_customimagerecipe"("name", "project_id"); []


 ! The South developers regret this has happened, and would
 ! like to gently persuade you to consider a slightly
 ! easier-to-deal-with DBMS (one that supports DDL transactions)
 ! NOTE: The error which caused the migration to fail is further up.
Error in migration:
orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
e_id
Traceback (most recent call last):
  File "../bitbake/bin/../lib/toaster/manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/__init__.py", line 399, in execute_from_command_line
    utility.execute()
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/__init__.py", line 392, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/base.py", line 242, in run_from_argv
    self.execute(*args, **options.__dict__)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
gement/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/management
/commands/migrate.py", line 111, in handle
    ignore_ghosts = ignore_ghosts,
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
__init__.py", line 220, in migrate_app
    success = migrator.migrate_many(target, workplan, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 254, in migrate_many
    result = migrator.__class__.migrate_many(migrator, target, migrations,
database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 329, in migrate_many
    result = self.migrate(migration, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 133, in migrate
    result = self.run(migration, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 114, in run
    return self.run_migration(migration, database)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 84, in run_migration
    migration_function()
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
migrators.py", line 60, in <lambda>
    return (lambda: direction(orm))
  File 
"/home/yocto/ic6/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_cu
stomimagerecipe_name__del_field_customimagerecipe_id.py", line 26, in
forwards
    db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/sqlite3
.py", line 272, in delete_table
    generic.DatabaseOperations.delete_table(self, table_name, False)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
.py", line 47, in _cache_clear
    return func(self, table, *args, **opts)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
.py", line 388, in delete_table
    self.execute('DROP TABLE %s;' % params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
.py", line 282, in execute
    cursor.execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/util.py", line 69, in execute
    return super(CursorDebugWrapper, self).execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/util.py", line 53, in execute
    return self.cursor.execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/utils.
py", line 99, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/util.py", line 53, in execute
    return self.cursor.execute(sql, params)
  File 
"/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
ds/sqlite3/base.py", line 450, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.OperationalError: no such table:
orm_customimagerecipe_packages


Error on orm migration, rolling back...
Running migrations for orm:
 - Migrating backwards to just after 0001_initial.
 < 
orm:0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_fi
eld_
   (faked)
 < orm:0028_auto__chg_field_logmessage_message
   (faked)
 < 
orm:0027_auto__add_customimagerecipe__add_unique_customimagerecipe_name_pro
ject
   (faked)
 < orm:0026_set_default_project
   (faked)
 < orm:0025_auto__add_field_project_is_default
   (faked)
 < orm:0024_auto__add_field_recipe_is_image
   (faked)
 < 
orm:0023_auto__del_field_build_warnings_no__del_field_build_errors_no__del_
fiel
   (faked)
 < 
orm:0022_auto__add_field_target_task__add_field_layer_version_local_path__d
el_f
   (faked)
 < 
orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version__
chg_
   (faked)
 < 
orm:0020_auto__add_field_layer_version_local_path__add_field_recipe_pathfla
gs__
   (faked)
 < orm:0019_auto__add_buildartifact
   (faked)
 < orm:0018_auto__add_field_layer_version_project
   (faked)
 < 
orm:0017_auto__del_toastersettingdefaultlayer__add_releaselayersourcepriori
ty__
   (faked)
 < 
orm:0016_auto__add_field_release_helptext__chg_field_release_branch__add_in
dex_
   (faked)
 < 
orm:0015_auto__add_field_layer_vcs_web_url__add_field_layer_vcs_web_tree_ba
se_u
   (faked)
 < 
orm:0014_auto__chg_field_package_summary__chg_field_layer_summary__chg_fiel
d_re
   (faked)
 < 
orm:0013_auto__add_release__add_layerversiondependency__add_unique_layerver
sion
   (faked)
 < 
orm:0012_auto__add_field_projectlayer_optional__add_field_projecttarget_tas
k
   (faked)
 < orm:0011_auto__add_field_projectlayer_dirpath
   (faked)
 < 
orm:0010_auto__add_field_project_branch__add_field_project_short_descriptio
n__a
   (faked)
 < 
orm:0009_auto__add_projectvariable__add_projectlayer__add_projecttarget__ad
d_pr
   (faked)
 < 
orm:0008_auto__chg_field_variablehistory_operation__chg_field_recipe_descri
ptio
   (faked)
 < orm:0007_auto__add_helptext
   (faked)
 < 
orm:0006_auto__add_field_target_image_size__add_field_target_license_manife
st_p
   (faked)
 < 
orm:0005_auto__add_target_image_file__add_target_file__add_field_variablehi
stor
   (faked)
 < orm:0004_auto__add_field_package_installed_name
   (faked)
 < orm:0003_timespent
   (faked)
 < orm:0002_auto__add_field_build_timespent
   (faked)
Failed start.


>
>Michael Wood (34):
>  toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe
>  toaster: models fall back to a sensible string for no vcs reference
>  toaster: ToasterTables simplify filter function move common part to
>    widget
>  toaster: tablejs Add an event handler to manually trigger a data
>    reload
>  toaster: orm Add sum of dependencies size function to
>    PackageDependencyManager
>  toaster: orm make CustomImageRecipe inherit from Recipe
>  toaster: orm: Add db migration for new CustomImageRecipe inheritance
>    change
>  toaster: orm Add ProjectPackage table
>  toaster: orm: Add db migration for new  ProjectPackages table
>  toaster: buildinfohelper Add the concept of ProjectPackages
>  toaster: orm add CustomImageRecipe generate contents function
>  toaster: move CustomImageRecipe generation to API entry point
>  toaster: views Add view to download custom recipe
>  toaster: tables Add table for Packages and update SelectPackagesTable
>  toaster: Continue front end features to custom image recipe page.
>  toaster: newcustomimage Move modal dialog out of newcustomimage
>    template
>  toaster: Add recipe details page
>  toaster: toastertable remove title from Show all in table
>  toaster: views xhr_customrecipe_packages clean up API
>  toaster: toastergui tests Update to reflect changes to
>    CustomImageRecipe
>  toaster: toastergui tests Add unit test for download custom recipe
>  toaster: orm get_project_layer_versions to return layer_version
>    objects
>  toaster: orm Add convenience method to get all pkgs in a
>    CustomImageRecipe
>  toaster: libtoaster Add createCustomRecipe method
>  toaster: newcustomimage_modal use libtoaster method for new
>    CustomRecipe
>  toaster: tables add recipe download link to CustomImagesTable
>  toaster: tables Change SelectPackagesTable to use ProjectPackage
>  toaster: API allow CustomImageRecipe to be updated after creation
>  toaster: xhr_customrecipe_id  change to use ProjectPackage
>  toaster: xhr_customrecipe_packages add GET info for package response
>  toaster: customrecipe Add further front end features using new API
>  toaster: toastergui tests Update package test to use ProjectPackage
>  toaster: tables SelectPackagesTable rename recipe_id to custrecipeid
>  toaster: toastergui tests Add addtional data to the setUp for new
>    tables
>
> bitbake/lib/bb/ui/buildinfohelper.py               | 113 +++++-
> .../toaster/bldcontrol/localhostbecontroller.py    |  29 +-
> ...del_field_customimagerecipe_name__del_field_.py | 433
>+++++++++++++++++++++
> ...erecipe_name__del_field_customimagerecipe_id.py | 379
>++++++++++++++++++
> bitbake/lib/toaster/orm/models.py                  |  95 ++++-
> .../toaster/toastergui/static/js/customrecipe.js   | 176 ++++++++-
> .../lib/toaster/toastergui/static/js/libtoaster.js |  27 ++
> .../toaster/toastergui/static/js/newcustomimage.js |  49 ---
> .../toastergui/static/js/newcustomimage_modal.js   |  28 ++
> .../toaster/toastergui/static/js/recipedetails.js  |  52 +++
> bitbake/lib/toaster/toastergui/static/js/table.js  |   7 +
> bitbake/lib/toaster/toastergui/tables.py           | 239 +++++++++---
> bitbake/lib/toaster/toastergui/templates/base.html |   1 +
> .../toaster/toastergui/templates/customrecipe.html | 186 ++++++---
> .../toastergui/templates/newcustomimage.html       |  44 +--
> .../toastergui/templates/newcustomimage_modal.html |  33 ++
> .../toastergui/templates/pkg_add_rm_btn.html       |  35 +-
> .../toastergui/templates/recipedetails.html        | 180 +++++++++
> .../snippets/pkg_dependencies_popover.html         |  14 +
> .../toaster/toastergui/templates/toastertable.html |   2 +-
> bitbake/lib/toaster/toastergui/tests.py            | 145 +++++--
> bitbake/lib/toaster/toastergui/urls.py             |  17 +-
> bitbake/lib/toaster/toastergui/views.py            | 262 ++++++++++---
> bitbake/lib/toaster/toastergui/widgets.py          |   5 +-
> 24 files changed, 2171 insertions(+), 380 deletions(-)
> create mode 100644
>bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_fiel
>d_customimagerecipe_name__del_field_.py
> create mode 100644
>bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_
>name__del_field_customimagerecipe_id.py
> delete mode 100644
>bitbake/lib/toaster/toastergui/static/js/newcustomimage.js
> create mode 100644
>bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
> create mode 100644
>bitbake/lib/toaster/toastergui/static/js/recipedetails.js
> create mode 100644
>bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html
> create mode 100644
>bitbake/lib/toaster/toastergui/templates/recipedetails.html
> create mode 100644
>bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover
>.html
>
>-- 
>2.1.4
>
>-- 
>_______________________________________________
>toaster mailing list
>toaster@yoctoproject.org
>https://lists.yoctoproject.org/listinfo/toaster



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

* Re: [PATCH 00/34] michaelw/toaster/ic-6
  2015-12-09 12:13 ` [PATCH 00/34] michaelw/toaster/ic-6 Barros Pena, Belen
@ 2015-12-09 13:42   ` Michael Wood
  2015-12-10 15:37     ` Michael Wood
  0 siblings, 1 reply; 39+ messages in thread
From: Michael Wood @ 2015-12-09 13:42 UTC (permalink / raw)
  To: Barros Pena, Belen, toaster

On 09/12/15 12:13, Barros Pena, Belen wrote:
>
> On 08/12/2015 21:15, "toaster-bounces@yoctoproject.org on behalf of
> Michael Wood" <toaster-bounces@yoctoproject.org on behalf of
> michael.g.wood@intel.com> wrote:
>
>> This is the work to introduce the image customisation feature to Toaster.
>> As a version 1 this allows basic adding and removing packages of a
>> customised
>> version of a pre-existing image recipe. To enable this feature run toaster
>> with the environment var set CUSTOM_IMAGE=1
>>
>> http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/log/?h=michaelw/toa
>> ster/ic-6
> Tried this, but Toaster didn't want to start. I got the same problem when
> starting from an existing database, when starting from a clean database
> and with a completely clean clone. I might be doing something silly: not
> sure.
>
> Output below:
>
> (venv)yocto@icarus:~/ic6/build$ . ../bitbake/bin/toaster
>
> The system will start.
> Syncing...
> Creating tables ...
> Creating table auth_permission
> Creating table auth_group_permissions
> Creating table auth_group
> Creating table auth_user_groups
> Creating table auth_user_user_permissions
> Creating table auth_user
> Creating table django_content_type
> Creating table django_session
> Creating table django_admin_log
> Creating table south_migrationhistory
> Installing custom SQL ...
> Installing indexes ...
> Installed 0 object(s) from 0 fixture(s)
>
>
> Synced:
>   > django.contrib.auth
>   > django.contrib.contenttypes
>   > django.contrib.messages
>   > django.contrib.sessions
>   > django.contrib.admin
>   > django.contrib.staticfiles
>   > django.contrib.humanize
>   > south
>
>
> Not synced (use migrations):
>   - bldcontrol
>   - orm
> (use ./manage.py migrate to migrate these)
> Running migrations for orm:
>   - Migrating forwards to
> 0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id
> .
>   > orm:0001_initial
>   > orm:0002_auto__add_field_build_timespent
>   > orm:0003_timespent
>   - Migration 'orm:0003_timespent' is marked for no-dry-run.
>   > orm:0004_auto__add_field_package_installed_name
>   >
> orm:0005_auto__add_target_image_file__add_target_file__add_field_variablehi
> stor
>   >
> orm:0006_auto__add_field_target_image_size__add_field_target_license_manife
> st_p
>   > orm:0007_auto__add_helptext
>   >
> orm:0008_auto__chg_field_variablehistory_operation__chg_field_recipe_descri
> ptio
>   >
> orm:0009_auto__add_projectvariable__add_projectlayer__add_projecttarget__ad
> d_pr
>   >
> orm:0010_auto__add_field_project_branch__add_field_project_short_descriptio
> n__a
>   > orm:0011_auto__add_field_projectlayer_dirpath
>   >
> orm:0012_auto__add_field_projectlayer_optional__add_field_projecttarget_tas
> k
>   >
> orm:0013_auto__add_release__add_layerversiondependency__add_unique_layerver
> sion
>   >
> orm:0014_auto__chg_field_package_summary__chg_field_layer_summary__chg_fiel
> d_re
>   >
> orm:0015_auto__add_field_layer_vcs_web_url__add_field_layer_vcs_web_tree_ba
> se_u
>   >
> orm:0016_auto__add_field_release_helptext__chg_field_release_branch__add_in
> dex_
>   >
> orm:0017_auto__del_toastersettingdefaultlayer__add_releaselayersourcepriori
> ty__
>   > orm:0018_auto__add_field_layer_version_project
>   > orm:0019_auto__add_buildartifact
>   >
> orm:0020_auto__add_field_layer_version_local_path__add_field_recipe_pathfla
> gs__
>   >
> orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version__
> chg_
>   - Migration
> 'orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version_
> _chg_' is marked for no-dry-run.
>   >
> orm:0022_auto__add_field_target_task__add_field_layer_version_local_path__d
> el_f
>   >
> orm:0023_auto__del_field_build_warnings_no__del_field_build_errors_no__del_
> fiel
>   > orm:0024_auto__add_field_recipe_is_image
>   > orm:0025_auto__add_field_project_is_default
>   > orm:0026_set_default_project
>   - Migration 'orm:0026_set_default_project' is marked for no-dry-run.
>   >
> orm:0027_auto__add_customimagerecipe__add_unique_customimagerecipe_name_pro
> ject
>   > orm:0028_auto__chg_field_logmessage_message
>   >
> orm:0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_fi
> eld_
>   >
> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
> e_id
> FATAL ERROR - The following SQL query failed: DROP TABLE
> "orm_customimagerecipe_packages";
> The error was: no such table: orm_customimagerecipe_packages
>   ! Error found during real run of migration! Aborting.
>
>
>   ! Since you have a database that does not support running
>   ! schema-altering statements in transactions, we have had
>   ! to leave it in an interim state between migrations.
>
>
> ! You *might* be able to recover with:   = CREATE TABLE
> "orm_customimagerecipe_packages" ("id" integer NOT NULL PRIMARY KEY,
> "customimagerecipe_id" integer NOT NULL, "package_id" integer NOT NULL) []
>     = CREATE UNIQUE INDEX
> "orm_customimagerecipe_packages_customimagerecipe_id__package_id" ON
> "orm_customimagerecipe_packages"("customimagerecipe_id", "package_id"); []
>     = CREATE UNIQUE INDEX "orm_customimagerecipe_name__project_id" ON
> "orm_customimagerecipe"("name", "project_id"); []
>
>
>   ! The South developers regret this has happened, and would
>   ! like to gently persuade you to consider a slightly
>   ! easier-to-deal-with DBMS (one that supports DDL transactions)
>   ! NOTE: The error which caused the migration to fail is further up.
> Error in migration:
> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
> e_id
> Traceback (most recent call last):
>    File "../bitbake/bin/../lib/toaster/manage.py", line 10, in <module>
>      execute_from_command_line(sys.argv)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/__init__.py", line 399, in execute_from_command_line
>      utility.execute()
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/__init__.py", line 392, in execute
>      self.fetch_command(subcommand).run_from_argv(self.argv)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/base.py", line 242, in run_from_argv
>      self.execute(*args, **options.__dict__)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/base.py", line 285, in execute
>      output = self.handle(*args, **options)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/management
> /commands/migrate.py", line 111, in handle
>      ignore_ghosts = ignore_ghosts,
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> __init__.py", line 220, in migrate_app
>      success = migrator.migrate_many(target, workplan, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 254, in migrate_many
>      result = migrator.__class__.migrate_many(migrator, target, migrations,
> database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 329, in migrate_many
>      result = self.migrate(migration, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 133, in migrate
>      result = self.run(migration, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 114, in run
>      return self.run_migration(migration, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 84, in run_migration
>      migration_function()
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 60, in <lambda>
>      return (lambda: direction(orm))
>    File
> "/home/yocto/ic6/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_cu
> stomimagerecipe_name__del_field_customimagerecipe_id.py", line 26, in
> forwards
>      db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/sqlite3
> .py", line 272, in delete_table
>      generic.DatabaseOperations.delete_table(self, table_name, False)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
> .py", line 47, in _cache_clear
>      return func(self, table, *args, **opts)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
> .py", line 388, in delete_table
>      self.execute('DROP TABLE %s;' % params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
> .py", line 282, in execute
>      cursor.execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/util.py", line 69, in execute
>      return super(CursorDebugWrapper, self).execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/util.py", line 53, in execute
>      return self.cursor.execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/utils.
> py", line 99, in __exit__
>      six.reraise(dj_exc_type, dj_exc_value, traceback)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/util.py", line 53, in execute
>      return self.cursor.execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/sqlite3/base.py", line 450, in execute
>      return Database.Cursor.execute(self, query, params)
> django.db.utils.OperationalError: no such table:
> orm_customimagerecipe_packages
> Running migrations for orm:
>   - Migrating forwards to
> 0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id
> .
>   >
> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
> e_id
> FATAL ERROR - The following SQL query failed: DROP TABLE
> "orm_customimagerecipe_packages";
> The error was: no such table: orm_customimagerecipe_packages
>   ! Error found during real run of migration! Aborting.
>
>
>   ! Since you have a database that does not support running
>   ! schema-altering statements in transactions, we have had
>   ! to leave it in an interim state between migrations.
>
>
> ! You *might* be able to recover with:   = CREATE TABLE
> "orm_customimagerecipe_packages" ("id" integer NOT NULL PRIMARY KEY,
> "customimagerecipe_id" integer NOT NULL, "package_id" integer NOT NULL) []
>     = CREATE UNIQUE INDEX
> "orm_customimagerecipe_packages_customimagerecipe_id__package_id" ON
> "orm_customimagerecipe_packages"("customimagerecipe_id", "package_id"); []
>     = CREATE UNIQUE INDEX "orm_customimagerecipe_name__project_id" ON
> "orm_customimagerecipe"("name", "project_id"); []
>
>
>   ! The South developers regret this has happened, and would
>   ! like to gently persuade you to consider a slightly
>   ! easier-to-deal-with DBMS (one that supports DDL transactions)
>   ! NOTE: The error which caused the migration to fail is further up.
> Error in migration:
> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip
> e_id
> Traceback (most recent call last):
>    File "../bitbake/bin/../lib/toaster/manage.py", line 10, in <module>
>      execute_from_command_line(sys.argv)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/__init__.py", line 399, in execute_from_command_line
>      utility.execute()
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/__init__.py", line 392, in execute
>      self.fetch_command(subcommand).run_from_argv(self.argv)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/base.py", line 242, in run_from_argv
>      self.execute(*args, **options.__dict__)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana
> gement/base.py", line 285, in execute
>      output = self.handle(*args, **options)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/management
> /commands/migrate.py", line 111, in handle
>      ignore_ghosts = ignore_ghosts,
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> __init__.py", line 220, in migrate_app
>      success = migrator.migrate_many(target, workplan, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 254, in migrate_many
>      result = migrator.__class__.migrate_many(migrator, target, migrations,
> database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 329, in migrate_many
>      result = self.migrate(migration, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 133, in migrate
>      result = self.run(migration, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 114, in run
>      return self.run_migration(migration, database)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 84, in run_migration
>      migration_function()
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/
> migrators.py", line 60, in <lambda>
>      return (lambda: direction(orm))
>    File
> "/home/yocto/ic6/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_cu
> stomimagerecipe_name__del_field_customimagerecipe_id.py", line 26, in
> forwards
>      db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/sqlite3
> .py", line 272, in delete_table
>      generic.DatabaseOperations.delete_table(self, table_name, False)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
> .py", line 47, in _cache_clear
>      return func(self, table, *args, **opts)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
> .py", line 388, in delete_table
>      self.execute('DROP TABLE %s;' % params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic
> .py", line 282, in execute
>      cursor.execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/util.py", line 69, in execute
>      return super(CursorDebugWrapper, self).execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/util.py", line 53, in execute
>      return self.cursor.execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/utils.
> py", line 99, in __exit__
>      six.reraise(dj_exc_type, dj_exc_value, traceback)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/util.py", line 53, in execute
>      return self.cursor.execute(sql, params)
>    File
> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen
> ds/sqlite3/base.py", line 450, in execute
>      return Database.Cursor.execute(self, query, params)
> django.db.utils.OperationalError: no such table:
> orm_customimagerecipe_packages
>
>
> Error on orm migration, rolling back...
> Running migrations for orm:
>   - Migrating backwards to just after 0001_initial.
>   <
> orm:0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_fi
> eld_
>     (faked)
>   < orm:0028_auto__chg_field_logmessage_message
>     (faked)
>   <
> orm:0027_auto__add_customimagerecipe__add_unique_customimagerecipe_name_pro
> ject
>     (faked)
>   < orm:0026_set_default_project
>     (faked)
>   < orm:0025_auto__add_field_project_is_default
>     (faked)
>   < orm:0024_auto__add_field_recipe_is_image
>     (faked)
>   <
> orm:0023_auto__del_field_build_warnings_no__del_field_build_errors_no__del_
> fiel
>     (faked)
>   <
> orm:0022_auto__add_field_target_task__add_field_layer_version_local_path__d
> el_f
>     (faked)
>   <
> orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version__
> chg_
>     (faked)
>   <
> orm:0020_auto__add_field_layer_version_local_path__add_field_recipe_pathfla
> gs__
>     (faked)
>   < orm:0019_auto__add_buildartifact
>     (faked)
>   < orm:0018_auto__add_field_layer_version_project
>     (faked)
>   <
> orm:0017_auto__del_toastersettingdefaultlayer__add_releaselayersourcepriori
> ty__
>     (faked)
>   <
> orm:0016_auto__add_field_release_helptext__chg_field_release_branch__add_in
> dex_
>     (faked)
>   <
> orm:0015_auto__add_field_layer_vcs_web_url__add_field_layer_vcs_web_tree_ba
> se_u
>     (faked)
>   <
> orm:0014_auto__chg_field_package_summary__chg_field_layer_summary__chg_fiel
> d_re
>     (faked)
>   <
> orm:0013_auto__add_release__add_layerversiondependency__add_unique_layerver
> sion
>     (faked)
>   <
> orm:0012_auto__add_field_projectlayer_optional__add_field_projecttarget_tas
> k
>     (faked)
>   < orm:0011_auto__add_field_projectlayer_dirpath
>     (faked)
>   <
> orm:0010_auto__add_field_project_branch__add_field_project_short_descriptio
> n__a
>     (faked)
>   <
> orm:0009_auto__add_projectvariable__add_projectlayer__add_projecttarget__ad
> d_pr
>     (faked)
>   <
> orm:0008_auto__chg_field_variablehistory_operation__chg_field_recipe_descri
> ptio
>     (faked)
>   < orm:0007_auto__add_helptext
>     (faked)
>   <
> orm:0006_auto__add_field_target_image_size__add_field_target_license_manife
> st_p
>     (faked)
>   <
> orm:0005_auto__add_target_image_file__add_target_file__add_field_variablehi
> stor
>     (faked)
>   < orm:0004_auto__add_field_package_installed_name
>     (faked)
>   < orm:0003_timespent
>     (faked)
>   < orm:0002_auto__add_field_build_timespent
>     (faked)
> Failed start.
>
>
>> Michael Wood (34):
>>   toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe
>>   toaster: models fall back to a sensible string for no vcs reference
>>   toaster: ToasterTables simplify filter function move common part to
>>     widget
>>   toaster: tablejs Add an event handler to manually trigger a data
>>     reload
>>   toaster: orm Add sum of dependencies size function to
>>     PackageDependencyManager
>>   toaster: orm make CustomImageRecipe inherit from Recipe
>>   toaster: orm: Add db migration for new CustomImageRecipe inheritance
>>     change
>>   toaster: orm Add ProjectPackage table
>>   toaster: orm: Add db migration for new  ProjectPackages table
>>   toaster: buildinfohelper Add the concept of ProjectPackages
>>   toaster: orm add CustomImageRecipe generate contents function
>>   toaster: move CustomImageRecipe generation to API entry point
>>   toaster: views Add view to download custom recipe
>>   toaster: tables Add table for Packages and update SelectPackagesTable
>>   toaster: Continue front end features to custom image recipe page.
>>   toaster: newcustomimage Move modal dialog out of newcustomimage
>>     template
>>   toaster: Add recipe details page
>>   toaster: toastertable remove title from Show all in table
>>   toaster: views xhr_customrecipe_packages clean up API
>>   toaster: toastergui tests Update to reflect changes to
>>     CustomImageRecipe
>>   toaster: toastergui tests Add unit test for download custom recipe
>>   toaster: orm get_project_layer_versions to return layer_version
>>     objects
>>   toaster: orm Add convenience method to get all pkgs in a
>>     CustomImageRecipe
>>   toaster: libtoaster Add createCustomRecipe method
>>   toaster: newcustomimage_modal use libtoaster method for new
>>     CustomRecipe
>>   toaster: tables add recipe download link to CustomImagesTable
>>   toaster: tables Change SelectPackagesTable to use ProjectPackage
>>   toaster: API allow CustomImageRecipe to be updated after creation
>>   toaster: xhr_customrecipe_id  change to use ProjectPackage
>>   toaster: xhr_customrecipe_packages add GET info for package response
>>   toaster: customrecipe Add further front end features using new API
>>   toaster: toastergui tests Update package test to use ProjectPackage
>>   toaster: tables SelectPackagesTable rename recipe_id to custrecipeid
>>   toaster: toastergui tests Add addtional data to the setUp for new
>>     tables
>>
>> bitbake/lib/bb/ui/buildinfohelper.py               | 113 +++++-
>> .../toaster/bldcontrol/localhostbecontroller.py    |  29 +-
>> ...del_field_customimagerecipe_name__del_field_.py | 433
>> +++++++++++++++++++++
>> ...erecipe_name__del_field_customimagerecipe_id.py | 379
>> ++++++++++++++++++
>> bitbake/lib/toaster/orm/models.py                  |  95 ++++-
>> .../toaster/toastergui/static/js/customrecipe.js   | 176 ++++++++-
>> .../lib/toaster/toastergui/static/js/libtoaster.js |  27 ++
>> .../toaster/toastergui/static/js/newcustomimage.js |  49 ---
>> .../toastergui/static/js/newcustomimage_modal.js   |  28 ++
>> .../toaster/toastergui/static/js/recipedetails.js  |  52 +++
>> bitbake/lib/toaster/toastergui/static/js/table.js  |   7 +
>> bitbake/lib/toaster/toastergui/tables.py           | 239 +++++++++---
>> bitbake/lib/toaster/toastergui/templates/base.html |   1 +
>> .../toaster/toastergui/templates/customrecipe.html | 186 ++++++---
>> .../toastergui/templates/newcustomimage.html       |  44 +--
>> .../toastergui/templates/newcustomimage_modal.html |  33 ++
>> .../toastergui/templates/pkg_add_rm_btn.html       |  35 +-
>> .../toastergui/templates/recipedetails.html        | 180 +++++++++
>> .../snippets/pkg_dependencies_popover.html         |  14 +
>> .../toaster/toastergui/templates/toastertable.html |   2 +-
>> bitbake/lib/toaster/toastergui/tests.py            | 145 +++++--
>> bitbake/lib/toaster/toastergui/urls.py             |  17 +-
>> bitbake/lib/toaster/toastergui/views.py            | 262 ++++++++++---
>> bitbake/lib/toaster/toastergui/widgets.py          |   5 +-
>> 24 files changed, 2171 insertions(+), 380 deletions(-)
>> create mode 100644
>> bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_fiel
>> d_customimagerecipe_name__del_field_.py
>> create mode 100644
>> bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_
>> name__del_field_customimagerecipe_id.py
>> delete mode 100644
>> bitbake/lib/toaster/toastergui/static/js/newcustomimage.js
>> create mode 100644
>> bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
>> create mode 100644
>> bitbake/lib/toaster/toastergui/static/js/recipedetails.js
>> create mode 100644
>> bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html
>> create mode 100644
>> bitbake/lib/toaster/toastergui/templates/recipedetails.html
>> create mode 100644
>> bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover
>> .html
>>
>> -- 
>> 2.1.4
>>
>> -- 
>> _______________________________________________
>> toaster mailing list
>> toaster@yoctoproject.org
>> https://lists.yoctoproject.org/listinfo/toaster

The migration generated seemed to be wrong, so it might have been 
because of a previous WIP migration that wasn't ever committed. I've 
pushed a new migration to the michaelw/toaster/ic-6

Thanks,

Michael



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

* [PATCH v2] toaster: orm: Add db migration for new ProjectPackages table
  2015-12-08 21:15 ` [PATCH 09/34] toaster: orm: Add db migration for new ProjectPackages table Michael Wood
@ 2015-12-09 13:47   ` Michael Wood
  0 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-09 13:47 UTC (permalink / raw)
  To: toaster

Signed-off-by: Michael Wood <michael.g.wood@intel.com>
---
 .../migrations/0030_auto__add_projectpackage.py    | 391 +++++++++++++++++++++
 1 file changed, 391 insertions(+)
 create mode 100644 bitbake/lib/toaster/orm/migrations/0030_auto__add_projectpackage.py

diff --git a/bitbake/lib/toaster/orm/migrations/0030_auto__add_projectpackage.py b/bitbake/lib/toaster/orm/migrations/0030_auto__add_projectpackage.py
new file mode 100644
index 0000000..f83c1e6
--- /dev/null
+++ b/bitbake/lib/toaster/orm/migrations/0030_auto__add_projectpackage.py
@@ -0,0 +1,391 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'ProjectPackage'
+        db.create_table(u'orm_projectpackage', (
+            (u'package_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['orm.Package'], unique=True, primary_key=True)),
+            ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['orm.Project'])),
+        ))
+        db.send_create_signal(u'orm', ['ProjectPackage'])
+
+        # Adding M2M table for field recipe_includes on 'ProjectPackage'
+        m2m_table_name = db.shorten_name(u'orm_projectpackage_recipe_includes')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('projectpackage', models.ForeignKey(orm[u'orm.projectpackage'], null=False)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['projectpackage_id', 'customimagerecipe_id'])
+
+        # Adding M2M table for field recipe_excludes on 'ProjectPackage'
+        m2m_table_name = db.shorten_name(u'orm_projectpackage_recipe_excludes')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('projectpackage', models.ForeignKey(orm[u'orm.projectpackage'], null=False)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['projectpackage_id', 'customimagerecipe_id'])
+
+        # Adding M2M table for field recipe_appends on 'ProjectPackage'
+        m2m_table_name = db.shorten_name(u'orm_projectpackage_recipe_appends')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('projectpackage', models.ForeignKey(orm[u'orm.projectpackage'], null=False)),
+            ('customimagerecipe', models.ForeignKey(orm[u'orm.customimagerecipe'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['projectpackage_id', 'customimagerecipe_id'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'ProjectPackage'
+        db.delete_table(u'orm_projectpackage')
+
+        # Removing M2M table for field recipe_includes on 'ProjectPackage'
+        db.delete_table(db.shorten_name(u'orm_projectpackage_recipe_includes'))
+
+        # Removing M2M table for field recipe_excludes on 'ProjectPackage'
+        db.delete_table(db.shorten_name(u'orm_projectpackage_recipe_excludes'))
+
+        # Removing M2M table for field recipe_appends on 'ProjectPackage'
+        db.delete_table(db.shorten_name(u'orm_projectpackage_recipe_appends'))
+
+
+    models = {
+        u'orm.bitbakeversion': {
+            'Meta': {'object_name': 'BitbakeVersion'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'giturl': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.branch': {
+            'Meta': {'unique_together': "(('layer_source', 'name'), ('layer_source', 'up_id'))", 'object_name': 'Branch'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'True', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.build': {
+            'Meta': {'object_name': 'Build'},
+            'bitbake_version': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'build_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'completed_on': ('django.db.models.fields.DateTimeField', [], {}),
+            'cooker_log_path': ('django.db.models.fields.CharField', [], {'max_length': '500'}),
+            'distro': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'distro_version': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'machine': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '2'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'started_on': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        u'orm.buildartifact': {
+            'Meta': {'object_name': 'BuildArtifact'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        u'orm.customimagerecipe': {
+            'Meta': {'object_name': 'CustomImageRecipe', '_ormbases': [u'orm.Recipe']},
+            'base_recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'based_on_recipe'", 'to': u"orm['orm.Recipe']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            u'recipe_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['orm.Recipe']", 'unique': 'True', 'primary_key': 'True'})
+        },
+        u'orm.helptext': {
+            'Meta': {'object_name': 'HelpText'},
+            'area': ('django.db.models.fields.IntegerField', [], {}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'helptext_build'", 'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'text': ('django.db.models.fields.TextField', [], {})
+        },
+        u'orm.layer': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'), ('layer_source', 'name'))", 'object_name': 'Layer'},
+            'description': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_index_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'summary': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'vcs_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_file_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_tree_base_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'}),
+            'vcs_web_url': ('django.db.models.fields.URLField', [], {'default': 'None', 'max_length': '200', 'null': 'True'})
+        },
+        u'orm.layer_version': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Layer_Version'},
+            'branch': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'layer_version_build'", 'null': 'True', 'to': u"orm['orm.Build']"}),
+            'commit': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'dirpath': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'layer_version_layer'", 'to': u"orm['orm.Layer']"}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'local_path': ('django.db.models.fields.FilePathField', [], {'default': "'/'", 'max_length': '1024'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Project']", 'null': 'True'}),
+            'up_branch': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.Branch']", 'null': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.layersource': {
+            'Meta': {'unique_together': "(('sourcetype', 'apiurl'),)", 'object_name': 'LayerSource'},
+            'apiurl': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '63'}),
+            'sourcetype': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.layerversiondependency': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'LayerVersionDependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependees'", 'to': u"orm['orm.Layer_Version']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'dependencies'", 'to': u"orm['orm.Layer_Version']"}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.logmessage': {
+            'Meta': {'object_name': 'LogMessage'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'lineno': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'pathname': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Task']", 'null': 'True', 'blank': 'True'})
+        },
+        u'orm.machine': {
+            'Meta': {'unique_together': "(('layer_source', 'up_id'),)", 'object_name': 'Machine'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'})
+        },
+        u'orm.package': {
+            'Meta': {'object_name': 'Package'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']", 'null': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'installed_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'installed_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Recipe']", 'null': 'True'}),
+            'revision': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.package_dependency': {
+            'Meta': {'object_name': 'Package_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_target'", 'to': u"orm['orm.Package']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_dependencies_source'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']", 'null': 'True'})
+        },
+        u'orm.package_file': {
+            'Meta': {'object_name': 'Package_File'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildfilelist_package'", 'to': u"orm['orm.Package']"}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'size': ('django.db.models.fields.IntegerField', [], {})
+        },
+        u'orm.project': {
+            'Meta': {'object_name': 'Project'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']", 'null': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']", 'null': 'True'}),
+            'short_description': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user_id': ('django.db.models.fields.IntegerField', [], {'null': 'True'})
+        },
+        u'orm.projectlayer': {
+            'Meta': {'unique_together': "(('project', 'layercommit'),)", 'object_name': 'ProjectLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layercommit': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Layer_Version']", 'null': 'True'}),
+            'optional': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"})
+        },
+        u'orm.projectpackage': {
+            'Meta': {'object_name': 'ProjectPackage', '_ormbases': [u'orm.Package']},
+            u'package_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['orm.Package']", 'unique': 'True', 'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'recipe_appends': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'appends_set'", 'null': 'True', 'to': u"orm['orm.CustomImageRecipe']"}),
+            'recipe_excludes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'excludes_set'", 'null': 'True', 'to': u"orm['orm.CustomImageRecipe']"}),
+            'recipe_includes': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'includes_set'", 'null': 'True', 'to': u"orm['orm.CustomImageRecipe']"})
+        },
+        u'orm.projecttarget': {
+            'Meta': {'object_name': 'ProjectTarget'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+        },
+        u'orm.projectvariable': {
+            'Meta': {'object_name': 'ProjectVariable'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Project']"}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.recipe': {
+            'Meta': {'unique_together': "(('layer_version', 'file_path', 'pathflags'),)", 'object_name': 'Recipe'},
+            'bugtracker': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'file_path': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['orm.LayerSource']", 'null': 'True'}),
+            'layer_version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'recipe_layer_version'", 'to': u"orm['orm.Layer_Version']"}),
+            'license': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'pathflags': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
+            'section': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'up_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'up_id': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
+        },
+        u'orm.recipe_dependency': {
+            'Meta': {'object_name': 'Recipe_Dependency'},
+            'dep_type': ('django.db.models.fields.IntegerField', [], {}),
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_depends'", 'to': u"orm['orm.Recipe']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'r_dependencies_recipe'", 'to': u"orm['orm.Recipe']"})
+        },
+        u'orm.release': {
+            'Meta': {'object_name': 'Release'},
+            'bitbake_version': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.BitbakeVersion']"}),
+            'branch_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50'}),
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'helptext': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'})
+        },
+        u'orm.releasedefaultlayer': {
+            'Meta': {'object_name': 'ReleaseDefaultLayer'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.releaselayersourcepriority': {
+            'Meta': {'unique_together': "(('release', 'layer_source'),)", 'object_name': 'ReleaseLayerSourcePriority'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'layer_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.LayerSource']"}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'release': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Release']"})
+        },
+        u'orm.target': {
+            'Meta': {'object_name': 'Target'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Build']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'image_size': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_image': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'license_manifest_path': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
+            'target': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'task': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'})
+        },
+        u'orm.target_file': {
+            'Meta': {'object_name': 'Target_File'},
+            'directory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'directory_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'group': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inodetype': ('django.db.models.fields.IntegerField', [], {}),
+            'owner': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'path': ('django.db.models.fields.FilePathField', [], {'max_length': '100'}),
+            'permission': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+            'size': ('django.db.models.fields.IntegerField', [], {}),
+            'sym_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'symlink_set'", 'null': 'True', 'to': u"orm['orm.Target_File']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_image_file': {
+            'Meta': {'object_name': 'Target_Image_File'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '254'}),
+            'file_size': ('django.db.models.fields.IntegerField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.target_installed_package': {
+            'Meta': {'object_name': 'Target_Installed_Package'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'buildtargetlist_package'", 'to': u"orm['orm.Package']"}),
+            'target': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['orm.Target']"})
+        },
+        u'orm.task': {
+            'Meta': {'ordering': "('order', 'recipe')", 'unique_together': "(('build', 'recipe', 'task_name'),)", 'object_name': 'Task'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_build'", 'to': u"orm['orm.Build']"}),
+            'cpu_usage': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', 'decimal_places': '2'}),
+            'disk_io': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'elapsed_time': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '8', 'decimal_places': '2'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'logfile': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'message': ('django.db.models.fields.CharField', [], {'max_length': '240'}),
+            'order': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'outcome': ('django.db.models.fields.IntegerField', [], {'default': '-1'}),
+            'path_to_sstate_obj': ('django.db.models.fields.FilePathField', [], {'max_length': '500', 'blank': 'True'}),
+            'recipe': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'to': u"orm['orm.Recipe']"}),
+            'script_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'source_url': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'}),
+            'sstate_checksum': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+            'sstate_result': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'task_executed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'task_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'work_directory': ('django.db.models.fields.FilePathField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        u'orm.task_dependency': {
+            'Meta': {'object_name': 'Task_Dependency'},
+            'depends_on': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_depends'", 'to': u"orm['orm.Task']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_dependencies_task'", 'to': u"orm['orm.Task']"})
+        },
+        u'orm.toastersetting': {
+            'Meta': {'object_name': 'ToasterSetting'},
+            'helptext': ('django.db.models.fields.TextField', [], {}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '63'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        u'orm.variable': {
+            'Meta': {'object_name': 'Variable'},
+            'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variable_build'", 'to': u"orm['orm.Build']"}),
+            'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'human_readable_name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'variable_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'variable_value': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'orm.variablehistory': {
+            'Meta': {'object_name': 'VariableHistory'},
+            'file_name': ('django.db.models.fields.FilePathField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'line_number': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'operation': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'value': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'variable': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'vhistory'", 'to': u"orm['orm.Variable']"})
+        }
+    }
+
+    complete_apps = ['orm']
\ No newline at end of file
-- 
2.1.4



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

* Re: [PATCH 00/34] michaelw/toaster/ic-6
  2015-12-09 13:42   ` Michael Wood
@ 2015-12-10 15:37     ` Michael Wood
  0 siblings, 0 replies; 39+ messages in thread
From: Michael Wood @ 2015-12-10 15:37 UTC (permalink / raw)
  To: toaster

On 09/12/15 13:42, Michael Wood wrote:
> On 09/12/15 12:13, Barros Pena, Belen wrote:
>>
>> On 08/12/2015 21:15, "toaster-bounces@yoctoproject.org on behalf of
>> Michael Wood" <toaster-bounces@yoctoproject.org on behalf of
>> michael.g.wood@intel.com> wrote:
>>
>>> This is the work to introduce the image customisation feature to 
>>> Toaster.
>>> As a version 1 this allows basic adding and removing packages of a
>>> customised
>>> version of a pre-existing image recipe. To enable this feature run 
>>> toaster
>>> with the environment var set CUSTOM_IMAGE=1
>>>
>>> http://git.yoctoproject.org/cgit/cgit.cgi/poky-contrib/log/?h=michaelw/toa 
>>>
>>> ster/ic-6
>> Tried this, but Toaster didn't want to start. I got the same problem 
>> when
>> starting from an existing database, when starting from a clean database
>> and with a completely clean clone. I might be doing something silly: not
>> sure.
>>
>> Output below:
>>
>> (venv)yocto@icarus:~/ic6/build$ . ../bitbake/bin/toaster
>>
>> The system will start.
>> Syncing...
>> Creating tables ...
>> Creating table auth_permission
>> Creating table auth_group_permissions
>> Creating table auth_group
>> Creating table auth_user_groups
>> Creating table auth_user_user_permissions
>> Creating table auth_user
>> Creating table django_content_type
>> Creating table django_session
>> Creating table django_admin_log
>> Creating table south_migrationhistory
>> Installing custom SQL ...
>> Installing indexes ...
>> Installed 0 object(s) from 0 fixture(s)
>>
>>
>> Synced:
>>   > django.contrib.auth
>>   > django.contrib.contenttypes
>>   > django.contrib.messages
>>   > django.contrib.sessions
>>   > django.contrib.admin
>>   > django.contrib.staticfiles
>>   > django.contrib.humanize
>>   > south
>>
>>
>> Not synced (use migrations):
>>   - bldcontrol
>>   - orm
>> (use ./manage.py migrate to migrate these)
>> Running migrations for orm:
>>   - Migrating forwards to
>> 0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id 
>>
>> .
>>   > orm:0001_initial
>>   > orm:0002_auto__add_field_build_timespent
>>   > orm:0003_timespent
>>   - Migration 'orm:0003_timespent' is marked for no-dry-run.
>>   > orm:0004_auto__add_field_package_installed_name
>>   >
>> orm:0005_auto__add_target_image_file__add_target_file__add_field_variablehi 
>>
>> stor
>>   >
>> orm:0006_auto__add_field_target_image_size__add_field_target_license_manife 
>>
>> st_p
>>   > orm:0007_auto__add_helptext
>>   >
>> orm:0008_auto__chg_field_variablehistory_operation__chg_field_recipe_descri 
>>
>> ptio
>>   >
>> orm:0009_auto__add_projectvariable__add_projectlayer__add_projecttarget__ad 
>>
>> d_pr
>>   >
>> orm:0010_auto__add_field_project_branch__add_field_project_short_descriptio 
>>
>> n__a
>>   > orm:0011_auto__add_field_projectlayer_dirpath
>>   >
>> orm:0012_auto__add_field_projectlayer_optional__add_field_projecttarget_tas 
>>
>> k
>>   >
>> orm:0013_auto__add_release__add_layerversiondependency__add_unique_layerver 
>>
>> sion
>>   >
>> orm:0014_auto__chg_field_package_summary__chg_field_layer_summary__chg_fiel 
>>
>> d_re
>>   >
>> orm:0015_auto__add_field_layer_vcs_web_url__add_field_layer_vcs_web_tree_ba 
>>
>> se_u
>>   >
>> orm:0016_auto__add_field_release_helptext__chg_field_release_branch__add_in 
>>
>> dex_
>>   >
>> orm:0017_auto__del_toastersettingdefaultlayer__add_releaselayersourcepriori 
>>
>> ty__
>>   > orm:0018_auto__add_field_layer_version_project
>>   > orm:0019_auto__add_buildartifact
>>   >
>> orm:0020_auto__add_field_layer_version_local_path__add_field_recipe_pathfla 
>>
>> gs__
>>   >
>> orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version__ 
>>
>> chg_
>>   - Migration
>> 'orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version_ 
>>
>> _chg_' is marked for no-dry-run.
>>   >
>> orm:0022_auto__add_field_target_task__add_field_layer_version_local_path__d 
>>
>> el_f
>>   >
>> orm:0023_auto__del_field_build_warnings_no__del_field_build_errors_no__del_ 
>>
>> fiel
>>   > orm:0024_auto__add_field_recipe_is_image
>>   > orm:0025_auto__add_field_project_is_default
>>   > orm:0026_set_default_project
>>   - Migration 'orm:0026_set_default_project' is marked for no-dry-run.
>>   >
>> orm:0027_auto__add_customimagerecipe__add_unique_customimagerecipe_name_pro 
>>
>> ject
>>   > orm:0028_auto__chg_field_logmessage_message
>>   >
>> orm:0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_fi 
>>
>> eld_
>>   >
>> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip 
>>
>> e_id
>> FATAL ERROR - The following SQL query failed: DROP TABLE
>> "orm_customimagerecipe_packages";
>> The error was: no such table: orm_customimagerecipe_packages
>>   ! Error found during real run of migration! Aborting.
>>
>>
>>   ! Since you have a database that does not support running
>>   ! schema-altering statements in transactions, we have had
>>   ! to leave it in an interim state between migrations.
>>
>>
>> ! You *might* be able to recover with:   = CREATE TABLE
>> "orm_customimagerecipe_packages" ("id" integer NOT NULL PRIMARY KEY,
>> "customimagerecipe_id" integer NOT NULL, "package_id" integer NOT 
>> NULL) []
>>     = CREATE UNIQUE INDEX
>> "orm_customimagerecipe_packages_customimagerecipe_id__package_id" ON
>> "orm_customimagerecipe_packages"("customimagerecipe_id", 
>> "package_id"); []
>>     = CREATE UNIQUE INDEX "orm_customimagerecipe_name__project_id" ON
>> "orm_customimagerecipe"("name", "project_id"); []
>>
>>
>>   ! The South developers regret this has happened, and would
>>   ! like to gently persuade you to consider a slightly
>>   ! easier-to-deal-with DBMS (one that supports DDL transactions)
>>   ! NOTE: The error which caused the migration to fail is further up.
>> Error in migration:
>> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip 
>>
>> e_id
>> Traceback (most recent call last):
>>    File "../bitbake/bin/../lib/toaster/manage.py", line 10, in <module>
>>      execute_from_command_line(sys.argv)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/__init__.py", line 399, in execute_from_command_line
>>      utility.execute()
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/__init__.py", line 392, in execute
>>      self.fetch_command(subcommand).run_from_argv(self.argv)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/base.py", line 242, in run_from_argv
>>      self.execute(*args, **options.__dict__)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/base.py", line 285, in execute
>>      output = self.handle(*args, **options)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/management 
>>
>> /commands/migrate.py", line 111, in handle
>>      ignore_ghosts = ignore_ghosts,
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> __init__.py", line 220, in migrate_app
>>      success = migrator.migrate_many(target, workplan, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 254, in migrate_many
>>      result = migrator.__class__.migrate_many(migrator, target, 
>> migrations,
>> database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 329, in migrate_many
>>      result = self.migrate(migration, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 133, in migrate
>>      result = self.run(migration, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 114, in run
>>      return self.run_migration(migration, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 84, in run_migration
>>      migration_function()
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 60, in <lambda>
>>      return (lambda: direction(orm))
>>    File
>> "/home/yocto/ic6/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_cu 
>>
>> stomimagerecipe_name__del_field_customimagerecipe_id.py", line 26, in
>> forwards
>> db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/sqlite3 
>>
>> .py", line 272, in delete_table
>>      generic.DatabaseOperations.delete_table(self, table_name, False)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic 
>>
>> .py", line 47, in _cache_clear
>>      return func(self, table, *args, **opts)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic 
>>
>> .py", line 388, in delete_table
>>      self.execute('DROP TABLE %s;' % params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic 
>>
>> .py", line 282, in execute
>>      cursor.execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/util.py", line 69, in execute
>>      return super(CursorDebugWrapper, self).execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/util.py", line 53, in execute
>>      return self.cursor.execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/utils. 
>>
>> py", line 99, in __exit__
>>      six.reraise(dj_exc_type, dj_exc_value, traceback)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/util.py", line 53, in execute
>>      return self.cursor.execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/sqlite3/base.py", line 450, in execute
>>      return Database.Cursor.execute(self, query, params)
>> django.db.utils.OperationalError: no such table:
>> orm_customimagerecipe_packages
>> Running migrations for orm:
>>   - Migrating forwards to
>> 0029_auto__del_field_customimagerecipe_name__del_field_customimagerecipe_id 
>>
>> .
>>   >
>> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip 
>>
>> e_id
>> FATAL ERROR - The following SQL query failed: DROP TABLE
>> "orm_customimagerecipe_packages";
>> The error was: no such table: orm_customimagerecipe_packages
>>   ! Error found during real run of migration! Aborting.
>>
>>
>>   ! Since you have a database that does not support running
>>   ! schema-altering statements in transactions, we have had
>>   ! to leave it in an interim state between migrations.
>>
>>
>> ! You *might* be able to recover with:   = CREATE TABLE
>> "orm_customimagerecipe_packages" ("id" integer NOT NULL PRIMARY KEY,
>> "customimagerecipe_id" integer NOT NULL, "package_id" integer NOT 
>> NULL) []
>>     = CREATE UNIQUE INDEX
>> "orm_customimagerecipe_packages_customimagerecipe_id__package_id" ON
>> "orm_customimagerecipe_packages"("customimagerecipe_id", 
>> "package_id"); []
>>     = CREATE UNIQUE INDEX "orm_customimagerecipe_name__project_id" ON
>> "orm_customimagerecipe"("name", "project_id"); []
>>
>>
>>   ! The South developers regret this has happened, and would
>>   ! like to gently persuade you to consider a slightly
>>   ! easier-to-deal-with DBMS (one that supports DDL transactions)
>>   ! NOTE: The error which caused the migration to fail is further up.
>> Error in migration:
>> orm:0029_auto__del_field_customimagerecipe_name__del_field_customimagerecip 
>>
>> e_id
>> Traceback (most recent call last):
>>    File "../bitbake/bin/../lib/toaster/manage.py", line 10, in <module>
>>      execute_from_command_line(sys.argv)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/__init__.py", line 399, in execute_from_command_line
>>      utility.execute()
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/__init__.py", line 392, in execute
>>      self.fetch_command(subcommand).run_from_argv(self.argv)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/base.py", line 242, in run_from_argv
>>      self.execute(*args, **options.__dict__)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/core/mana 
>>
>> gement/base.py", line 285, in execute
>>      output = self.handle(*args, **options)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/management 
>>
>> /commands/migrate.py", line 111, in handle
>>      ignore_ghosts = ignore_ghosts,
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> __init__.py", line 220, in migrate_app
>>      success = migrator.migrate_many(target, workplan, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 254, in migrate_many
>>      result = migrator.__class__.migrate_many(migrator, target, 
>> migrations,
>> database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 329, in migrate_many
>>      result = self.migrate(migration, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 133, in migrate
>>      result = self.run(migration, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 114, in run
>>      return self.run_migration(migration, database)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 84, in run_migration
>>      migration_function()
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/migration/ 
>>
>> migrators.py", line 60, in <lambda>
>>      return (lambda: direction(orm))
>>    File
>> "/home/yocto/ic6/bitbake/lib/toaster/orm/migrations/0029_auto__del_field_cu 
>>
>> stomimagerecipe_name__del_field_customimagerecipe_id.py", line 26, in
>> forwards
>> db.delete_table(db.shorten_name(u'orm_customimagerecipe_packages'))
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/sqlite3 
>>
>> .py", line 272, in delete_table
>>      generic.DatabaseOperations.delete_table(self, table_name, False)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic 
>>
>> .py", line 47, in _cache_clear
>>      return func(self, table, *args, **opts)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic 
>>
>> .py", line 388, in delete_table
>>      self.execute('DROP TABLE %s;' % params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/south/db/generic 
>>
>> .py", line 282, in execute
>>      cursor.execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/util.py", line 69, in execute
>>      return super(CursorDebugWrapper, self).execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/util.py", line 53, in execute
>>      return self.cursor.execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/utils. 
>>
>> py", line 99, in __exit__
>>      six.reraise(dj_exc_type, dj_exc_value, traceback)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/util.py", line 53, in execute
>>      return self.cursor.execute(sql, params)
>>    File
>> "/home/yocto/master/venv/local/lib/python2.7/site-packages/django/db/backen 
>>
>> ds/sqlite3/base.py", line 450, in execute
>>      return Database.Cursor.execute(self, query, params)
>> django.db.utils.OperationalError: no such table:
>> orm_customimagerecipe_packages
>>
>>
>> Error on orm migration, rolling back...
>> Running migrations for orm:
>>   - Migrating backwards to just after 0001_initial.
>>   <
>> orm:0029_auto__add_projectpackage__del_field_customimagerecipe_name__del_fi 
>>
>> eld_
>>     (faked)
>>   < orm:0028_auto__chg_field_logmessage_message
>>     (faked)
>>   <
>> orm:0027_auto__add_customimagerecipe__add_unique_customimagerecipe_name_pro 
>>
>> ject
>>     (faked)
>>   < orm:0026_set_default_project
>>     (faked)
>>   < orm:0025_auto__add_field_project_is_default
>>     (faked)
>>   < orm:0024_auto__add_field_recipe_is_image
>>     (faked)
>>   <
>> orm:0023_auto__del_field_build_warnings_no__del_field_build_errors_no__del_ 
>>
>> fiel
>>     (faked)
>>   <
>> orm:0022_auto__add_field_target_task__add_field_layer_version_local_path__d 
>>
>> el_f
>>     (faked)
>>   <
>> orm:0021_auto__chg_field_build_project__chg_field_project_bitbake_version__ 
>>
>> chg_
>>     (faked)
>>   <
>> orm:0020_auto__add_field_layer_version_local_path__add_field_recipe_pathfla 
>>
>> gs__
>>     (faked)
>>   < orm:0019_auto__add_buildartifact
>>     (faked)
>>   < orm:0018_auto__add_field_layer_version_project
>>     (faked)
>>   <
>> orm:0017_auto__del_toastersettingdefaultlayer__add_releaselayersourcepriori 
>>
>> ty__
>>     (faked)
>>   <
>> orm:0016_auto__add_field_release_helptext__chg_field_release_branch__add_in 
>>
>> dex_
>>     (faked)
>>   <
>> orm:0015_auto__add_field_layer_vcs_web_url__add_field_layer_vcs_web_tree_ba 
>>
>> se_u
>>     (faked)
>>   <
>> orm:0014_auto__chg_field_package_summary__chg_field_layer_summary__chg_fiel 
>>
>> d_re
>>     (faked)
>>   <
>> orm:0013_auto__add_release__add_layerversiondependency__add_unique_layerver 
>>
>> sion
>>     (faked)
>>   <
>> orm:0012_auto__add_field_projectlayer_optional__add_field_projecttarget_tas 
>>
>> k
>>     (faked)
>>   < orm:0011_auto__add_field_projectlayer_dirpath
>>     (faked)
>>   <
>> orm:0010_auto__add_field_project_branch__add_field_project_short_descriptio 
>>
>> n__a
>>     (faked)
>>   <
>> orm:0009_auto__add_projectvariable__add_projectlayer__add_projecttarget__ad 
>>
>> d_pr
>>     (faked)
>>   <
>> orm:0008_auto__chg_field_variablehistory_operation__chg_field_recipe_descri 
>>
>> ptio
>>     (faked)
>>   < orm:0007_auto__add_helptext
>>     (faked)
>>   <
>> orm:0006_auto__add_field_target_image_size__add_field_target_license_manife 
>>
>> st_p
>>     (faked)
>>   <
>> orm:0005_auto__add_target_image_file__add_target_file__add_field_variablehi 
>>
>> stor
>>     (faked)
>>   < orm:0004_auto__add_field_package_installed_name
>>     (faked)
>>   < orm:0003_timespent
>>     (faked)
>>   < orm:0002_auto__add_field_build_timespent
>>     (faked)
>> Failed start.
>>
>>
>>> Michael Wood (34):
>>>   toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe
>>>   toaster: models fall back to a sensible string for no vcs reference
>>>   toaster: ToasterTables simplify filter function move common part to
>>>     widget
>>>   toaster: tablejs Add an event handler to manually trigger a data
>>>     reload
>>>   toaster: orm Add sum of dependencies size function to
>>>     PackageDependencyManager
>>>   toaster: orm make CustomImageRecipe inherit from Recipe
>>>   toaster: orm: Add db migration for new CustomImageRecipe inheritance
>>>     change
>>>   toaster: orm Add ProjectPackage table
>>>   toaster: orm: Add db migration for new  ProjectPackages table
>>>   toaster: buildinfohelper Add the concept of ProjectPackages
>>>   toaster: orm add CustomImageRecipe generate contents function
>>>   toaster: move CustomImageRecipe generation to API entry point
>>>   toaster: views Add view to download custom recipe
>>>   toaster: tables Add table for Packages and update SelectPackagesTable
>>>   toaster: Continue front end features to custom image recipe page.
>>>   toaster: newcustomimage Move modal dialog out of newcustomimage
>>>     template
>>>   toaster: Add recipe details page
>>>   toaster: toastertable remove title from Show all in table
>>>   toaster: views xhr_customrecipe_packages clean up API
>>>   toaster: toastergui tests Update to reflect changes to
>>>     CustomImageRecipe
>>>   toaster: toastergui tests Add unit test for download custom recipe
>>>   toaster: orm get_project_layer_versions to return layer_version
>>>     objects
>>>   toaster: orm Add convenience method to get all pkgs in a
>>>     CustomImageRecipe
>>>   toaster: libtoaster Add createCustomRecipe method
>>>   toaster: newcustomimage_modal use libtoaster method for new
>>>     CustomRecipe
>>>   toaster: tables add recipe download link to CustomImagesTable
>>>   toaster: tables Change SelectPackagesTable to use ProjectPackage
>>>   toaster: API allow CustomImageRecipe to be updated after creation
>>>   toaster: xhr_customrecipe_id  change to use ProjectPackage
>>>   toaster: xhr_customrecipe_packages add GET info for package response
>>>   toaster: customrecipe Add further front end features using new API
>>>   toaster: toastergui tests Update package test to use ProjectPackage
>>>   toaster: tables SelectPackagesTable rename recipe_id to custrecipeid
>>>   toaster: toastergui tests Add addtional data to the setUp for new
>>>     tables
>>>
>>> bitbake/lib/bb/ui/buildinfohelper.py               | 113 +++++-
>>> .../toaster/bldcontrol/localhostbecontroller.py    |  29 +-
>>> ...del_field_customimagerecipe_name__del_field_.py | 433
>>> +++++++++++++++++++++
>>> ...erecipe_name__del_field_customimagerecipe_id.py | 379
>>> ++++++++++++++++++
>>> bitbake/lib/toaster/orm/models.py                  |  95 ++++-
>>> .../toaster/toastergui/static/js/customrecipe.js   | 176 ++++++++-
>>> .../lib/toaster/toastergui/static/js/libtoaster.js |  27 ++
>>> .../toaster/toastergui/static/js/newcustomimage.js |  49 ---
>>> .../toastergui/static/js/newcustomimage_modal.js   |  28 ++
>>> .../toaster/toastergui/static/js/recipedetails.js  |  52 +++
>>> bitbake/lib/toaster/toastergui/static/js/table.js  |   7 +
>>> bitbake/lib/toaster/toastergui/tables.py           | 239 +++++++++---
>>> bitbake/lib/toaster/toastergui/templates/base.html |   1 +
>>> .../toaster/toastergui/templates/customrecipe.html | 186 ++++++---
>>> .../toastergui/templates/newcustomimage.html       |  44 +--
>>> .../toastergui/templates/newcustomimage_modal.html |  33 ++
>>> .../toastergui/templates/pkg_add_rm_btn.html       |  35 +-
>>> .../toastergui/templates/recipedetails.html        | 180 +++++++++
>>> .../snippets/pkg_dependencies_popover.html         |  14 +
>>> .../toaster/toastergui/templates/toastertable.html |   2 +-
>>> bitbake/lib/toaster/toastergui/tests.py            | 145 +++++--
>>> bitbake/lib/toaster/toastergui/urls.py             |  17 +-
>>> bitbake/lib/toaster/toastergui/views.py            | 262 ++++++++++---
>>> bitbake/lib/toaster/toastergui/widgets.py          |   5 +-
>>> 24 files changed, 2171 insertions(+), 380 deletions(-)
>>> create mode 100644
>>> bitbake/lib/toaster/orm/migrations/0029_auto__add_projectpackage__del_fiel 
>>>
>>> d_customimagerecipe_name__del_field_.py
>>> create mode 100644
>>> bitbake/lib/toaster/orm/migrations/0029_auto__del_field_customimagerecipe_ 
>>>
>>> name__del_field_customimagerecipe_id.py
>>> delete mode 100644
>>> bitbake/lib/toaster/toastergui/static/js/newcustomimage.js
>>> create mode 100644
>>> bitbake/lib/toaster/toastergui/static/js/newcustomimage_modal.js
>>> create mode 100644
>>> bitbake/lib/toaster/toastergui/static/js/recipedetails.js
>>> create mode 100644
>>> bitbake/lib/toaster/toastergui/templates/newcustomimage_modal.html
>>> create mode 100644
>>> bitbake/lib/toaster/toastergui/templates/recipedetails.html
>>> create mode 100644
>>> bitbake/lib/toaster/toastergui/templates/snippets/pkg_dependencies_popover 
>>>
>>> .html
>>>
>>> -- 
>>> 2.1.4
>>>
>>> -- 
>>> _______________________________________________
>>> toaster mailing list
>>> toaster@yoctoproject.org
>>> https://lists.yoctoproject.org/listinfo/toaster
>
> The migration generated seemed to be wrong, so it might have been 
> because of a previous WIP migration that wasn't ever committed. I've 
> pushed a new migration to the michaelw/toaster/ic-6
>
> Thanks,
>
> Michael
>

This branch has now been re-based on top of the current toaster-next 
which includes the django 1.8 upgrade patches. For the latest patches 
please see poky-contrib michaelw/toaster/ic-6





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

end of thread, other threads:[~2015-12-10 15:37 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-08 21:15 [PATCH 00/34] michaelw/toaster/ic-6 Michael Wood
2015-12-08 21:15 ` [PATCH 01/34] toaster: localhostbecontroller CustomRecipe now base_recipe is Recipe Michael Wood
2015-12-08 21:15 ` [PATCH 02/34] toaster: models fall back to a sensible string for no vcs reference Michael Wood
2015-12-08 21:15 ` [PATCH 03/34] toaster: ToasterTables simplify filter function move common part to widget Michael Wood
2015-12-08 21:15 ` [PATCH 04/34] toaster: tablejs Add an event handler to manually trigger a data reload Michael Wood
2015-12-08 21:15 ` [PATCH 05/34] toaster: orm Add sum of dependencies size function to PackageDependencyManager Michael Wood
2015-12-08 21:15 ` [PATCH 06/34] toaster: orm make CustomImageRecipe inherit from Recipe Michael Wood
2015-12-08 21:15 ` [PATCH 07/34] toaster: orm: Add db migration for new CustomImageRecipe inheritance change Michael Wood
2015-12-08 21:15 ` [PATCH 08/34] toaster: orm Add ProjectPackage table Michael Wood
2015-12-08 21:15 ` [PATCH 09/34] toaster: orm: Add db migration for new ProjectPackages table Michael Wood
2015-12-09 13:47   ` [PATCH v2] " Michael Wood
2015-12-08 21:15 ` [PATCH 10/34] toaster: buildinfohelper Add the concept of ProjectPackages Michael Wood
2015-12-08 21:15 ` [PATCH 11/34] toaster: orm add CustomImageRecipe generate contents function Michael Wood
2015-12-08 21:15 ` [PATCH 12/34] toaster: move CustomImageRecipe generation to API entry point Michael Wood
2015-12-08 21:15 ` [PATCH 13/34] toaster: views Add view to download custom recipe Michael Wood
2015-12-08 21:16 ` [PATCH 14/34] toaster: tables Add table for Packages and update SelectPackagesTable Michael Wood
2015-12-08 21:16 ` [PATCH 15/34] toaster: Continue front end features to custom image recipe page Michael Wood
2015-12-08 21:16 ` [PATCH 16/34] toaster: newcustomimage Move modal dialog out of newcustomimage template Michael Wood
2015-12-08 21:16 ` [PATCH 17/34] toaster: Add recipe details page Michael Wood
2015-12-08 21:16 ` [PATCH 18/34] toaster: toastertable remove title from Show all in table Michael Wood
2015-12-08 21:16 ` [PATCH 19/34] toaster: views xhr_customrecipe_packages clean up API Michael Wood
2015-12-08 21:16 ` [PATCH 20/34] toaster: toastergui tests Update to reflect changes to CustomImageRecipe Michael Wood
2015-12-08 21:16 ` [PATCH 21/34] toaster: toastergui tests Add unit test for download custom recipe Michael Wood
2015-12-08 21:16 ` [PATCH 22/34] toaster: orm get_project_layer_versions to return layer_version objects Michael Wood
2015-12-08 21:16 ` [PATCH 23/34] toaster: orm Add convenience method to get all pkgs in a CustomImageRecipe Michael Wood
2015-12-08 21:16 ` [PATCH 24/34] toaster: libtoaster Add createCustomRecipe method Michael Wood
2015-12-08 21:16 ` [PATCH 25/34] toaster: newcustomimage_modal use libtoaster method for new CustomRecipe Michael Wood
2015-12-08 21:16 ` [PATCH 26/34] toaster: tables add recipe download link to CustomImagesTable Michael Wood
2015-12-08 21:16 ` [PATCH 27/34] toaster: tables Change SelectPackagesTable to use ProjectPackage Michael Wood
2015-12-08 21:16 ` [PATCH 28/34] toaster: API allow CustomImageRecipe to be updated after creation Michael Wood
2015-12-08 21:16 ` [PATCH 29/34] toaster: xhr_customrecipe_id change to use ProjectPackage Michael Wood
2015-12-08 21:16 ` [PATCH 30/34] toaster: xhr_customrecipe_packages add GET info for package response Michael Wood
2015-12-08 21:16 ` [PATCH 31/34] toaster: customrecipe Add further front end features using new API Michael Wood
2015-12-08 21:16 ` [PATCH 32/34] toaster: toastergui tests Update package test to use ProjectPackage Michael Wood
2015-12-08 21:16 ` [PATCH 33/34] toaster: tables SelectPackagesTable rename recipe_id to custrecipeid Michael Wood
2015-12-08 21:16 ` [PATCH 34/34] toaster: toastergui tests Add addtional data to the setUp for new tables Michael Wood
2015-12-09 12:13 ` [PATCH 00/34] michaelw/toaster/ic-6 Barros Pena, Belen
2015-12-09 13:42   ` Michael Wood
2015-12-10 15:37     ` Michael Wood

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