← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pappacena/launchpad:bugtask-oci-project into launchpad:master

 

Thiago F. Pappacena has proposed merging ~pappacena/launchpad:bugtask-oci-project into launchpad:master with ~pappacena/launchpad:stormify-bug-task as a prerequisite.

Commit message:
Mapping the support for ociproject on BugTask.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/393570
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:bugtask-oci-project into launchpad:master.
diff --git a/database/schema/security.cfg b/database/schema/security.cfg
index 11aba6f..e9829ea 100644
--- a/database/schema/security.cfg
+++ b/database/schema/security.cfg
@@ -746,6 +746,8 @@ public.bugtask                                  = SELECT, UPDATE
 public.bugtaskflat                              = SELECT
 public.distribution                             = SELECT
 public.distroseries                             = SELECT
+public.ociproject                               = SELECT
+public.ociprojectname                           = SELECT
 public.potemplate                               = SELECT, UPDATE
 public.product                                  = SELECT
 public.productseries                            = SELECT
diff --git a/lib/lp/bugs/interfaces/bugsummary.py b/lib/lp/bugs/interfaces/bugsummary.py
index 577e986..3822f34 100644
--- a/lib/lp/bugs/interfaces/bugsummary.py
+++ b/lib/lp/bugs/interfaces/bugsummary.py
@@ -1,4 +1,4 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """BugSummary interfaces."""
@@ -9,7 +9,6 @@ __all__ = [
     'IBugSummaryDimension',
     ]
 
-
 from zope.interface import Interface
 from zope.schema import (
     Bool,
@@ -27,6 +26,7 @@ from lp.bugs.interfaces.bugtask import (
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.milestone import IMilestone
+from lp.registry.interfaces.ociproject import IOCIProject
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.productseries import IProductSeries
@@ -58,6 +58,9 @@ class IBugSummary(Interface):
     sourcepackagename_id = Int(readonly=True)
     sourcepackagename = Object(ISourcePackageName, readonly=True)
 
+    ociproject_id = Int(readonly=True)
+    ociproject = Object(IOCIProject, readonly=True)
+
     milestone_id = Int(readonly=True)
     milestone = Object(IMilestone, readonly=True)
 
diff --git a/lib/lp/bugs/model/bugsummary.py b/lib/lp/bugs/model/bugsummary.py
index 2de0ad1..f01134e 100644
--- a/lib/lp/bugs/model/bugsummary.py
+++ b/lib/lp/bugs/model/bugsummary.py
@@ -1,4 +1,4 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """BugSummary Storm database classes."""
@@ -76,6 +76,9 @@ class BugSummary(Storm):
     sourcepackagename_id = Int(name='sourcepackagename')
     sourcepackagename = Reference(sourcepackagename_id, SourcePackageName.id)
 
+    ociproject_id = Int(name='ociproject')
+    ociproject = Reference(ociproject_id, 'OCIProject.id')
+
     milestone_id = Int(name='milestone')
     milestone = Reference(milestone_id, Milestone.id)
 
diff --git a/lib/lp/bugs/model/bugtask.py b/lib/lp/bugs/model/bugtask.py
index 87f4e9f..11f04f6 100644
--- a/lib/lp/bugs/model/bugtask.py
+++ b/lib/lp/bugs/model/bugtask.py
@@ -103,6 +103,7 @@ from lp.registry.interfaces.distributionsourcepackage import (
 from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.milestone import IMilestoneSet
 from lp.registry.interfaces.milestonetag import IProjectGroupMilestoneTag
+from lp.registry.interfaces.ociproject import IOCIProject
 from lp.registry.interfaces.person import (
     validate_person,
     validate_public_person,
@@ -183,7 +184,7 @@ def bugtask_sort_key(bugtask):
 
 
 def bug_target_from_key(product, productseries, distribution, distroseries,
-                        sourcepackagename):
+                        sourcepackagename, ociproject):
     """Returns the IBugTarget defined by the given DB column values."""
     if product:
         return product
@@ -201,6 +202,8 @@ def bug_target_from_key(product, productseries, distribution, distroseries,
                 sourcepackagename)
         else:
             return distroseries
+    elif ociproject:
+        return ociproject
     else:
         raise AssertionError("Unable to determine bugtask target.")
 
@@ -213,6 +216,7 @@ def bug_target_to_key(target):
                 distribution=None,
                 distroseries=None,
                 sourcepackagename=None,
+                ociproject=None,
                 )
     if IProduct.providedBy(target):
         values['product'] = target
@@ -228,6 +232,8 @@ def bug_target_to_key(target):
     elif ISourcePackage.providedBy(target):
         values['distroseries'] = target.distroseries
         values['sourcepackagename'] = target.sourcepackagename
+    elif IOCIProject.providedBy(target):
+        values['ociproject'] = target
     else:
         raise AssertionError("Not an IBugTarget.")
     return values
@@ -422,6 +428,9 @@ class BugTask(StormBase):
     product_id = Int(name="product", allow_none=True)
     product = Reference(product_id, 'Product.id')
 
+    ociproject_id = Int(name="ociproject", allow_none=True)
+    ociproject = Reference(ociproject_id, 'OCIProject.id')
+
     productseries_id = Int(name="productseries", allow_none=True)
     productseries = Reference(productseries_id, 'ProductSeries.id')
 
@@ -541,7 +550,7 @@ class BugTask(StormBase):
         """See `IBugTask`."""
         return bug_target_from_key(
             self.product, self.productseries, self.distribution,
-            self.distroseries, self.sourcepackagename)
+            self.distroseries, self.sourcepackagename, self.ociproject)
 
     @property
     def related_tasks(self):
@@ -1612,13 +1621,14 @@ class BugTaskSet:
         values = [
             (bug, owner, key['product'], key['productseries'],
              key['distribution'], key['distroseries'],
-             key['sourcepackagename'], status, importance, assignee,
-             milestone)
+             key['sourcepackagename'], key['ociproject'],
+             status, importance, assignee, milestone)
             for key in target_keys]
         tasks = create(
             (BugTask.bug, BugTask.owner, BugTask.product,
              BugTask.productseries, BugTask.distribution,
-             BugTask.distroseries, BugTask.sourcepackagename, BugTask._status,
+             BugTask.distroseries, BugTask.sourcepackagename,
+             BugTask.ociproject, BugTask._status,
              BugTask.importance, BugTask.assignee, BugTask.milestone),
             values, get_objects=True)
 
