← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:stormify-bugtracker into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:stormify-bugtracker into launchpad:master.

Commit message:
Convert BugTracker to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/435977
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-bugtracker into launchpad:master.
diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
index 88f793d..18d6d7d 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -402,7 +402,7 @@ class Bug(SQLBase, InformationTypeMixin):
         "id", BugMessage.bug_id, order_by=BugMessage.index
     )
     watches = SQLMultipleJoin(
-        "BugWatch", joinColumn="bug", orderBy=["bugtracker", "remotebug"]
+        "BugWatch", joinColumn="bug", orderBy=["bugtracker_id", "remotebug"]
     )
     duplicates = SQLMultipleJoin("Bug", joinColumn="duplicateof", orderBy="id")
     linked_bugbranches = ReferenceSet(
diff --git a/lib/lp/bugs/model/bugtracker.py b/lib/lp/bugs/model/bugtracker.py
index 8a8a94e..f589dc9 100644
--- a/lib/lp/bugs/model/bugtracker.py
+++ b/lib/lp/bugs/model/bugtracker.py
@@ -12,12 +12,12 @@ __all__ = [
 
 from datetime import datetime
 from itertools import chain
+from operator import itemgetter
 from urllib.parse import quote, urlsplit, urlunsplit
 
-import six
 from lazr.uri import URI
 from pytz import timezone
-from storm.expr import Count, Desc, Not
+from storm.expr import Count, Desc, Not, Or
 from storm.locals import SQL, Bool, Int, Reference, ReferenceSet, Unicode
 from storm.store import Store
 from zope.component import getUtility
@@ -45,17 +45,10 @@ from lp.bugs.model.bugwatch import BugWatch
 from lp.registry.interfaces.person import IPersonSet, validate_public_person
 from lp.registry.model.product import Product, ProductSet
 from lp.registry.model.projectgroup import ProjectGroup
+from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase, flush_database_updates
-from lp.services.database.sqlobject import (
-    OR,
-    BoolCol,
-    ForeignKey,
-    SQLMultipleJoin,
-    SQLObjectNotFound,
-    StringCol,
-)
+from lp.services.database.sqlbase import flush_database_updates
 from lp.services.database.stormbase import StormBase
 from lp.services.helpers import shortlist
 
@@ -286,7 +279,7 @@ class BugTrackerComponentGroup(StormBase):
 
 
 @implementer(IBugTracker)
-class BugTracker(SQLBase):
+class BugTracker(StormBase):
     """A class to access the BugTracker table in the database.
 
     Each BugTracker is a distinct instance of that bug tracking
@@ -295,35 +288,60 @@ class BugTracker(SQLBase):
     distinct BugTrackers.
     """
 
-    _table = "BugTracker"
+    __storm_table__ = "BugTracker"
 
+    id = Int(primary=True)
     bugtrackertype = DBEnum(
         name="bugtrackertype", enum=BugTrackerType, allow_none=False
     )
-    name = StringCol(notNull=True, unique=True)
-    title = StringCol(notNull=True)
-    summary = StringCol(notNull=False)
-    baseurl = StringCol(notNull=True)
+    name = Unicode(name="name", allow_none=False)
+    title = Unicode(name="title", allow_none=False)
+    summary = Unicode(name="summary", allow_none=True)
+    baseurl = Unicode(name="baseurl", allow_none=False)
     active = Bool(name="active", allow_none=False, default=True)
 
-    owner = ForeignKey(
-        dbName="owner",
-        foreignKey="Person",
-        storm_validator=validate_public_person,
-        notNull=True,
-    )
-    contactdetails = StringCol(notNull=False)
-    has_lp_plugin = BoolCol(notNull=False, default=False)
-    products = SQLMultipleJoin(
-        "Product", joinColumn="bugtracker", orderBy="name"
+    owner_id = Int(
+        name="owner", validator=validate_public_person, allow_none=False
     )
-    watches = SQLMultipleJoin(
-        "BugWatch",
-        joinColumn="bugtracker",
-        orderBy="-datecreated",
-        prejoins=["bug"],
+    owner = Reference(owner_id, "Person.id")
+    contactdetails = Unicode(name="contactdetails", allow_none=True)
+    has_lp_plugin = Bool(name="has_lp_plugin", allow_none=True, default=False)
+    products = ReferenceSet(
+        "id", "Product.bugtracker_id", order_by="Product.name"
     )
 
+    @property
+    def watches(self):
+        return DecoratedResultSet(
+            IStore(BugWatch)
+            .find(
+                (BugWatch, Bug),
+                BugWatch.bugtracker == self,
+                BugWatch.bugID == Bug.id,
+            )
+            .order_by(Desc(BugWatch.datecreated)),
+            result_decorator=itemgetter(0),
+        )
+
+    def __init__(
+        self,
+        bugtrackertype,
+        name,
+        title,
+        baseurl,
+        owner,
+        summary=None,
+        contactdetails=None,
+    ):
+        super().__init__()
+        self.bugtrackertype = bugtrackertype
+        self.name = name
+        self.title = title
+        self.baseurl = baseurl
+        self.owner = owner
+        self.summary = summary
+        self.contactdetails = contactdetails
+
     _filing_url_patterns = {
         BugTrackerType.BUGZILLA: (
             "%(base_url)s/enter_bug.cgi?product=%(remote_product)s"
@@ -548,7 +566,7 @@ class BugTracker(SQLBase):
             .find(
                 Bug,
                 BugWatch.bugID == Bug.id,
-                BugWatch.bugtrackerID == self.id,
+                BugWatch.bugtracker == self,
                 BugWatch.remotebug == remotebug,
             )
             .config(distinct=True)
@@ -590,9 +608,7 @@ class BugTracker(SQLBase):
 
     # Join to return a list of BugTrackerAliases relating to this
     # BugTracker.
-    _bugtracker_aliases = ReferenceSet(
-        "<primary key>", "BugTrackerAlias.bugtracker_id"
-    )
+    _bugtracker_aliases = ReferenceSet("id", "BugTrackerAlias.bugtracker_id")
 
     def _get_aliases(self):
         """See `IBugTracker.aliases`."""
@@ -646,7 +662,7 @@ class BugTracker(SQLBase):
             .find(
                 BugMessage,
                 BugMessage.bugwatch_id == BugWatch.id,
-                BugWatch.bugtrackerID == self.id,
+                BugWatch.bugtracker == self,
             )
             .order_by(BugMessage.id)
         )
@@ -807,7 +823,7 @@ class BugTracker(SQLBase):
             IStore(Product)
             .find(
                 Product,
-                Product.bugtrackerID == self.id,
+                Product.bugtracker == self,
                 Product.active == True,
                 ProductSet.getProductPrivacyFilter(user),
             )
@@ -817,13 +833,16 @@ class BugTracker(SQLBase):
             IStore(ProjectGroup)
             .find(
                 ProjectGroup,
-                ProjectGroup.bugtrackerID == self.id,
+                ProjectGroup.bugtracker == self,
                 ProjectGroup.active == True,
             )
             .order_by(ProjectGroup.name)
         )
         return groups, products
 
+    def destroySelf(self):
+        IStore(self).remove(self)
+
 
 @implementer(IBugTrackerSet)
 class BugTrackerSet:
@@ -831,53 +850,52 @@ class BugTrackerSet:
     either the full set in the db, or a subset.
     """
 
-    table = BugTracker
-
     def __init__(self):
         self.title = "Bug trackers registered in Launchpad"
 
     def get(self, bugtracker_id, default=None):
         """See `IBugTrackerSet`."""
-        try:
-            return BugTracker.get(bugtracker_id)
-        except SQLObjectNotFound:
+        bugtracker = IStore(BugTracker).get(BugTracker, bugtracker_id)
+        if bugtracker is None:
             return default
+        return bugtracker
 
     def getByName(self, name, default=None):
         """See `IBugTrackerSet`."""
-        return self.table.selectOne(self.table.q.name == name)
+        return IStore(BugTracker).find(BugTracker, name=name).one()
 
     def __getitem__(self, name):
-        item = self.table.selectOne(self.table.q.name == name)
+        item = IStore(BugTracker).find(BugTracker, name=name).one()
         if item is None:
             raise NotFoundError(name)
         else:
             return item
 
     def __iter__(self):
-        yield from self.table.select(orderBy="title")
+        yield from IStore(BugTracker).find(BugTracker).order_by("title")
 
     def queryByBaseURL(self, baseurl):
         """See `IBugTrackerSet`."""
         # All permutations we'll search for.
         permutations = base_url_permutations(baseurl)
         # Construct the search. All the important parts in the next
-        # expression are lazily evaluated. SQLObject queries do not
+        # expression are lazily evaluated. Storm queries do not
         # execute any SQL until results are pulled, so the first query
         # to return a match will be the last query executed.
         matching_bugtrackers = chain(
             # Search for any permutation in BugTracker.
-            BugTracker.select(
-                OR(*(BugTracker.q.baseurl == url for url in permutations))
+            IStore(BugTracker).find(
+                BugTracker,
+                Or(*(BugTracker.baseurl == url for url in permutations)),
             ),
             # Search for any permutation in BugTrackerAlias.
             (
                 alias.bugtracker
                 for alias in IStore(BugTrackerAlias).find(
                     BugTrackerAlias,
-                    OR(
+                    Or(
                         *(
-                            BugTrackerAlias.base_url == six.ensure_text(url)
+                            BugTrackerAlias.base_url == url
                             for url in permutations
                         )
                     ),
@@ -891,7 +909,7 @@ class BugTrackerSet:
 
     def search(self):
         """See `IBugTrackerSet`."""
-        return BugTracker.select()
+        return IStore(BugTracker).find(BugTracker)
 
     def getAllTrackers(self, active=None):
         if active is not None:
@@ -945,17 +963,17 @@ class BugTrackerSet:
 
     @property
     def count(self):
-        return IStore(self.table).find(self.table).count()
+        return IStore(BugTracker).find(BugTracker).count()
 
     @property
     def names(self):
-        return IStore(self.table).find(self.table).values(self.table.name)
+        return IStore(BugTracker).find(BugTracker).values(BugTracker.name)
 
     def getMostActiveBugTrackers(self, limit=None):
         """See `IBugTrackerSet`."""
         return (
             IStore(BugTracker)
-            .find(BugTracker, BugTracker.id == BugWatch.bugtrackerID)
+            .find(BugTracker, BugTracker.id == BugWatch.bugtracker_id)
             .group_by(BugTracker)
             .order_by(Desc(Count(BugWatch)))
             .config(limit=limit)
@@ -968,7 +986,7 @@ class BugTrackerSet:
             IStore(Product)
             .find(
                 Product,
-                Product.bugtrackerID.is_in(ids),
+                Product.bugtracker_id.is_in(ids),
                 Product.active == True,
                 ProductSet.getProductPrivacyFilter(user),
             )
@@ -978,7 +996,7 @@ class BugTrackerSet:
             IStore(ProjectGroup)
             .find(
                 ProjectGroup,
-                ProjectGroup.bugtrackerID.is_in(ids),
+                ProjectGroup.bugtracker_id.is_in(ids),
                 ProjectGroup.active == True,
             )
             .order_by(ProjectGroup.name)
diff --git a/lib/lp/bugs/model/bugwatch.py b/lib/lp/bugs/model/bugwatch.py
index 2bbef55..c617a24 100644
--- a/lib/lp/bugs/model/bugwatch.py
+++ b/lib/lp/bugs/model/bugwatch.py
@@ -105,9 +105,8 @@ class BugWatch(SQLBase):
 
     _table = "BugWatch"
     bug = ForeignKey(dbName="bug", foreignKey="Bug", notNull=True)
-    bugtracker = ForeignKey(
-        dbName="bugtracker", foreignKey="BugTracker", notNull=True
-    )
+    bugtracker_id = Int(name="bugtracker", allow_none=False)
+    bugtracker = Reference(bugtracker_id, "BugTracker.id")
     remotebug = StringCol(notNull=True)
     remotestatus = StringCol(notNull=False, default=None)
     remote_importance = StringCol(notNull=False, default=None)
diff --git a/lib/lp/bugs/stories/webservice/xx-bug.rst b/lib/lp/bugs/stories/webservice/xx-bug.rst
index 5423f01..cbd273f 100644
--- a/lib/lp/bugs/stories/webservice/xx-bug.rst
+++ b/lib/lp/bugs/stories/webservice/xx-bug.rst
@@ -1510,7 +1510,7 @@ Non-admins can't disable a bugtracker through the API.
     ... )
     HTTP/1.1 401 Unauthorized
     ...
-    (<BugTracker at ...>, 'active', 'launchpad.Admin')
+    (<...BugTracker object at ...>, 'active', 'launchpad.Admin')
 
 Admins can, however.
 
diff --git a/lib/lp/bugs/templates/bugtracker-index.pt b/lib/lp/bugs/templates/bugtracker-index.pt
index 5320747..b47fcfd 100644
--- a/lib/lp/bugs/templates/bugtracker-index.pt
+++ b/lib/lp/bugs/templates/bugtracker-index.pt
@@ -44,7 +44,7 @@
       <div tal:replace="structure context/@@+portlet-components" />
     </div>
     <div class="yui-u"
-      tal:condition="context/watches">
+      tal:condition="not: context/watches/is_empty">
       <div tal:replace="structure context/@@+portlet-watches" />
     </div>
   </div>
diff --git a/lib/lp/bugs/vocabularies.py b/lib/lp/bugs/vocabularies.py
index ed5f2f6..10a96f9 100644
--- a/lib/lp/bugs/vocabularies.py
+++ b/lib/lp/bugs/vocabularies.py
@@ -48,7 +48,7 @@ from lp.registry.model.milestone import milestone_sort_key
 from lp.registry.model.productseries import ProductSeries
 from lp.registry.vocabularies import DistributionVocabulary
 from lp.services.database.interfaces import IStore
-from lp.services.database.sqlobject import CONTAINSSTRING, OR
+from lp.services.database.sqlobject import OR
 from lp.services.helpers import shortlist
 from lp.services.webapp.escaping import html_escape, structured
 from lp.services.webapp.interfaces import ILaunchBag
@@ -57,6 +57,7 @@ from lp.services.webapp.vocabulary import (
     IHugeVocabulary,
     NamedSQLObjectVocabulary,
     SQLObjectVocabularyBase,
+    StormVocabularyBase,
 )
 
 
@@ -91,14 +92,13 @@ class BugVocabulary(SQLObjectVocabularyBase):
 
 
 @implementer(IHugeVocabulary)
-class BugTrackerVocabulary(SQLObjectVocabularyBase):
+class BugTrackerVocabulary(StormVocabularyBase):
     """All web and email based external bug trackers."""
 
     displayname = "Select a bug tracker"
     step_title = "Search"
     _table = BugTracker
     _filter = True
-    _orderBy = "title"
     _order_by = [BugTracker.title]
 
     def toTerm(self, obj):
@@ -125,10 +125,10 @@ class BugTrackerVocabulary(SQLObjectVocabularyBase):
                 self._filter,
                 BugTracker.active == True,
                 Or(
-                    CONTAINSSTRING(BugTracker.name, query),
-                    CONTAINSSTRING(BugTracker.title, query),
-                    CONTAINSSTRING(BugTracker.summary, query),
-                    CONTAINSSTRING(BugTracker.baseurl, query),
+                    BugTracker.name.contains_string(query),
+                    BugTracker.title.contains_string(query),
+                    BugTracker.summary.contains_string(query),
+                    BugTracker.baseurl.contains_string(query),
                 ),
             ),
         )
diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
index 8a941d8..12f1035 100644
--- a/lib/lp/registry/model/product.py
+++ b/lib/lp/registry/model/product.py
@@ -348,12 +348,8 @@ class Product(
         notNull=False,
         default=None,
     )
-    bugtracker = ForeignKey(
-        foreignKey="BugTracker",
-        dbName="bugtracker",
-        notNull=False,
-        default=None,
-    )
+    bugtracker_id = Int(name="bugtracker", allow_none=True, default=None)
+    bugtracker = Reference(bugtracker_id, "BugTracker.id")
     official_answers = BoolCol(
         dbName="official_answers", notNull=True, default=False
     )
diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
index 135c433..46cd5cb 100644
--- a/lib/lp/registry/model/projectgroup.py
+++ b/lib/lp/registry/model/projectgroup.py
@@ -164,12 +164,8 @@ class ProjectGroup(
     )
     active = BoolCol(dbName="active", notNull=True, default=True)
     reviewed = BoolCol(dbName="reviewed", notNull=True, default=False)
-    bugtracker = ForeignKey(
-        foreignKey="BugTracker",
-        dbName="bugtracker",
-        notNull=False,
-        default=None,
-    )
+    bugtracker_id = Int(name="bugtracker", allow_none=True, default=None)
+    bugtracker = Reference(bugtracker_id, "BugTracker.id")
     bug_reporting_guidelines = StringCol(default=None)
     bug_reported_acknowledgement = StringCol(default=None)