← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pappacena/launchpad:stormify-bug-task into launchpad:master

 

Thiago F. Pappacena has proposed merging ~pappacena/launchpad:stormify-bug-task into launchpad:master.

Commit message:
Stormifying BugTask class, to prepare it to receive foreign key to OCI project

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/393473
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:stormify-bug-task into launchpad:master.
diff --git a/lib/lp/bugs/browser/buglisting.py b/lib/lp/bugs/browser/buglisting.py
index c0e67a6..6acbf8a 100644
--- a/lib/lp/bugs/browser/buglisting.py
+++ b/lib/lp/bugs/browser/buglisting.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).
 
 """IBugTask-related browser views."""
@@ -657,8 +657,8 @@ class BugTaskListingItem:
         else:
             milestone_name = None
         assignee = None
-        if self.assigneeID is not None:
-            assignee = self.people[self.assigneeID].displayname
+        if self.assignee_id is not None:
+            assignee = self.people[self.assignee_id].displayname
         reporter = self.people[self.bug.ownerID]
 
         # the case that there is no target context (e.g. viewing bug that
diff --git a/lib/lp/bugs/browser/tests/test_bugtask.py b/lib/lp/bugs/browser/tests/test_bugtask.py
index dd33206..cb98e74 100644
--- a/lib/lp/bugs/browser/tests/test_bugtask.py
+++ b/lib/lp/bugs/browser/tests/test_bugtask.py
@@ -129,7 +129,7 @@ class TestBugTaskView(TestCaseWithFactory):
             0, 10, login_method=lambda: login(ADMIN_EMAIL))
         # This may seem large: it is; there is easily another 25% fat in
         # there.
-        self.assertThat(recorder1, HasQueryCount(LessThan(85)))
+        self.assertThat(recorder1, HasQueryCount(LessThan(86)))
         self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
 
     def test_rendered_query_counts_constant_with_attachments(self):
@@ -140,7 +140,7 @@ class TestBugTaskView(TestCaseWithFactory):
             lambda: self.getUserBrowser(url, person),
             lambda: self.factory.makeBugAttachment(bug=task.bug),
             1, 9, login_method=lambda: login(ADMIN_EMAIL))
-        self.assertThat(recorder1, HasQueryCount(LessThan(86)))
+        self.assertThat(recorder1, HasQueryCount(LessThan(87)))
         self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
 
     def makeLinkedBranchMergeProposal(self, sourcepackage, bug, owner):
@@ -175,7 +175,7 @@ class TestBugTaskView(TestCaseWithFactory):
         recorder1, recorder2 = record_two_runs(
             lambda: self.getUserBrowser(url, owner),
             make_merge_proposals, 0, 1)
-        self.assertThat(recorder1, HasQueryCount(LessThan(93)))
+        self.assertThat(recorder1, HasQueryCount(LessThan(94)))
         # Ideally this should be much fewer, but this tries to keep a win of
         # removing more than half of these.
         self.assertThat(
@@ -221,7 +221,7 @@ class TestBugTaskView(TestCaseWithFactory):
             lambda: self.getUserBrowser(url, person),
             lambda: add_activity("description", self.factory.makePerson()),
             1, 20, login_method=lambda: login(ADMIN_EMAIL))
-        self.assertThat(recorder1, HasQueryCount(LessThan(86)))
+        self.assertThat(recorder1, HasQueryCount(LessThan(87)))
         self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
 
     def test_rendered_query_counts_constant_with_milestones(self):
@@ -231,7 +231,7 @@ class TestBugTaskView(TestCaseWithFactory):
 
         with celebrity_logged_in('admin'):
             browses_under_limit = BrowsesWithQueryLimit(
-                86, self.factory.makePerson())
+                87, self.factory.makePerson())
 
         self.assertThat(bug, browses_under_limit)
 
diff --git a/lib/lp/bugs/configure.zcml b/lib/lp/bugs/configure.zcml
index 7edae44..3e4544b 100644
--- a/lib/lp/bugs/configure.zcml
+++ b/lib/lp/bugs/configure.zcml
@@ -167,9 +167,9 @@
             <allow
                 attributes="
                     id
-                    assigneeID
+                    assignee_id
                     bug
-                    bugID
+                    bug_id
                     target
                     date_assigned
                     datecreated
@@ -182,12 +182,12 @@
                     date_fix_released
                     date_left_closed
                     date_closed
-                    distributionID
-                    distroseriesID
-                    milestoneID
-                    productID
-                    productseriesID
-                    sourcepackagenameID
+                    distribution_id
+                    distroseries_id
+                    milestone_id
+                    product_id
+                    productseries_id
+                    sourcepackagename_id
                     task_age
                     bug_subscribers
                     is_complete
diff --git a/lib/lp/bugs/interfaces/bugtask.py b/lib/lp/bugs/interfaces/bugtask.py
index f7c1600..2bf45da 100644
--- a/lib/lp/bugs/interfaces/bugtask.py
+++ b/lib/lp/bugs/interfaces/bugtask.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).
 
 """Bug task interfaces."""