diff --git a/lib/lp/bugs/model/bugtaskflat.py b/lib/lp/bugs/model/bugtaskflat.py
index bce3bc0..312b51a 100644
--- a/lib/lp/bugs/model/bugtaskflat.py
+++ b/lib/lp/bugs/model/bugtaskflat.py
@@ -1,4 +1,4 @@
-# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2012-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -50,6 +50,8 @@ class BugTaskFlat(Storm):
     sourcepackagename_id = Int(name='sourcepackagename')
     sourcepackagename = Reference(
         sourcepackagename_id, 'SourcePackageName.id')
+    ociproject_id = Int(name='ociproject')
+    ociproject = Reference(ociproject_id, 'OCIProject.id')
     status = EnumCol(schema=(BugTaskStatus, BugTaskStatusSearch))
     importance = EnumCol(schema=BugTaskImportance)
     assignee_id = Int(name='assignee')
diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py
index 7466f7e..e392040 100644
--- a/lib/lp/bugs/model/tests/test_bugtask.py
+++ b/lib/lp/bugs/model/tests/test_bugtask.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -2037,6 +2037,14 @@ class TestAutoConfirmBugTasksFlagForDistributionSourcePackage(
         return self.factory.makeDistributionSourcePackage()
 
 
+class TestAutoConfirmBugTasksFlagForOCIProject(
+    TestAutoConfirmBugTasksFlagForDistribution):
+    """Tests for auto-confirming bug tasks."""
+
+    def makeTarget(self):
+        return self.factory.makeOCIProject()
+
+
 class TestAutoConfirmBugTasksTransitionToTarget(TestCaseWithFactory):
     """Tests for auto-confirming bug tasks."""
     # Tests for making sure that switching a task from one project that
@@ -2195,6 +2203,11 @@ class TestValidateTransitionToTarget(TestCaseWithFactory):
             self.factory.makeProduct(),
             self.factory.makeDistributionSourcePackage())
 
+    def test_product_to_ociproject_works(self):
+        self.assertTransitionWorks(
+            self.factory.makeProduct(),
+            self.factory.makeOCIProject())
+
     def test_distribution_to_distribution_works(self):
         self.assertTransitionWorks(
             self.factory.makeDistribution(),
@@ -2421,13 +2434,18 @@ class TestTransitionToTarget(TestCaseWithFactory):
         self.assertEqual(milestone, task.milestone)
 
     def test_targetnamecache_updated(self):
-        new_product = self.factory.makeProduct()
-        task = self.factory.makeBugTask()
-        with person_logged_in(task.owner):
-            task.transitionToTarget(new_product, task.owner)
-        self.assertEqual(
-            new_product.bugtargetdisplayname,
-            removeSecurityProxy(task).targetnamecache)
+        new_targets = [
+            self.factory.makeProduct(),
+            self.factory.makeDistribution(),
+            self.factory.makeOCIProject()]
+        # Test it with a different target types.
+        for new_target in new_targets:
+            task = self.factory.makeBugTask()
+            with person_logged_in(task.owner):
+                task.transitionToTarget(new_target, task.owner)
+            self.assertEqual(
+                new_target.bugtargetdisplayname,
+                removeSecurityProxy(task).targetnamecache)
 
     def test_cached_recipients_cleared(self):
         # The bug's notification recipients caches are cleared when
@@ -2606,6 +2624,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
                 distribution=None,
                 distroseries=None,
                 sourcepackagename=None,
+                ociproject=None,
                 ))
 
     def test_productseries(self):
@@ -2618,6 +2637,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
                 distribution=None,
                 distroseries=None,
                 sourcepackagename=None,
+                ociproject=None,
                 ))
 
     def test_distribution(self):
@@ -2630,6 +2650,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
                 distribution=distro,
                 distroseries=None,
                 sourcepackagename=None,
+                ociproject=None,
                 ))
 
     def test_distroseries(self):
@@ -2642,6 +2663,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
                 distribution=None,
                 distroseries=distroseries,
                 sourcepackagename=None,
+                ociproject=None,
                 ))
 
     def test_distributionsourcepackage(self):
@@ -2654,6 +2676,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
                 distribution=dsp.distribution,
                 distroseries=None,
                 sourcepackagename=dsp.sourcepackagename,
+                ociproject=None,
                 ))
 
     def test_sourcepackage(self):
@@ -2666,6 +2689,20 @@ class TestBugTargetKeys(TestCaseWithFactory):
                 distribution=None,
                 distroseries=sp.distroseries,
                 sourcepackagename=sp.sourcepackagename,
+                ociproject=None,
+                ))
+
+    def test_ociproject(self):
+        ociproject = self.factory.makeOCIProject()
+        self.assertTargetKeyWorks(
+            ociproject,
+            dict(
+                product=None,
+                productseries=None,
+                distribution=None,
+                distroseries=None,
+                sourcepackagename=None,
+                ociproject=ociproject,
                 ))
 
     def test_no_key_for_non_targets(self):
@@ -2674,7 +2711,8 @@ class TestBugTargetKeys(TestCaseWithFactory):
 
     def test_no_target_for_bad_keys(self):
         self.assertRaises(
-            AssertionError, bug_target_from_key, None, None, None, None, None)
+            AssertionError, bug_target_from_key,
+            None, None, None, None, None, None)
 
 
 class ValidateTargetMixin:
@@ -2761,6 +2799,23 @@ class TestValidateTarget(TestCaseWithFactory, ValidateTargetMixin):
             % p.displayname,
             validate_target, task.bug, p)
 
+    def test_new_ociproject_is_allowed(self):
+        # A new oci project not on the bug is OK.
+        p1 = self.factory.makeOCIProject()
+        task = self.factory.makeBugTask(target=p1)
+        p2 = self.factory.makeOCIProject()
+        validate_target(task.bug, p2)
+
+    def test_same_ociproject_is_forbidden(self):
+        # An oci project with an existing task is not.
+        p = self.factory.makeOCIProject()
+        task = self.factory.makeBugTask(target=p)
+        self.assertRaisesWithContent(
+            IllegalTarget,
+            "A fix for this bug has already been requested for %s"
+            % p.bugtargetdisplayname,
+            validate_target, task.bug, p)
+
     def test_new_distribution_is_allowed(self):
         # A new distribution not on the bug is OK.
         d1 = self.factory.makeDistribution()
@@ -2914,6 +2969,12 @@ class TestValidateNewTarget(TestCaseWithFactory, ValidateTargetMixin):
         # Used for ValidateTargetMixin.
         return validate_new_target
 
+    def test_ociprojects_are_ok(self):
+        p1 = self.factory.makeOCIProject()
+        task = self.factory.makeBugTask(target=p1)
+        p2 = self.factory.makeOCIProject()
+        validate_new_target(task.bug, p2)
+
     def test_products_are_ok(self):
         p1 = self.factory.makeProduct()
         task = self.factory.makeBugTask(target=p1)