@@ -374,6 +374,9 @@ class IllegalTarget(Exception):
 
 class IBugTaskDelete(Interface):
     """An interface for operations allowed with the Delete permission."""
+    def destroySelf(self):
+        """Removes this BugTask from the database."""
+
     @export_destructor_operation()
     @call_with(who=REQUEST_USER)
     @operation_for_version('devel')
@@ -395,30 +398,31 @@ class IBugTask(IHasBug, IBugTaskDelete):
     id = Int(title=_("Bug Task #"))
     bug = exported(
         BugField(title=_("Bug"), readonly=True))
+    bug_id = Int(title=_("The bug ID for this bug task"))
     product = Choice(
         title=_('Project'), required=False, vocabulary='Product')
-    productID = Attribute('The product ID')
+    product_id = Attribute('The product ID')
     productseries = Choice(
         title=_('Series'), required=False, vocabulary='ProductSeries')
-    productseriesID = Attribute('The product series ID')
+    productseries_id = Attribute('The product series ID')
     sourcepackagename = Choice(
         title=_("Package"), required=False,
         vocabulary='SourcePackageName')
-    sourcepackagenameID = Attribute('The sourcepackagename ID')
+    sourcepackagename_id = Attribute('The sourcepackagename ID')
     distribution = Choice(
         title=_("Distribution"), required=False, vocabulary='Distribution')
-    distributionID = Attribute('The distribution ID')
+    distribution_id = Attribute('The distribution ID')
     distroseries = Choice(
         title=_("Series"), required=False,
         vocabulary='DistroSeries')
-    distroseriesID = Attribute('The distroseries ID')
+    distroseries_id = Attribute('The distroseries ID')
     milestone = exported(ReferenceChoice(
         title=_('Milestone'),
         required=False,
         readonly=True,
         vocabulary='BugTaskMilestone',
         schema=Interface))  # IMilestone
-    milestoneID = Attribute('The id of the milestone.')
+    milestone_id = Attribute('The id of the milestone.')
 
     # The status and importance's vocabularies do not
     # contain an UNKNOWN item in bugtasks that aren't linked to a remote
@@ -440,7 +444,7 @@ class IBugTask(IHasBug, IBugTaskDelete):
             title=_('Assigned to'), required=False,
             vocabulary='ValidAssignee',
             readonly=True))