@@ -3028,6 +3089,19 @@ class TestBugTaskUserHasBugSupervisorPrivileges(TestCaseWithFactory):
         self.assertTrue(
             bugtask.userHasBugSupervisorPrivileges(bugsupervisor))
 
+    def test_ociproject_pillar_bug_supervisor(self):
+        # The pillar bug supervisor has privileges.
+        bugsupervisor = self.factory.makePerson()
+        someone_else = self.factory.makePerson()
+        target = self.factory.makeOCIProject()
+        with admin_logged_in():
+            target.pillar.bug_supervisor = bugsupervisor
+        bugtask = self.factory.makeBugTask(target=target)
+        self.assertTrue(
+            bugtask.userHasBugSupervisorPrivileges(bugsupervisor))
+        self.assertFalse(
+            bugtask.userHasBugSupervisorPrivileges(someone_else))
+
     def test_productseries_driver_is_allowed(self):
         # The series driver has privileges.
         series = self.factory.makeProductSeries()
@@ -3074,6 +3148,10 @@ class TestBugTaskUserHasBugSupervisorPrivilegesContext(TestCaseWithFactory):
         distribution = self.factory.makeDistribution()
         self.assert_userHasBugSupervisorPrivilegesContext(distribution)
 
+    def test_ociproject(self):
+        ociproject = self.factory.makeOCIProject()
+        self.assert_userHasBugSupervisorPrivilegesContext(ociproject)
+
     def test_distributionsourcepackage(self):
         dsp = self.factory.makeDistributionSourcePackage()
         self.assert_userHasBugSupervisorPrivilegesContext(dsp)
diff --git a/lib/lp/bugs/scripts/bugsummaryrebuild.py b/lib/lp/bugs/scripts/bugsummaryrebuild.py
index ab89e7b..fe11cb7 100644
--- a/lib/lp/bugs/scripts/bugsummaryrebuild.py
+++ b/lib/lp/bugs/scripts/bugsummaryrebuild.py
@@ -35,6 +35,7 @@ from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.series import ISeriesMixin
 from lp.registry.model.distribution import Distribution
 from lp.registry.model.distroseries import DistroSeries
+from lp.registry.model.ociproject import OCIProject
 from lp.registry.model.product import Product
 from lp.registry.model.productseries import ProductSeries
 from lp.registry.model.sourcepackagename import SourcePackageName
@@ -63,7 +64,8 @@ def get_bugsummary_targets():
     return set(IStore(RawBugSummary).find(
         (RawBugSummary.product_id, RawBugSummary.productseries_id,
          RawBugSummary.distribution_id, RawBugSummary.distroseries_id,
-         RawBugSummary.sourcepackagename_id)).config(distinct=True))
+         RawBugSummary.sourcepackagename_id, RawBugSummary.ociproject_id
+         )).config(distinct=True))
 
 
 def get_bugtask_targets():
@@ -71,24 +73,26 @@ def get_bugtask_targets():
     new_targets = set(IStore(BugTask).find(
         (BugTask.product_id, BugTask.productseries_id,
          BugTask.distribution_id, BugTask.distroseries_id,
-         BugTask.sourcepackagename_id)).config(distinct=True))
+         BugTask.sourcepackagename_id, BugTask.ociproject_id
+         )).config(distinct=True))
     # BugSummary counts package tasks in the packageless totals, so
     # ensure that there's also a packageless total for each distro(series).
     new_targets.update(set(
-        (p, ps, d, ds, None) for (p, ps, d, ds, spn) in new_targets))
+        (p, ps, d, ds, None, ocip)
+        for (p, ps, d, ds, spn, ocip) in new_targets))
     return new_targets
 
 
-def load_target(pid, psid, did, dsid, spnid):
+def load_target(pid, psid, did, dsid, spnid, ociproject_id):
     store = IStore(Product)
-    p, ps, d, ds, spn = map(
+    p, ps, d, ds, spn, ociproject = map(
         lambda cls_id: (
             store.get(cls_id[0], cls_id[1]) if cls_id[1] is not None
             else None),
         zip((Product, ProductSeries, Distribution, DistroSeries,
-             SourcePackageName),
-            (pid, psid, did, dsid, spnid)))
-    return bug_target_from_key(p, ps, d, ds, spn)
+             SourcePackageName, OCIProject),
+            (pid, psid, did, dsid, spnid, ociproject_id)))
+    return bug_target_from_key(p, ps, d, ds, spn, ociproject)
 
 
 def format_target(target):