-    assigneeID = Int(title=_('The assignee ID (for eager loading)'))
+    assignee_id = Int(title=_('The assignee ID (for eager loading)'))
     bugtargetdisplayname = exported(
         Text(title=_("The short, descriptive name of the target"),
              readonly=True),
diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
index c1e35a1..12f9e72 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -659,14 +659,14 @@ class Bug(SQLBase, InformationTypeMixin):
         from lp.registry.model.productseries import ProductSeries
         from lp.registry.model.sourcepackagename import SourcePackageName
         store = Store.of(self)
-        tasks = list(store.find(BugTask, BugTask.bugID == self.id))
+        tasks = list(store.find(BugTask, BugTask.bug_id == self.id))
         # The bugtasks attribute is iterated in the API and web
         # services, so it needs to preload all related data otherwise
         # late evaluation is triggered in both places. Separately,
         # bugtask_sort_key requires the related products, series,
         # distros, distroseries and source package names to be loaded.
-        ids = set(map(operator.attrgetter('assigneeID'), tasks))
-        ids.update(map(operator.attrgetter('ownerID'), tasks))
+        ids = set(map(operator.attrgetter('assignee_id'), tasks))
+        ids.update(map(operator.attrgetter('owner_id'), tasks))
         ids.discard(None)
         if ids:
             list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
@@ -678,11 +678,11 @@ class Bug(SQLBase, InformationTypeMixin):
             if not ids:
                 return
             list(store.find(klass, klass.id.is_in(ids)))
-        load_something('productID', Product)
-        load_something('productseriesID', ProductSeries)
-        load_something('distributionID', Distribution)
-        load_something('distroseriesID', DistroSeries)
-        load_something('sourcepackagenameID', SourcePackageName)
+        load_something('product_id', Product)
+        load_something('productseries_id', ProductSeries)
+        load_something('distribution_id', Distribution)
+        load_something('distroseries_id', DistroSeries)
+        load_something('sourcepackagename_id', SourcePackageName)
         list(store.find(BugWatch, BugWatch.bugID == self.id))
         return sorted(tasks, key=bugtask_sort_key)
 
@@ -692,7 +692,7 @@ class Bug(SQLBase, InformationTypeMixin):
         from lp.registry.model.product import Product
         return Store.of(self).using(
                 BugTask,
-                LeftJoin(Product, Product.id == BugTask.productID)
+                LeftJoin(Product, Product.id == BugTask.product_id)
             ).find(
                 BugTask, bug=self
             ).order_by(
@@ -2619,10 +2619,10 @@ class BugSubscriptionInfo:
         """
         if self.bugtask is None:
             assignees = load_people(
-                Person.id.is_in(Select(BugTask.assigneeID,
+                Person.id.is_in(Select(BugTask.assignee_id,
                     BugTask.bug == self.bug)))
         else:
-            assignees = load_people(Person.id == self.bugtask.assigneeID)
+            assignees = load_people(Person.id == self.bugtask.assignee_id)
         if self.bug.private:
             return IStore(Person).find(Person,
                 Person.id.is_in([a.id for a in assignees]),
diff --git a/lib/lp/bugs/model/bugtask.py b/lib/lp/bugs/model/bugtask.py
index fb2bfee..85ca522 100644
--- a/lib/lp/bugs/model/bugtask.py
+++ b/lib/lp/bugs/model/bugtask.py
@@ -33,11 +33,6 @@ import re
 from lazr.lifecycle.event import ObjectDeletedEvent
 import pytz
 import six
-from sqlobject import (
-    ForeignKey,
-    SQLObjectNotFound,
-    StringCol,
-    )
 from storm.expr import (
     And,
     Cast,
@@ -51,6 +46,12 @@ from storm.expr import (
     Sum,
     )
 from storm.info import ClassAlias
+from storm.properties import (
+    DateTime,
+    Int,
+    Unicode,
+    )
+from storm.references import Reference
 from storm.store import (
     EmptyResultSet,
     Store,
@@ -124,18 +125,16 @@ from lp.services.database.bulk import (
     load_related,
     )
 from lp.services.database.constants import UTC_NOW
-from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.decoratedresultset import DecoratedResultSet
-from lp.services.database.enumcol import EnumCol
+from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
 from lp.services.database.nl_search import nl_phrase_search
 from lp.services.database.sqlbase import (
-    block_implicit_flushes,
     cursor,
     quote,
-    SQLBase,
     sqlvalues,
     )
+from lp.services.database.stormbase import StormBase
 from lp.services.helpers import shortlist
 from lp.services.propertycache import get_property_cache
 from lp.services.searchbuilder import any
@@ -261,7 +260,6 @@ class PassthroughValue:
         self.value = value
 
 
-@block_implicit_flushes
 def validate_conjoined_attribute(self, attr, value):
     # If the value has been wrapped in a _PassthroughValue instance,
     # then we are being updated by our conjoined master: pass the
@@ -269,12 +267,6 @@ def validate_conjoined_attribute(self, attr, value):
     if isinstance(value, PassthroughValue):
         return value.value
 
-    # Check to see if the object is being instantiated.  This test is specific
-    # to SQLBase.  Checking for specific attributes (like self.bug) is
-    # insufficient and fragile.
-    if self._SO_creating:
-        return value
-
     # If this is a conjoined slave then call setattr on the master.
     # Effectively this means that making a change to the slave will
     # actually make the change to the master (which will then be passed
@@ -282,13 +274,15 @@ def validate_conjoined_attribute(self, attr, value):
     # people try to update the conjoined slave via the API.
     conjoined_master = self.conjoined_master
     if conjoined_master is not None:
-        setattr(conjoined_master, attr, value)
+        if getattr(conjoined_master, attr) != value:
+            setattr(conjoined_master, attr, value)
         return value
 
     # If there is a conjoined slave, update that.
     conjoined_bugtask = self.conjoined_slave
     if conjoined_bugtask:
-        setattr(conjoined_bugtask, attr, PassthroughValue(value))
+        if getattr(conjoined_bugtask, attr) != value:
+            setattr(conjoined_bugtask, attr, value)
 
     return value
 
@@ -409,13 +403,13 @@ def validate_new_target(bug, target, check_source_package=True):
 
 
 @implementer(IBugTask)
-class BugTask(SQLBase):
+class BugTask(StormBase):
     """See `IBugTask`."""
-    _table = "BugTask"
+    __storm_table__ = "BugTask"
     _defaultOrder = ['distribution', 'product', 'productseries',
                      'distroseries', 'milestone', 'sourcepackagename']
     _CONJOINED_ATTRIBUTES = (
-        "_status", "importance", "assigneeID", "milestoneID",
+        "_status", "importance", "assignee_id", "milestone_id",
         "date_assigned", "date_confirmed", "date_inprogress",
         "date_closed", "date_incomplete", "date_left_new",
         "date_triaged", "date_fix_committed", "date_fix_released",
@@ -424,66 +418,82 @@ class BugTask(SQLBase):
 
     _inhibit_target_check = False
 
-    bug = ForeignKey(dbName='bug', foreignKey='Bug', notNull=True)
-    product = ForeignKey(
-        dbName='product', foreignKey='Product',
-        notNull=False, default=None)
-    productseries = ForeignKey(
-        dbName='productseries', foreignKey='ProductSeries',
-        notNull=False, default=None)
-    sourcepackagename = ForeignKey(
-        dbName='sourcepackagename', foreignKey='SourcePackageName',
-        notNull=False, default=None)
-    distribution = ForeignKey(
-        dbName='distribution', foreignKey='Distribution',
-        notNull=False, default=None)
-    distroseries = ForeignKey(
-        dbName='distroseries', foreignKey='DistroSeries',
-        notNull=False, default=None)
-    milestone = ForeignKey(
-        dbName='milestone', foreignKey='Milestone',
-        notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    _status = EnumCol(
-        dbName='status', notNull=True,
-        schema=(BugTaskStatus, BugTaskStatusSearch),
+    id = Int(primary=True)
+
+    bug_id = Int(name='bug', allow_none=False)
+    bug = Reference(bug_id, 'Bug.id')
+
+    product_id = Int(name="product", allow_none=True)
+    product = Reference(product_id, 'Product.id')
+
+    productseries_id = Int(name="productseries", allow_none=True)
+    productseries = Reference(productseries_id, 'ProductSeries.id')
+
+    sourcepackagename_id = Int(name="sourcepackagename", allow_none=True)
+    sourcepackagename = Reference(sourcepackagename_id, "SourcePackageName.id")
+
+    distribution_id = Int(name="distribution", allow_none=True)
+    distribution = Reference(distribution_id, "Distribution.id")
+
+    distroseries_id = Int(name="distroseries", allow_none=True)
+    distroseries = Reference(distroseries_id, "DistroSeries.id")
+
+    milestone_id = Int(name="milestone", allow_none=True)
+    milestone = Reference(milestone_id, "Milestone.id")
+
+    _status = DBEnum(
+        name='status', allow_none=False,
+        enum=(BugTaskStatus, BugTaskStatusSearch),
         default=BugTaskStatus.NEW,
-        storm_validator=validate_status)
-    importance = EnumCol(
-        dbName='importance', notNull=True,
-        schema=BugTaskImportance,
+        validator=validate_status)
+    importance = DBEnum(
+        name='importance', allow_none=False,
+        enum=BugTaskImportance,
         default=BugTaskImportance.UNDECIDED,
-        storm_validator=validate_conjoined_attribute)
-    assignee = ForeignKey(
-        dbName='assignee', foreignKey='Person',
-        storm_validator=validate_assignee,
-        notNull=False, default=None)
-    bugwatch = ForeignKey(dbName='bugwatch', foreignKey='BugWatch',
-        notNull=False, default=None)
-    date_assigned = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    datecreated = UtcDateTimeCol(notNull=False, default=UTC_NOW)
-    date_confirmed = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_inprogress = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_closed = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_incomplete = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_left_new = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_triaged = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_fix_committed = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_fix_released = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    date_left_closed = UtcDateTimeCol(notNull=False, default=None,
-        storm_validator=validate_conjoined_attribute)
-    owner = ForeignKey(
-        dbName='owner', foreignKey='Person',
-        storm_validator=validate_public_person, notNull=True)
+        validator=validate_conjoined_attribute)
+
+    assignee_id = Int(
+        name="assignee", allow_none=True, validator=validate_assignee)
+    assignee = Reference(assignee_id, "Person.id")
+
+    bugwatch_id = Int(name="bugwatch", allow_none=True)
+    bugwatch = Reference(bugwatch_id, "BugWatch.id")
+
+    date_assigned = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    datecreated = DateTime(tzinfo=pytz.UTC, allow_none=True, default=UTC_NOW)
+    date_confirmed = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_inprogress = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_closed = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_incomplete = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_left_new = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_triaged = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_fix_committed = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_fix_released = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+    date_left_closed = DateTime(
+        tzinfo=pytz.UTC, allow_none=True, default=None,
+        validator=validate_conjoined_attribute)
+
+    owner_id = Int(
+        name="owner", allow_none=False, validator=validate_public_person)
+    owner = Reference(owner_id, "Person.id")
     # The targetnamecache is a value that is only supposed to be set
     # when a bugtask is created/modified or by the
     # update-bugtask-targetnamecaches cronscript. For this reason it's
@@ -492,8 +502,8 @@ class BugTask(SQLBase):
     #
     # This field is actually incorrectly named, since it currently
     # stores the bugtargetdisplayname.
-    targetnamecache = StringCol(
-        dbName='targetnamecache', notNull=False, default=None)
+    targetnamecache = Unicode(
+        name='targetnamecache', allow_none=True, default=None)
 
     @property
     def status(self):
@@ -596,6 +606,9 @@ class BugTask(SQLBase):
                 "Cannot delete only bugtask affecting: %s."
                 % self.target.bugtargetdisplayname)
 
+    def destroySelf(self):
+        Store.of(self).remove(self)
+
     def delete(self, who=None):
         """See `IBugTask`."""
         if who is None:
@@ -644,7 +657,7 @@ class BugTask(SQLBase):
             matching_bugtasks, user, limit)
 
         # Make sure to exclude the bug of the current bugtask.
-        return [bug for bug in matching_bugs if bug.id != self.bugID]
+        return [bug for bug in matching_bugs if bug.id != self.bug_id]
 
     def subscribe(self, person, subscribed_by):
         """See `IBugTask`."""
@@ -716,7 +729,7 @@ class BugTask(SQLBase):
                 'A product should always have a development series.')
             devel_focusID = self.product.development_focusID
             for bugtask in bugtasks:
-                if bugtask.productseriesID == devel_focusID:
+                if bugtask.productseries_id == devel_focusID:
                     conjoined_master = bugtask
                     break
 
@@ -776,7 +789,7 @@ class BugTask(SQLBase):
             # Bypass our checks that prevent setting attributes on
             # conjoined masters by calling the underlying sqlobject
             # setter methods directly.
-            setattr(self, synched_attr, PassthroughValue(slave_attr_value))
+            setattr(self, synched_attr, slave_attr_value)
 
     def transitionToMilestone(self, new_milestone, user):
         """See `IBugTask`."""
@@ -1089,8 +1102,8 @@ class BugTask(SQLBase):
                 # series have tasks on this bug.
                 if not Store.of(self).find(
                     BugTask,
-                    BugTask.bugID == self.bugID,
-                    BugTask.distroseriesID == DistroSeries.id,
+                    BugTask.bug_id == self.bug_id,
+                    BugTask.distroseries_id == DistroSeries.id,
                     DistroSeries.distributionID.is_in(
                         distro.id for distro in distros if distro),
                     ).is_empty():
@@ -1324,7 +1337,7 @@ class BugTask(SQLBase):
         return self.userHasBugSupervisorPrivilegesContext(self.target, user)
 
     def __repr__(self):
-        return "<BugTask for bug %s on %r>" % (self.bugID, self.target)
+        return "<BugTask for bug %s on %r>" % (self.bug_id, self.target)
 
 
 @implementer(IBugTaskSet)
@@ -1346,9 +1359,8 @@ class BugTaskSet:
         # XXX: JSK: 2007-12-19: This method should probably return
         # None when task_id is not present. See:
         # https://bugs.launchpad.net/launchpad/+bug/123592
-        try:
-            bugtask = BugTask.get(task_id)
-        except SQLObjectNotFound:
+        bugtask = IStore(BugTask).find(BugTask, BugTask.id == task_id).one()
+        if bugtask is None:
             raise NotFoundError("BugTask with ID %s does not exist." %
                                 str(task_id))
         return bugtask
@@ -1358,7 +1370,7 @@ class BugTaskSet:
         # Import locally to avoid circular imports.
         from lp.bugs.model.bug import Bug, BugTag
         bugtask_ids = set(bugtask.id for bugtask in bugtasks)
-        bug_ids = set(bugtask.bugID for bugtask in bugtasks)
+        bug_ids = set(bugtask.bug_id for bugtask in bugtasks)
         tags = IStore(BugTag).find(
             (BugTag.tag, BugTask.id),
             BugTask.bug == Bug.id,
@@ -1375,7 +1387,7 @@ class BugTaskSet:
         # Avoid circular imports.
         from lp.registry.interfaces.person import IPersonSet
         people_ids = set(
-            [bugtask.assigneeID for bugtask in bugtasks] +
+            [bugtask.assignee_id for bugtask in bugtasks] +
             [bugtask.bug.ownerID for bugtask in bugtasks])
         people = getUtility(IPersonSet).getPrecachedPersonsFromIDs(people_ids)
         return dict(
@@ -1387,7 +1399,7 @@ class BugTaskSet:
         from lp.bugs.model.bug import Bug
         from lp.bugs.model.bugbranch import BugBranch
 
-        bug_ids = set(bugtask.bugID for bugtask in bugtasks)
+        bug_ids = set(bugtask.bug_id for bugtask in bugtasks)
         bug_ids_with_specifications = set(
             int(id) for _, id in getUtility(IXRefSet).findFromMany(
                 [(u'bug', six.text_type(bug_id)) for bug_id in bug_ids],
@@ -1397,7 +1409,7 @@ class BugTaskSet:
         # Badging looks up milestones too : eager load into the storm cache.
         milestoneset = getUtility(IMilestoneSet)
         # And trigger a load:
-        milestone_ids = set(map(attrgetter('milestoneID'), bugtasks))
+        milestone_ids = set(map(attrgetter('milestone_id'), bugtasks))
         milestone_ids.discard(None)
         if milestone_ids:
             list(milestoneset.getByIds(milestone_ids))
@@ -1415,7 +1427,7 @@ class BugTaskSet:
 
         badge_properties = {}
         for bugtask in bugtasks:
-            bug = bugs[bugtask.bugID]
+            bug = bugs[bugtask.bug_id]
             badge_properties[bugtask] = {
                 'has_specification':
                     bug.id in bug_ids_with_specifications,
@@ -1486,12 +1498,12 @@ class BugTaskSet:
             eager_load = None
         else:
             def eager_load(rows):
-                load_related(Bug, rows, ['bugID'])
-                load_related(Product, rows, ['productID'])
-                load_related(ProductSeries, rows, ['productseriesID'])
-                load_related(Distribution, rows, ['distributionID'])
-                load_related(DistroSeries, rows, ['distroseriesID'])
-                load_related(SourcePackageName, rows, ['sourcepackagenameID'])
+                load_related(Bug, rows, ['bug_id'])
+                load_related(Product, rows, ['product_id'])
+                load_related(ProductSeries, rows, ['productseries_id'])
+                load_related(Distribution, rows, ['distribution_id'])
+                load_related(DistroSeries, rows, ['distroseries_id'])
+                load_related(SourcePackageName, rows, ['sourcepackagename_id'])
         return search_bugs(eager_load, (params,) + args)
 
     def searchBugIds(self, params):
@@ -1757,7 +1769,7 @@ class BugTaskSet:
             ids = ids[:limit]
 
         return DecoratedResultSet(
-            ids, lambda id: BugTask.get(id),
+            ids, lambda id: getUtility(IBugTaskSet).get(id),
             pre_iter_hook=lambda rows: load(BugTask, rows))
 
     def _getTargetJoinAndClause(self, target):
@@ -1946,12 +1958,12 @@ class BugTaskSet:
         # for the milestone vocabulary
         for task in bugtasks:
             task = removeSecurityProxy(task)
-            distro_ids.add(task.distributionID)
-            distro_series_ids.add(task.distroseriesID)
-            product_ids.add(task.productID)
+            distro_ids.add(task.distribution_id)
+            distro_series_ids.add(task.distroseries_id)
+            product_ids.add(task.product_id)
             if task.productseries:
                 product_ids.add(task.productseries.productID)
-            product_series_ids.add(task.productseriesID)
+            product_series_ids.add(task.productseries_id)
 
         distro_ids.discard(None)
         distro_series_ids.discard(None)
diff --git a/lib/lp/bugs/model/bugtasksearch.py b/lib/lp/bugs/model/bugtasksearch.py
index 15a489d..47332d5 100644
--- a/lib/lp/bugs/model/bugtasksearch.py
+++ b/lib/lp/bugs/model/bugtasksearch.py
@@ -1054,9 +1054,9 @@ def _build_pending_bugwatch_elsewhere_clause(params):
         # Restrict the target to params.upstream_target.
         target = params.upstream_target
         if IProduct.providedBy(target):
-            target_col = RelatedBugTask.productID
+            target_col = RelatedBugTask.product_id
         elif IDistribution.providedBy(target):
-            target_col = RelatedBugTask.distributionID
+            target_col = RelatedBugTask.distribution_id
         else:
             raise AssertionError(
                 'params.upstream_target must be a Distribution or '
@@ -1070,10 +1070,10 @@ def _build_pending_bugwatch_elsewhere_clause(params):
         extra_joins = [
             LeftJoin(
                 OtherDistribution,
-                OtherDistribution.id == RelatedBugTask.distributionID),
+                OtherDistribution.id == RelatedBugTask.distribution_id),
             LeftJoin(
                 OtherProduct,
-                OtherProduct.id == RelatedBugTask.productID),
+                OtherProduct.id == RelatedBugTask.product_id),
             ]
         target_clause = Or(
             OtherDistribution.official_malone == False,
@@ -1091,9 +1091,9 @@ def _build_pending_bugwatch_elsewhere_clause(params):
         1,
         tables=[RelatedBugTask] + extra_joins,
         where=And(
-            RelatedBugTask.bugID == BugTaskFlat.bug_id,
+            RelatedBugTask.bug_id == BugTaskFlat.bug_id,
             task_match_clause,
-            RelatedBugTask.bugwatchID == None,
+            RelatedBugTask.bugwatch_id == None,
             RelatedBugTask._status != BugTaskStatus.INVALID,
             target_clause)))
 
@@ -1102,18 +1102,18 @@ def _build_no_upstream_bugtask_clause(params):
     """Return a clause for BugTaskSearchParams.has_no_upstream_bugtask."""
     OtherBugTask = ClassAlias(BugTask)
     if params.upstream_target is None:
-        target = OtherBugTask.productID != None
+        target = OtherBugTask.product_id != None
     elif IProduct.providedBy(params.upstream_target):
-        target = OtherBugTask.productID == params.upstream_target.id
+        target = OtherBugTask.product_id == params.upstream_target.id
     elif IDistribution.providedBy(params.upstream_target):
-        target = OtherBugTask.distributionID == params.upstream_target.id
+        target = OtherBugTask.distribution_id == params.upstream_target.id
     else:
         raise AssertionError(
             'params.upstream_target must be a Distribution or '
             'a Product')
     return Not(Exists(Select(
         1, tables=[OtherBugTask],
-        where=And(OtherBugTask.bugID == BugTaskFlat.bug_id, target))))
+        where=And(OtherBugTask.bug_id == BugTaskFlat.bug_id, target))))
 
 
 def _build_open_or_resolved_upstream_clause(params,
@@ -1128,12 +1128,12 @@ def _build_open_or_resolved_upstream_clause(params,
         RelatedBugTask._status, any(*statuses_for_upstream_tasks))
     if params.upstream_target is None:
         watch_target_clause = True
-        no_watch_target_clause = RelatedBugTask.productID != None
+        no_watch_target_clause = RelatedBugTask.product_id != None
     else:
         if IProduct.providedBy(params.upstream_target):
-            target_col = RelatedBugTask.productID
+            target_col = RelatedBugTask.product_id
         elif IDistribution.providedBy(params.upstream_target):
-            target_col = RelatedBugTask.distributionID
+            target_col = RelatedBugTask.distribution_id
         else:
             raise AssertionError(
                 'params.upstream_target must be a Distribution or '
@@ -1144,14 +1144,14 @@ def _build_open_or_resolved_upstream_clause(params,
         1,
         tables=[RelatedBugTask],
         where=And(
-            RelatedBugTask.bugID == BugTaskFlat.bug_id,
+            RelatedBugTask.bug_id == BugTaskFlat.bug_id,
             RelatedBugTask.id != BugTaskFlat.bugtask_id,
             Or(
                 And(watch_target_clause,
-                    RelatedBugTask.bugwatchID != None,
+                    RelatedBugTask.bugwatch_id != None,
                     watch_status_clause),
                 And(no_watch_target_clause,
-                    RelatedBugTask.bugwatchID == None,
+                    RelatedBugTask.bugwatch_id == None,
                     no_watch_status_clause)))))
 
 
diff --git a/lib/lp/bugs/model/personsubscriptioninfo.py b/lib/lp/bugs/model/personsubscriptioninfo.py
index 3767e6b..1d82c22 100644
--- a/lib/lp/bugs/model/personsubscriptioninfo.py
+++ b/lib/lp/bugs/model/personsubscriptioninfo.py
@@ -1,4 +1,4 @@
-# Copyright 2011-2012 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).
 
 __metaclass__ = type
@@ -184,8 +184,8 @@ class PersonSubscriptions(object):
         list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
             [bug.ownerID for bug in bugs]))
         all_tasks = [task for task in bug.bugtasks for bug in bugs]
-        load_related(Product, all_tasks, ['productID'])
-        load_related(Distribution, all_tasks, ['distributionID'])
+        load_related(Product, all_tasks, ['product_id'])
+        load_related(Distribution, all_tasks, ['distribution_id'])
         for bug in bugs:
             # indicate the reporter and bug_supervisor
             duplicates.annotateReporter(bug, bug.owner)
diff --git a/lib/lp/bugs/scripts/bugsummaryrebuild.py b/lib/lp/bugs/scripts/bugsummaryrebuild.py
index 3537a98..ab89e7b 100644
--- a/lib/lp/bugs/scripts/bugsummaryrebuild.py
+++ b/lib/lp/bugs/scripts/bugsummaryrebuild.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2018 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
@@ -69,9 +69,9 @@ def get_bugsummary_targets():
 def get_bugtask_targets():
     """Get the current set of targets represented in BugTask."""
     new_targets = set(IStore(BugTask).find(
-        (BugTask.productID, BugTask.productseriesID,
-         BugTask.distributionID, BugTask.distroseriesID,
-         BugTask.sourcepackagenameID)).config(distinct=True))
+        (BugTask.product_id, BugTask.productseries_id,
+         BugTask.distribution_id, BugTask.distroseries_id,
+         BugTask.sourcepackagename_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(
diff --git a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
index 429ff49..5e4cb1e 100644
--- a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
+++ b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
@@ -1,4 +1,4 @@
-# Copyright 2009 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).
 
 """A utility module for the update-bugtasktargetnamecaches.py cronscript."""
@@ -32,8 +32,8 @@ from lp.services.looptuner import (
 # These two tuples must be in the same order. They specify the ID
 # columns to get from BugTask, and the classes that they correspond to.
 target_columns = (
-    BugTask.productID, BugTask.productseriesID, BugTask.distributionID,
-    BugTask.distroseriesID, BugTask.sourcepackagenameID,
+    BugTask.product_id, BugTask.productseries_id, BugTask.distribution_id,
+    BugTask.distroseries_id, BugTask.sourcepackagename_id,
     BugTask.targetnamecache)
 target_classes = (
     Product, ProductSeries, Distribution, DistroSeries, SourcePackageName)
diff --git a/lib/lp/code/model/branchcollection.py b/lib/lp/code/model/branchcollection.py
index f07416a..0ab5fd8 100644
--- a/lib/lp/code/model/branchcollection.py
+++ b/lib/lp/code/model/branchcollection.py
@@ -550,7 +550,7 @@ class GenericBranchCollection:
             store = IStore(BugBranch)
             rs = store.using(
                 BugBranch,
-                Join(BugTask, BugTask.bugID == BugBranch.bug_id),
+                Join(BugTask, BugTask.bug_id == BugBranch.bug_id),
             ).find(
                 (BugTask, BugBranch),
                 BugBranch.bug_id.is_in(bug_ids),
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index b9baba7..0f07325 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -1473,7 +1473,7 @@ class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
         distributionID = self.id
 
         def weight_function(bugtask):
-            if bugtask.distributionID == distributionID:
+            if bugtask.distribution_id == distributionID:
                 return OrderedBugTask(1, bugtask.id, bugtask)
             return OrderedBugTask(2, bugtask.id, bugtask)
 
diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
index 9f0fc8c..2811a42 100644
--- a/lib/lp/registry/model/distroseries.py
+++ b/lib/lp/registry/model/distroseries.py
@@ -1454,9 +1454,9 @@ class DistroSeries(SQLBase, BugTargetBase, HasSpecificationsMixin,
         distributionID = self.distributionID
 
         def weight_function(bugtask):
-            if bugtask.distroseriesID == seriesID:
+            if bugtask.distroseries_id == seriesID:
                 return OrderedBugTask(1, bugtask.id, bugtask)
-            elif bugtask.distributionID == distributionID:
+            elif bugtask.distribution_id == distributionID:
                 return OrderedBugTask(2, bugtask.id, bugtask)
             else:
                 return OrderedBugTask(3, bugtask.id, bugtask)
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 6f96764..d63fb70 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -1821,7 +1821,7 @@ class Person(
                 Bug.id,
                 tables=(
                     Bug,
-                    Join(BugTask, BugTask.bugID == Bug.id)),
+                    Join(BugTask, BugTask.bug_id == Bug.id)),
                 where=And(Bug.information_type.is_in(
                     PRIVATE_INFORMATION_TYPES),
                     BugTask.assignee == self.id)),
diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
index ae43ede..07d652e 100644
--- a/lib/lp/registry/model/product.py
+++ b/lib/lp/registry/model/product.py
@@ -1470,7 +1470,7 @@ class Product(SQLBase, BugTargetBase, MakesAnnouncements,
         productID = self.id
 
         def weight_function(bugtask):
-            if bugtask.productID == productID:
+            if bugtask.product_id == productID:
                 return OrderedBugTask(1, bugtask.id, bugtask)
             return OrderedBugTask(2, bugtask.id, bugtask)
 
diff --git a/lib/lp/registry/model/productseries.py b/lib/lp/registry/model/productseries.py
index 416577e..3459214 100644
--- a/lib/lp/registry/model/productseries.py
+++ b/lib/lp/registry/model/productseries.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 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).
 
 """Models for `IProductSeries`."""
@@ -575,9 +575,9 @@ class ProductSeries(SQLBase, BugTargetBase, HasMilestonesMixin,
         productID = self.productID
 
         def weight_function(bugtask):
-            if bugtask.productseriesID == seriesID:
+            if bugtask.productseries_id == seriesID:
                 return OrderedBugTask(1, bugtask.id, bugtask)
-            elif bugtask.productID == productID:
+            elif bugtask.product_id == productID:
                 return OrderedBugTask(2, bugtask.id, bugtask)
             else:
                 return OrderedBugTask(3, bugtask.id, bugtask)
diff --git a/lib/lp/registry/model/sourcepackage.py b/lib/lp/registry/model/sourcepackage.py
index 9e40417..61b66e4 100644
--- a/lib/lp/registry/model/sourcepackage.py
+++ b/lib/lp/registry/model/sourcepackage.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 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).
 
 """Database classes that implement SourcePackage items."""
@@ -774,14 +774,14 @@ class SourcePackage(BugTargetBase, HasCodeImportsMixin,
         distributionID = self.distroseries.distributionID
 
         def weight_function(bugtask):
-            if bugtask.sourcepackagenameID == sourcepackagenameID:
-                if bugtask.distroseriesID == seriesID:
+            if bugtask.sourcepackagename_id == sourcepackagenameID:
+                if bugtask.distroseries_id == seriesID:
                     return OrderedBugTask(1, bugtask.id, bugtask)
-                elif bugtask.distributionID == distributionID:
+                elif bugtask.distribution_id == distributionID:
                     return OrderedBugTask(2, bugtask.id, bugtask)
-            elif bugtask.distroseriesID == seriesID:
+            elif bugtask.distroseries_id == seriesID:
                 return OrderedBugTask(3, bugtask.id, bugtask)
-            elif bugtask.distributionID == distributionID:
+            elif bugtask.distribution_id == distributionID:
                 return OrderedBugTask(4, bugtask.id, bugtask)
             # Catch the default case, and where there is a task for the same
             # sourcepackage on a different distro.
diff --git a/lib/lp/registry/scripts/closeaccount.py b/lib/lp/registry/scripts/closeaccount.py
index fba057f..9949b95 100644
--- a/lib/lp/registry/scripts/closeaccount.py
+++ b/lib/lp/registry/scripts/closeaccount.py
@@ -235,7 +235,7 @@ def close_account(username, log):
 
     # Reassign their bugs
     table_notification('BugTask')
-    store.find(BugTask, BugTask.assigneeID == person.id).set(assigneeID=None)
+    store.find(BugTask, BugTask.assignee_id == person.id).set(assignee_id=None)
 
     # Reassign questions assigned to the user, and close all their questions
     # in non-final states since nobody else can.

Follow ups