@@ -187,11 +191,11 @@ def apply_bugsummary_changes(target, added, updated, removed):
     target_key = tuple(map(
         bits.__getitem__,
         ('product_id', 'productseries_id', 'distribution_id',
-         'distroseries_id', 'sourcepackagename_id')))
+         'distroseries_id', 'sourcepackagename_id', 'ociproject_id')))
     target_cols = (
         RawBugSummary.product_id, RawBugSummary.productseries_id,
         RawBugSummary.distribution_id, RawBugSummary.distroseries_id,
-        RawBugSummary.sourcepackagename_id)
+        RawBugSummary.sourcepackagename_id, RawBugSummary.ociproject_id)
     key_cols = (
         RawBugSummary.status, RawBugSummary.milestone_id,
         RawBugSummary.importance, RawBugSummary.has_patch,
diff --git a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
index 5e4cb1e..f9c09fc 100644
--- a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
+++ b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
@@ -17,6 +17,7 @@ from lp.bugs.model.bugtask import (
     )
 from lp.registry.model.distribution import Distribution
 from lp.registry.model.distroseries import DistroSeries
+from lp.registry.model.ociproject import OCIProject
 from lp.registry.model.product import Product
 from lp.registry.model.productseries import ProductSeries
 from lp.registry.model.sourcepackagename import SourcePackageName
@@ -34,9 +35,10 @@ from lp.services.looptuner import (
 target_columns = (
     BugTask.product_id, BugTask.productseries_id, BugTask.distribution_id,
     BugTask.distroseries_id, BugTask.sourcepackagename_id,
-    BugTask.targetnamecache)
+    BugTask.ociproject_id, BugTask.targetnamecache)
 target_classes = (
-    Product, ProductSeries, Distribution, DistroSeries, SourcePackageName)
+    Product, ProductSeries, Distribution, DistroSeries, SourcePackageName,
+    OCIProject)
 
 
 @implementer(ITunableLoop)
diff --git a/lib/lp/bugs/scripts/tests/test_bugsummaryrebuild.py b/lib/lp/bugs/scripts/tests/test_bugsummaryrebuild.py
index 7c246c1..cb40a0f 100644
--- a/lib/lp/bugs/scripts/tests/test_bugsummaryrebuild.py
+++ b/lib/lp/bugs/scripts/tests/test_bugsummaryrebuild.py
@@ -1,4 +1,4 @@
-# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2012-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -48,18 +48,22 @@ def create_tasks(factory):
     sp = factory.makeSourcePackage(publish=True)
 
     bug = factory.makeBug(target=product)
+    ocip = factory.makeOCIProject()
+
     getUtility(IBugTaskSet).createManyTasks(
-        bug, bug.owner, [sp, sp.distribution_sourcepackage, ps])
+        bug, bug.owner, [sp, sp.distribution_sourcepackage, ps, ocip])
+
 
     # There'll be a target for each task, plus a packageless one for
     # each package task.
     expected_targets = [
-        (ps.product.id, None, None, None, None),
-        (None, ps.id, None, None, None),
-        (None, None, sp.distribution.id, None, None),
-        (None, None, sp.distribution.id, None, sp.sourcepackagename.id),
-        (None, None, None, sp.distroseries.id, None),
-        (None, None, None, sp.distroseries.id, sp.sourcepackagename.id)
+        (ps.product.id, None, None, None, None, None),
+        (None, ps.id, None, None, None, None),
+        (None, None, sp.distribution.id, None, None, None),
+        (None, None, sp.distribution.id, None, sp.sourcepackagename.id, None),
+        (None, None, None, sp.distroseries.id, None, None),
+        (None, None, None, sp.distroseries.id, sp.sourcepackagename.id, None),
+        (None, None, None, None, None, ocip.id)
         ]
     return expected_targets
 
@@ -146,9 +150,14 @@ class TestBugSummaryRebuild(TestCaseWithFactory):
 
     def test_script(self):
         product = self.factory.makeProduct()
+        ociproject = self.factory.makeOCIProject()
         self.factory.makeBug(target=product)
+        self.factory.makeBug(target=ociproject)
+
         self.assertEqual(0, get_bugsummary_rows(product).count())
+        self.assertEqual(0, get_bugsummary_rows(ociproject).count())
         self.assertEqual(1, get_bugsummaryjournal_rows(product).count())
+        self.assertEqual(1, get_bugsummaryjournal_rows(ociproject).count())
         transaction.commit()
 
         exit_code, out, err = run_script('scripts/bugsummary-rebuild.py')
@@ -158,7 +167,9 @@ class TestBugSummaryRebuild(TestCaseWithFactory):
 
         transaction.commit()
         self.assertEqual(1, get_bugsummary_rows(product).count())
+        self.assertEqual(1, get_bugsummary_rows(ociproject).count())
         self.assertEqual(0, get_bugsummaryjournal_rows(product).count())
+        self.assertEqual(0, get_bugsummaryjournal_rows(ociproject).count())
 
 
 class TestGetBugSummaryRows(TestCaseWithFactory):
diff --git a/lib/lp/bugs/subscribers/karma.py b/lib/lp/bugs/subscribers/karma.py
index 2ef5866..0eee2cf 100644
--- a/lib/lp/bugs/subscribers/karma.py
+++ b/lib/lp/bugs/subscribers/karma.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Assign karma for bugs domain activity."""
@@ -28,6 +28,11 @@ def _assign_karma_using_bugtask_context(person, bugtask, actionname):
     product = bugtask.product
     if bugtask.productseries is not None:
         product = bugtask.productseries.product
+    if (product is None and distribution is None
+            and bugtask.sourcepackagename is None):
+        # Something that does not support karma yet triggered this
+        # (OCIProject?), so let's skip karma assignment.
+        return
     person.assignKarma(
         actionname, product=product, distribution=distribution,
         sourcepackagename=bugtask.sourcepackagename)
diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
index e72a4cf..c7afb04 100644
--- a/lib/lp/registry/interfaces/ociproject.py
+++ b/lib/lp/registry/interfaces/ociproject.py
@@ -29,7 +29,10 @@ from lazr.restful.fields import (
     ReferenceChoice,
     )
 from six.moves import http_client
-from zope.interface import Interface
+from zope.interface import (
+    Attribute,
+    Interface,
+    )
 from zope.schema import (
     Bool,
     Datetime,
@@ -43,7 +46,11 @@ from zope.security.interfaces import Unauthorized
 from lp import _
 from lp.app.validators.name import name_validator
 from lp.app.validators.path import path_does_not_escape
-from lp.bugs.interfaces.bugtarget import IBugTarget
+from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
+from lp.bugs.interfaces.bugtarget import (
+    IBugTarget,
+    IHasExpirableBugs,
+    )
 from lp.code.interfaces.gitref import IGitRef
 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
 from lp.registry.interfaces.distribution import IDistribution
@@ -83,6 +90,12 @@ class IOCIProjectView(IHasGitRepositories, Interface):
         title=_("Display name for this OCI project."),
         required=True, readonly=True))
 
+    displayname = Attribute(_("Display name for this OCI project."))
+
+    driver = Attribute(_("The driver for this OCI project."))
+
+    bug_supervisor = Attribute(_("The bug supervisor for this OCI Project."))
+
     def getSeriesByName(name):
         """Get an OCIProjectSeries for this OCIProject by series' name."""
 
@@ -189,7 +202,8 @@ class IOCIProjectLegitimate(Interface):
 @exported_as_webservice_entry(
     publish_web_link=True, as_of="devel", singular_name="oci_project")
 class IOCIProject(IOCIProjectView, IOCIProjectEdit,
-                  IOCIProjectEditableAttributes, IOCIProjectLegitimate):
+                  IOCIProjectEditableAttributes, IOCIProjectLegitimate,
+                  IHasBugSupervisor, IHasExpirableBugs):
     """A project containing Open Container Initiative recipes."""
 
 
diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py
index c27f2c6..98b417b 100644
--- a/lib/lp/registry/model/ociproject.py
+++ b/lib/lp/registry/model/ociproject.py
@@ -125,9 +125,20 @@ class OCIProject(BugTargetBase, StormBase):
         return "OCI project %s for %s" % (
             self.ociprojectname.name, self.pillar.display_name)
 
+    displayname = display_name
     bugtargetname = display_name
     bugtargetdisplayname = display_name
 
+    @property
+    def driver(self):
+        """See `IOCIProject`."""
+        return self.pillar.driver
+
+    @property
+    def bug_supervisor(self):
+        """See `IOCIProject`."""
+        return self.pillar.bug_supervisor
+
     def newRecipe(self, name, registrant, owner, git_ref,
                   build_file, description=None, build_daily=False,
                   require_virtualized=True, build_args=None):

Follow ups