launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #30409
[Merge] ~cjwatson/launchpad:stormify-product into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:stormify-product into launchpad:master.
Commit message:
Convert Product to Storm
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/450113
There are a number of places that try to handle either a `Distribution` or a `Product` using similar code, so to avoid too much hassle I also converted `Distribution.owner` and `Distribution.driver` to the new style. For similar reasons I ended up changing `SQLObjectVocabularyBase.__contains__` to accept `Storm` objects as well as `SQLBase` objects and to use Storm-style query syntax; in some ways this is a little odd, but `SQLObjectVocabularyBase` only has a few users left and will go away soon.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-product into launchpad:master.
diff --git a/lib/lp/bugs/doc/bugnotificationrecipients.rst b/lib/lp/bugs/doc/bugnotificationrecipients.rst
index c3688c9..ecbe624 100644
--- a/lib/lp/bugs/doc/bugnotificationrecipients.rst
+++ b/lib/lp/bugs/doc/bugnotificationrecipients.rst
@@ -71,8 +71,8 @@ Let's up some data for our test:
... )
>>> debian = Distribution.selectOneBy(name="debian")
>>> pmount = debian.getSourcePackage("pmount")
- >>> alsa_utils = Product.selectOneBy(name="alsa-utils")
- >>> gnomebaker = Product.selectOneBy(name="gnomebaker")
+ >>> alsa_utils = IStore(Product).find(Product, name="alsa-utils").one()
+ >>> gnomebaker = IStore(Product).find(Product, name="gnomebaker").one()
>>> personset = getUtility(IPersonSet)
Here's where getBugNotificationRecipients() starts off. First, a
diff --git a/lib/lp/codehosting/tests/test_acceptance.py b/lib/lp/codehosting/tests/test_acceptance.py
index ecf597a..ece2d62 100644
--- a/lib/lp/codehosting/tests/test_acceptance.py
+++ b/lib/lp/codehosting/tests/test_acceptance.py
@@ -35,6 +35,7 @@ from lp.codehosting.vfs import branch_id_to_path
from lp.registry.model.person import Person
from lp.registry.model.product import Product
from lp.services.config import config
+from lp.services.database.interfaces import IStore
from lp.services.testing.profiled import profiled
from lp.testing import TestCaseWithFactory
from lp.testing.layers import ZopelessAppServerLayer
@@ -210,7 +211,7 @@ class SSHTestCase(TestCaseWithTransport, LoomTestMixin, TestCaseWithFactory):
if productName is None:
product = None
else:
- product = Product.selectOneBy(name=productName)
+ product = IStore(Product).find(Product, name=productName).one()
namespace = get_branch_namespace(owner, product)
return namespace.getByName(branchName)
@@ -338,7 +339,7 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
if product_name == "+junk":
product = None
else:
- product = Product.selectOneBy(name=product_name)
+ product = IStore(Product).find(Product, name=product_name).one()
if branch_type == BranchType.MIRRORED:
url = "http://example.com"
else:
@@ -399,7 +400,10 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
ZopelessAppServerLayer.txn.begin()
branch = self.getDatabaseBranch("testuser", None, "test-branch")
branch.owner.name = "renamed-user"
- branch.setTarget(user=branch.owner, project=Product.byName("firefox"))
+ branch.setTarget(
+ user=branch.owner,
+ project=IStore(Product).find(Product, name="firefox").one(),
+ )
branch.name = "renamed-branch"
ZopelessAppServerLayer.txn.commit()
@@ -542,7 +546,7 @@ class AcceptanceTests(WithScenarios, SSHTestCase):
# We can also push branches to URLs like /+branch/firefox
# Hack 'firefox' so we have permission to do this.
ZopelessAppServerLayer.txn.begin()
- firefox = Product.selectOneBy(name="firefox")
+ firefox = IStore(Product).find(Product, name="firefox").one()
testuser = Person.selectOneBy(name="testuser")
firefox.development_focus.owner = testuser
ZopelessAppServerLayer.txn.commit()
diff --git a/lib/lp/registry/doc/commercialsubscription.rst b/lib/lp/registry/doc/commercialsubscription.rst
index 6912fe4..fe06b94 100644
--- a/lib/lp/registry/doc/commercialsubscription.rst
+++ b/lib/lp/registry/doc/commercialsubscription.rst
@@ -346,8 +346,9 @@ their commercial subscription was modified.
All the products are returned when no parameters are passed in.
>>> from lp.registry.model.product import Product
+ >>> from lp.services.database.interfaces import IStore
>>> review_listing = product_set.forReview(commercial_member)
- >>> review_listing.count() == Product.select().count()
+ >>> review_listing.count() == IStore(Product).find(Product).count()
True
The full text search will not match strings with dots in their name
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index 7045053..81040cb 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -261,12 +261,12 @@ class Distribution(
dbName="mugshot", foreignKey="LibraryFileAlias", default=None
)
domainname = StringCol(notNull=True)
- owner = ForeignKey(
- dbName="owner",
- foreignKey="Person",
- storm_validator=validate_person_or_closed_team,
- notNull=True,
+ owner_id = Int(
+ name="owner",
+ validator=validate_person_or_closed_team,
+ allow_none=False,
)
+ owner = Reference(owner_id, "Person.id")
registrant = ForeignKey(
dbName="registrant",
foreignKey="Person",
@@ -282,13 +282,13 @@ class Distribution(
)
bug_reporting_guidelines = StringCol(default=None)
bug_reported_acknowledgement = StringCol(default=None)
- driver = ForeignKey(
- dbName="driver",
- foreignKey="Person",
- storm_validator=validate_public_person,
- notNull=False,
+ driver_id = Int(
+ name="driver",
+ validator=validate_public_person,
+ allow_none=True,
default=None,
)
+ driver = Reference(driver_id, "Person.id")
members = ForeignKey(
dbName="members",
foreignKey="Person",
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 1aa22bc..6514417 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -203,7 +203,6 @@ from lp.services.database.sqlbase import (
SQLBase,
convert_storm_clause_to_string,
cursor,
- quote,
sqlvalues,
)
from lp.services.database.sqlobject import (
@@ -1181,12 +1180,12 @@ class Person(
ownership_participation = ClassAlias(TeamParticipation)
clauses.extend(
[
- Product._ownerID == ownership_participation.team_id,
+ Product._owner_id == ownership_participation.team_id,
ownership_participation.person_id == self.id,
]
)
else:
- clauses.append(Product._ownerID == self.id)
+ clauses.append(Product._owner_id == self.id)
# We only want to use the extra query if match_name is not None and it
# is not the empty string ('' or u'').
@@ -1269,11 +1268,11 @@ class Person(
TeamParticipation, Person.id == TeamParticipation.person_id
),
LeftJoin(
- Product, TeamParticipation.team_id == Product._ownerID
+ Product, TeamParticipation.team_id == Product._owner_id
),
LeftJoin(
Distribution,
- TeamParticipation.team_id == Distribution.ownerID,
+ TeamParticipation.team_id == Distribution.owner_id,
),
Join(
CommercialSubscription,
@@ -1300,31 +1299,25 @@ class Person(
The given pillar must be either an IProduct or an IDistribution.
"""
if IProduct.providedBy(pillar):
- where_clause = "product = %s" % sqlvalues(pillar)
+ pillar_clause = KarmaCache.product == pillar
elif IDistribution.providedBy(pillar):
- where_clause = "distribution = %s" % sqlvalues(pillar)
+ pillar_clause = KarmaCache.distribution == pillar
else:
raise AssertionError(
"Pillar must be a product or distro, got %s" % pillar
)
- replacements = sqlvalues(person=self)
- replacements["where_clause"] = where_clause
- query = (
- """
- SELECT DISTINCT KarmaCategory.id
- FROM KarmaCategory
- JOIN KarmaCache ON KarmaCache.category = KarmaCategory.id
- WHERE %(where_clause)s
- AND category IS NOT NULL
- AND person = %(person)s
- """
- % replacements
- )
- cur = cursor()
- cur.execute(query)
- ids = [id for [id] in cur.fetchall()]
- return IStore(KarmaCategory).find(
- KarmaCategory, KarmaCategory.id.is_in(ids)
+ return (
+ IStore(KarmaCategory)
+ .using(
+ KarmaCategory,
+ Join(KarmaCache, KarmaCache.category == KarmaCategory.id),
+ )
+ .find(
+ KarmaCategory,
+ pillar_clause,
+ IsNot(KarmaCache.category_id, None),
+ KarmaCache.person == self,
+ )
)
@property
@@ -4561,14 +4554,14 @@ class PersonSet:
def getPeopleWithBranches(self, product=None):
"""See `IPersonSet`."""
- branch_clause = "SELECT owner FROM Branch"
+ # Circular import.
+ from lp.code.model.branch import Branch
+
+ branch_params = {}
if product is not None:
- branch_clause += " WHERE product = %s" % quote(product)
- return Person.select(
- """
- Person.id in (%s)
- """
- % branch_clause
+ branch_params["where"] = Branch.product == product
+ return IStore(Person).find(
+ Person, Person.id.is_in(Select(Branch.owner_id, **branch_params))
)
def updatePersonalStandings(self):
diff --git a/lib/lp/registry/model/pillar.py b/lib/lp/registry/model/pillar.py
index 580d50d..de817fb 100644
--- a/lib/lp/registry/model/pillar.py
+++ b/lib/lp/registry/model/pillar.py
@@ -307,7 +307,7 @@ class PillarNameSet:
pillar_names = set(rows).union(
load_related(PillarName, rows, ["alias_for"])
)
- pillars = load_related(Product, pillar_names, ["productID"])
+ pillars = load_related(Product, pillar_names, ["product_id"])
pillars.extend(
load_related(ProjectGroup, pillar_names, ["projectgroup_id"])
)
@@ -333,7 +333,8 @@ class PillarName(SQLBase):
name = StringCol(
dbName="name", notNull=True, unique=True, alternateID=True
)
- product = ForeignKey(foreignKey="Product", dbName="product")
+ product_id = Int(name="product", allow_none=True)
+ product = Reference(product_id, "Product.id")
projectgroup_id = Int(name="project", allow_none=True)
projectgroup = Reference(projectgroup_id, "ProjectGroup.id")
distribution = ForeignKey(foreignKey="Distribution", dbName="distribution")
diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
index f828474..f3bb669 100644
--- a/lib/lp/registry/model/product.py
+++ b/lib/lp/registry/model/product.py
@@ -31,7 +31,16 @@ from storm.expr import (
Or,
Select,
)
-from storm.locals import Int, List, Reference, ReferenceSet, Store, Unicode
+from storm.locals import (
+ Bool,
+ DateTime,
+ Int,
+ List,
+ Reference,
+ ReferenceSet,
+ Store,
+ Unicode,
+)
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
@@ -143,17 +152,10 @@ from lp.registry.model.sourcepackagename import SourcePackageName
from lp.registry.model.teammembership import TeamParticipation
from lp.services.database import bulk
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 DBEnum
from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase, sqlvalues
-from lp.services.database.sqlobject import (
- BoolCol,
- ForeignKey,
- SQLObjectNotFound,
- StringCol,
-)
+from lp.services.database.stormbase import StormBase
from lp.services.database.stormexpr import (
ArrayAgg,
ArrayIntersects,
@@ -245,7 +247,7 @@ specification_policy_default = {
@implementer(IBugSummaryDimension, IHasCustomLanguageCodes, IProduct)
class Product(
- SQLBase,
+ StormBase,
BugTargetBase,
HasDriversMixin,
OfficialBugTagTargetMixin,
@@ -269,65 +271,60 @@ class Product(
):
"""A Product."""
- _table = "Product"
+ __storm_table__ = "Product"
+ id = Int(primary=True)
projectgroup_id = Int(name="project", allow_none=True, default=None)
projectgroup = Reference(projectgroup_id, "ProjectGroup.id")
- _owner = ForeignKey(
- dbName="owner",
- foreignKey="Person",
- storm_validator=validate_person_or_closed_team,
- notNull=True,
+ _owner_id = Int(
+ name="owner",
+ validator=validate_person_or_closed_team,
+ allow_none=False,
)
- registrant = ForeignKey(
- dbName="registrant",
- foreignKey="Person",
- storm_validator=validate_public_person,
- notNull=True,
+ _owner = Reference(_owner_id, "Person.id")
+ registrant_id = Int(
+ name="registrant", validator=validate_public_person, allow_none=False
)
- bug_supervisor = ForeignKey(
- dbName="bug_supervisor",
- foreignKey="Person",
- storm_validator=validate_person,
- notNull=False,
- default=None,
- )
- driver = ForeignKey(
- dbName="driver",
- foreignKey="Person",
- storm_validator=validate_person,
- notNull=False,
+ registrant = Reference(registrant_id, "Person.id")
+ bug_supervisor_id = Int(
+ name="bug_supervisor",
+ validator=validate_person,
+ allow_none=True,
default=None,
)
- name = StringCol(
- dbName="name", notNull=True, alternateID=True, unique=True
+ bug_supervisor = Reference(bug_supervisor_id, "Person.id")
+ driver_id = Int(
+ name="driver", validator=validate_person, allow_none=True, default=None
)
- display_name = StringCol(dbName="displayname", notNull=True)
- _title = StringCol(dbName="title", notNull=True)
- summary = StringCol(dbName="summary", notNull=True)
- description = StringCol(notNull=False, default=None)
- datecreated = UtcDateTimeCol(
- dbName="datecreated", notNull=True, default=UTC_NOW
+ driver = Reference(driver_id, "Person.id")
+ name = Unicode(name="name", allow_none=False)
+ display_name = Unicode(name="displayname", allow_none=False)
+ _title = Unicode(name="title", allow_none=False)
+ summary = Unicode(name="summary", allow_none=False)
+ description = Unicode(allow_none=True, default=None)
+ datecreated = DateTime(
+ name="datecreated",
+ allow_none=False,
+ default=UTC_NOW,
+ tzinfo=timezone.utc,
)
- homepageurl = StringCol(dbName="homepageurl", notNull=False, default=None)
- homepage_content = StringCol(default=None)
- icon_id = Int(name="icon", default=None)
+ homepageurl = Unicode(name="homepageurl", allow_none=True, default=None)
+ homepage_content = Unicode(default=None)
+ icon_id = Int(name="icon", allow_none=True, default=None)
icon = Reference(icon_id, "LibraryFileAlias.id")
- logo = ForeignKey(
- dbName="logo", foreignKey="LibraryFileAlias", default=None
+ logo_id = Int(name="logo", allow_none=True, default=None)
+ logo = Reference(logo_id, "LibraryFileAlias.id")
+ mugshot_id = Int(name="mugshot", allow_none=True, default=None)
+ mugshot = Reference(mugshot_id, "LibraryFileAlias.id")
+ screenshotsurl = Unicode(
+ name="screenshotsurl", allow_none=True, default=None
)
- mugshot = ForeignKey(
- dbName="mugshot", foreignKey="LibraryFileAlias", default=None
+ wikiurl = Unicode(name="wikiurl", allow_none=True, default=None)
+ programminglang = Unicode(
+ name="programminglang", allow_none=True, default=None
)
- screenshotsurl = StringCol(
- dbName="screenshotsurl", notNull=False, default=None
- )
- wikiurl = StringCol(dbName="wikiurl", notNull=False, default=None)
- programminglang = StringCol(
- dbName="programminglang", notNull=False, default=None
- )
- downloadurl = StringCol(dbName="downloadurl", notNull=False, default=None)
- lastdoap = StringCol(dbName="lastdoap", notNull=False, default=None)
+ downloadurl = Unicode(name="downloadurl", allow_none=True, default=None)
+ lastdoap = Unicode(name="lastdoap", allow_none=True, default=None)
translationgroup_id = Int(
name="translationgroup", allow_none=True, default=None
)
@@ -344,14 +341,14 @@ class Product(
translation_focus = Reference(translation_focus_id, "ProductSeries.id")
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
+ official_answers = Bool(
+ name="official_answers", allow_none=False, default=False
)
- official_blueprints = BoolCol(
- dbName="official_blueprints", notNull=True, default=False
+ official_blueprints = Bool(
+ name="official_blueprints", allow_none=False, default=False
)
- official_malone = BoolCol(
- dbName="official_malone", notNull=True, default=False
+ official_malone = Bool(
+ name="official_malone", allow_none=False, default=False
)
remote_product = Unicode(
name="remote_product", allow_none=True, default=None
@@ -363,6 +360,70 @@ class Product(
# to an artifact in the policy is sufficient for access.
access_policies = List(type=Int())
+ _creating = False
+
+ def __init__(
+ self,
+ owner,
+ registrant,
+ name,
+ display_name,
+ title,
+ summary,
+ projectgroup=None,
+ bug_supervisor=None,
+ driver=None,
+ description=None,
+ homepageurl=None,
+ icon=None,
+ logo=None,
+ mugshot=None,
+ screenshotsurl=None,
+ wikiurl=None,
+ programminglang=None,
+ downloadurl=None,
+ vcs=None,
+ information_type=InformationType.PUBLIC,
+ project_reviewed=False,
+ sourceforgeproject=None,
+ license_info=None,
+ ):
+ super().__init__()
+ try:
+ self._creating = True
+ self.owner = owner
+ self.registrant = registrant
+ self.name = name
+ self.display_name = display_name
+ self._title = title
+ self.summary = summary
+ self.projectgroup = projectgroup
+ self.bug_supervisor = bug_supervisor
+ self.driver = driver
+ self.description = description
+ self.homepageurl = homepageurl
+ self.icon = icon
+ self.logo = logo
+ self.mugshot = mugshot
+ self.screenshotsurl = screenshotsurl
+ self.wikiurl = wikiurl
+ self.programminglang = programminglang
+ self.downloadurl = downloadurl
+ self.vcs = vcs
+ self.information_type = information_type
+ self.project_reviewed = project_reviewed
+ self.sourceforgeproject = sourceforgeproject
+ self.license_info = license_info
+ except Exception:
+ # If validating references such as `owner` fails, then the new
+ # object may have been added to the store first. Remove it
+ # again in that case.
+ store = Store.of(self)
+ if store is not None:
+ store.remove(self)
+ raise
+ del self._creating
+
@property
def displayname(self):
return self.display_name
@@ -417,7 +478,7 @@ class Product(
if value in PROPRIETARY_INFORMATION_TYPES:
if self.answers_usage == ServiceUsage.LAUNCHPAD:
yield CannotChangeInformationType("Answers is enabled.")
- if self._SO_creating or value not in PROPRIETARY_INFORMATION_TYPES:
+ if self._creating or value not in PROPRIETARY_INFORMATION_TYPES:
return
# Additional checks when transitioning an existing product to a
# proprietary type
@@ -524,7 +585,7 @@ class Product(
# maintainer as required for the Product.
# However, only on edits. If this is a new Product it's handled
# already.
- if not self._SO_creating:
+ if not self._creating:
if (
old_info_type == InformationType.PUBLIC
and value != InformationType.PUBLIC
@@ -644,11 +705,11 @@ class Product(
"""See `HasMilestonesMixin`."""
return Milestone.product == self
- enable_bug_expiration = BoolCol(
- dbName="enable_bug_expiration", notNull=True, default=False
+ enable_bug_expiration = Bool(
+ name="enable_bug_expiration", allow_none=False, default=False
)
- project_reviewed = BoolCol(dbName="reviewed", notNull=True, default=False)
- reviewer_whiteboard = StringCol(notNull=False, default=None)
+ project_reviewed = Bool(name="reviewed", allow_none=False, default=False)
+ reviewer_whiteboard = Unicode(allow_none=True, default=None)
private_bugs = False
bug_sharing_policy = DBEnum(
enum=BugSharingPolicy, allow_none=True, default=None
@@ -661,9 +722,9 @@ class Product(
allow_none=True,
default=SpecificationSharingPolicy.PUBLIC,
)
- autoupdate = BoolCol(dbName="autoupdate", notNull=True, default=False)
+ autoupdate = Bool(name="autoupdate", allow_none=False, default=False)
freshmeatproject = None
- sourceforgeproject = StringCol(notNull=False, default=None)
+ sourceforgeproject = Unicode(allow_none=True, default=None)
# While the interface defines this field as required, we need to
# allow it to be NULL so we can create new product records before
# the corresponding series records.
@@ -671,9 +732,9 @@ class Product(
name="development_focus", allow_none=True, default=None
)
development_focus = Reference(development_focus_id, "ProductSeries.id")
- bug_reporting_guidelines = StringCol(default=None)
- bug_reported_acknowledgement = StringCol(default=None)
- enable_bugfiling_duplicate_search = BoolCol(notNull=True, default=True)
+ bug_reporting_guidelines = Unicode(default=None)
+ bug_reported_acknowledgement = Unicode(default=None)
+ enable_bugfiling_duplicate_search = Bool(allow_none=False, default=True)
def _validate_active(self, attr, value):
# Validate deactivation.
@@ -685,29 +746,27 @@ class Product(
)
return value
- active = BoolCol(
- dbName="active",
- notNull=True,
+ active = Bool(
+ name="active",
+ allow_none=False,
default=True,
- storm_validator=_validate_active,
+ validator=_validate_active,
)
def _validate_license_info(self, attr, value):
- if not self._SO_creating and value != self.license_info:
+ if not self._creating and value != self.license_info:
# Clear the project_reviewed and license_approved flags
# if the licence changes.
self._resetLicenseReview()
return value
- license_info = StringCol(
- dbName="license_info",
- default=None,
- storm_validator=_validate_license_info,
+ license_info = Unicode(
+ name="license_info", default=None, validator=_validate_license_info
)
def _validate_license_approved(self, attr, value):
"""Ensure licence approved is only applied to the correct licences."""
- if not self._SO_creating:
+ if not self._creating:
licenses = list(self.licenses)
if value:
if (
@@ -723,11 +782,11 @@ class Product(
self.project_reviewed = True
return value
- license_approved = BoolCol(
- dbName="license_approved",
- notNull=True,
+ license_approved = Bool(
+ name="license_approved",
+ allow_none=False,
default=False,
- storm_validator=_validate_license_approved,
+ validator=_validate_license_approved,
)
def getAllowedBugInformationTypes(self):
@@ -1720,7 +1779,7 @@ def get_precached_products(
for attr_name in role_names:
person_ids.update(
map(
- lambda x: getattr(x, attr_name + "ID"),
+ lambda x: getattr(x, attr_name + "_id"),
products_by_id.values(),
)
)
@@ -1880,7 +1939,7 @@ class ProductSet:
return result
def do_eager_load(rows):
- owner_ids = set(map(operator.attrgetter("_ownerID"), rows))
+ owner_ids = set(map(operator.attrgetter("_owner_id"), rows))
# +detailed-listing renders the person with team branding.
list(
getUtility(IPersonSet).getPrecachedPersonsFromIDs(
@@ -1892,12 +1951,12 @@ class ProductSet:
def get(self, productid):
"""See `IProductSet`."""
- try:
- return Product.get(productid)
- except SQLObjectNotFound:
+ product = IStore(Product).get(Product, productid)
+ if product is None:
raise NotFoundError(
"Product with ID %s does not exist" % str(productid)
)
+ return product
def getByName(self, name, ignore_inactive=False):
"""See `IProductSet`."""
@@ -1908,18 +1967,25 @@ class ProductSet:
def getProductsWithBranches(self, num_products=None):
"""See `IProductSet`."""
- results = Product.select(
- """
- Product.id in (
- select distinct(product) from Branch
- where lifecycle_status in %s)
- and Product.active
- """
- % sqlvalues(DEFAULT_BRANCH_STATUS_IN_LISTING),
- orderBy="name",
+ results = (
+ IStore(Product)
+ .find(
+ Product,
+ Product.id.is_in(
+ Select(
+ Branch.product_id,
+ where=Branch.lifecycle_status.is_in(
+ DEFAULT_BRANCH_STATUS_IN_LISTING
+ ),
+ distinct=True,
+ )
+ ),
+ Product.active,
+ )
+ .order_by(Product.name)
)
if num_products is not None:
- results = results.limit(num_products)
+ results = results[:num_products]
return results
def createProduct(
@@ -1975,7 +2041,7 @@ class ProductSet:
registrant=registrant,
name=name,
display_name=display_name,
- _title=title,
+ title=title,
projectgroup=projectgroup,
summary=summary,
description=description,
@@ -1983,7 +2049,6 @@ class ProductSet:
screenshotsurl=screenshotsurl,
wikiurl=wikiurl,
downloadurl=downloadurl,
- freshmeatproject=None,
sourceforgeproject=sourceforgeproject,
programminglang=programminglang,
project_reviewed=project_reviewed,
@@ -2211,7 +2276,7 @@ class ProductSet:
Product.id == ProductSeries.product_id,
POTemplate.productseries_id == ProductSeries.id,
Product.translations_usage == ServiceUsage.LAUNCHPAD,
- Person.id == Product._ownerID,
+ Person.id == Product._owner_id,
)
.config(distinct=True)
.order_by(Product.display_name)
diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
index 81457e3..668be1d 100644
--- a/lib/lp/registry/model/projectgroup.py
+++ b/lib/lp/registry/model/projectgroup.py
@@ -175,6 +175,7 @@ class ProjectGroup(
logo=None,
mugshot=None,
):
+ super().__init__()
try:
self.owner = owner
self.registrant = registrant
@@ -221,7 +222,9 @@ class ProjectGroup(
return list(self.getProducts(getUtility(ILaunchBag).user))
def getProduct(self, name):
- return Product.selectOneBy(projectgroup=self, name=name)
+ return (
+ IStore(Product).find(Product, projectgroup=self, name=name).one()
+ )
def getConfigurableProducts(self):
return [
@@ -336,7 +339,7 @@ class ProjectGroup(
"""See `OfficialBugTagTargetMixin`."""
And(
ProjectGroup.id == Product.projectgroup_id,
- Product.id == OfficialBugTag.productID,
+ Product.id == OfficialBugTag.product_id,
)
@property
diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py
index 5fc8c27..65f355b 100644
--- a/lib/lp/registry/services/sharingservice.py
+++ b/lib/lp/registry/services/sharingservice.py
@@ -162,13 +162,13 @@ class SharingService:
teams_sql = SQL("SELECT team from teams")
store = store.with_(with_statement)
if IProduct.implementedBy(pillar_class):
- ownerID = pillar_class._ownerID
+ owner_id = pillar_class._owner_id
else:
- ownerID = pillar_class.ownerID
+ owner_id = pillar_class.owner_id
filter = Or(
extra_filter or False,
- ownerID.is_in(teams_sql),
- pillar_class.driverID.is_in(teams_sql),
+ owner_id.is_in(teams_sql),
+ pillar_class.driver_id.is_in(teams_sql),
)
tables = [
AccessPolicyGrantFlat,
diff --git a/lib/lp/registry/stories/product/xx-product-index.rst b/lib/lp/registry/stories/product/xx-product-index.rst
index 06afe68..ec30192 100644
--- a/lib/lp/registry/stories/product/xx-product-index.rst
+++ b/lib/lp/registry/stories/product/xx-product-index.rst
@@ -27,13 +27,14 @@ Evolution has no external links.
Now update Tomcat to actually have this data:
>>> import transaction
- >>> from lp.services.database.sqlbase import flush_database_updates
>>> from lp.registry.enums import VCSType
>>> from lp.registry.model.product import Product
+ >>> from lp.services.database.interfaces import IStore
+ >>> from lp.services.database.sqlbase import flush_database_updates
>>> from lp.testing import ANONYMOUS, login, logout
>>> login(ANONYMOUS)
- >>> tomcat = Product.selectOneBy(name="tomcat")
+ >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
>>> tomcat.vcs = VCSType.GIT
>>> tomcat.homepageurl = "http://home.page/"
>>> tomcat.sourceforgeproject = "sf-tomcat"
@@ -67,7 +68,7 @@ When the sourceforge URL is identical to the homepage, we omit the homepage:
>>> login(ANONYMOUS)
- >>> tomcat = Product.selectOneBy(name="tomcat")
+ >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
>>> tomcat.homepageurl = "http://sourceforge.net/projects/sf-tomcat"
>>> logout()
@@ -130,7 +131,7 @@ If the project doesn't qualify for free hosting, or if it doesn't have
much time left on its commercial subscription, a portlet is displayed to
direct the owner to purchase a subscription.
- >>> firefox = Product.selectOneBy(name="firefox")
+ >>> firefox = IStore(Product).find(Product, name="firefox").one()
>>> ignored = login_person(firefox.owner)
>>> firefox.licenses = [License.OTHER_PROPRIETARY]
>>> firefox.license_info = "Internal project."
@@ -285,7 +286,9 @@ Aliases
When a project has one or more aliases, they're shown on the project's
home page.
- >>> Product.byName("firefox").setAliases(["iceweasel", "snowchicken"])
+ >>> IStore(Product).find(Product, name="firefox").one().setAliases(
+ ... ["iceweasel", "snowchicken"]
+ ... )
>>> anon_browser.open("http://launchpad.test/firefox")
>>> print(extract_text(find_tag_by_id(anon_browser.contents, "aliases")))
Also known as: iceweasel, snowchicken
diff --git a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
index da4df2c..f25171d 100644
--- a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
+++ b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.rst
@@ -133,7 +133,8 @@ release too.
>>> from lp.registry.model.person import Person
>>> from lp.registry.model.product import Product
- >>> tomcat = Product.selectOneBy(name="tomcat")
+ >>> from lp.services.database.interfaces import IStore
+ >>> tomcat = IStore(Product).find(Product, name="tomcat").one()
>>> print(tomcat.owner.name)
ubuntu-team
diff --git a/lib/lp/registry/stories/project/xx-project-index.rst b/lib/lp/registry/stories/project/xx-project-index.rst
index 6f170cb..dc3d406 100644
--- a/lib/lp/registry/stories/project/xx-project-index.rst
+++ b/lib/lp/registry/stories/project/xx-project-index.rst
@@ -158,14 +158,15 @@ Inactive products are not included in that list, though.
# Use the DB classes directly to avoid having to setup a zope interaction
# (i.e. login()) and bypass the security proxy.
>>> from lp.registry.model.product import Product
- >>> firefox = Product.byName("firefox")
+ >>> from lp.services.database.interfaces import IStore
+ >>> firefox = IStore(Product).find(Product, name="firefox").one()
# Unlink the source packages so the project can be deactivated.
>>> from lp.testing import unlink_source_packages
>>> login("admin@xxxxxxxxxxxxx")
>>> unlink_source_packages(firefox)
>>> firefox.active = False
- >>> firefox.syncUpdate()
+ >>> IStore(firefox).flush()
>>> logout()
>>> browser.open("http://launchpad.test/mozilla")
@@ -176,7 +177,7 @@ Inactive products are not included in that list, though.
<a...Mozilla Thunderbird</a>
>>> firefox.active = True
- >>> firefox.syncUpdate()
+ >>> IStore(firefox).flush()
Project Group bug subscriptions
diff --git a/lib/lp/registry/tests/test_commercialprojects_vocabularies.py b/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
index d9dbc16..3af8c17 100644
--- a/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
+++ b/lib/lp/registry/tests/test_commercialprojects_vocabularies.py
@@ -54,7 +54,7 @@ class TestCommProjVocabulary(TestCaseWithFactory):
def test_attributes(self):
self.assertEqual("Select a commercial project", self.vocab.displayname)
self.assertEqual("Search", self.vocab.step_title)
- self.assertEqual("displayname", self.vocab._orderBy)
+ self.assertEqual("displayname", self.vocab._order_by)
def test_searchForTerms_empty(self):
# An empty search will return all active maintained projects.
diff --git a/lib/lp/registry/tests/test_product.py b/lib/lp/registry/tests/test_product.py
index 895ee15..607d872 100644
--- a/lib/lp/registry/tests/test_product.py
+++ b/lib/lp/registry/tests/test_product.py
@@ -1805,7 +1805,7 @@ class ProductAttributeCacheTestCase(TestCaseWithFactory):
def setUp(self):
super().setUp()
- self.product = Product.selectOneBy(name="tomcat")
+ self.product = IStore(Product).find(Product, name="tomcat").one()
def testLicensesCache(self):
"""License cache should be cleared automatically."""
diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
index 8642d22..c03b5c4 100644
--- a/lib/lp/registry/vocabularies.py
+++ b/lib/lp/registry/vocabularies.py
@@ -87,7 +87,6 @@ from zope.interface import implementer
from zope.schema.interfaces import IVocabularyTokenized
from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from zope.security.interfaces import Unauthorized
-from zope.security.proxy import isinstance as zisinstance
from zope.security.proxy import removeSecurityProxy
from lp.answers.interfaces.question import IQuestion
@@ -147,7 +146,7 @@ from lp.registry.model.teammembership import TeamParticipation
from lp.services.database import bulk
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.interfaces import IStore
-from lp.services.database.sqlbase import SQLBase, sqlvalues
+from lp.services.database.sqlbase import sqlvalues
from lp.services.database.sqlobject import AND, CONTAINSSTRING, OR
from lp.services.database.stormexpr import (
RegexpMatch,
@@ -247,27 +246,16 @@ class KarmaCategoryVocabulary(NamedStormVocabulary):
@implementer(IHugeVocabulary)
-class ProductVocabulary(SQLObjectVocabularyBase):
+class ProductVocabulary(StormVocabularyBase):
"""All `IProduct` objects vocabulary."""
step_title = "Search"
_table = Product
- _orderBy = "displayname"
+ _order_by = "displayname"
+ _clauses = [Product.active]
displayname = "Select a project"
- def __contains__(self, obj):
- # Sometimes this method is called with an SQLBase instance, but
- # z3 form machinery sends through integer ids. This might be due
- # to a bug somewhere.
- where = "active='t' AND id=%d"
- if zisinstance(obj, SQLBase):
- product = self._table.selectOne(where % obj.id)
- return product is not None and product == obj
- else:
- product = self._table.selectOne(where % int(obj))
- return product is not None
-
def toTerm(self, obj):
"""See `IVocabulary`."""
return SimpleTerm(obj, obj.name, obj.title)
@@ -276,7 +264,11 @@ class ProductVocabulary(SQLObjectVocabularyBase):
"""See `IVocabularyTokenized`."""
# Product names are always lowercase.
token = token.lower()
- product = self._table.selectOneBy(name=token, active=True)
+ product = (
+ IStore(self._table)
+ .find(self._table, self._table.active, name=token)
+ .one()
+ )
if product is None:
raise LookupError(token)
return self.toTerm(product)
@@ -584,7 +576,7 @@ class ValidPersonOrTeamVocabulary(
@cachedproperty
def store(self):
"""The storm store."""
- return IStore(Product)
+ return IStore(Person)
@cachedproperty
def _karma_context_constraint(self):
@@ -1521,7 +1513,7 @@ class MilestoneWithDateExpectedVocabulary(MilestoneVocabulary):
@implementer(IHugeVocabulary)
-class CommercialProjectsVocabulary(NamedSQLObjectVocabulary):
+class CommercialProjectsVocabulary(NamedStormVocabulary):
"""List all commercial projects.
A commercial project is an active project that can have a commercial
@@ -1531,7 +1523,7 @@ class CommercialProjectsVocabulary(NamedSQLObjectVocabulary):
"""
_table = Product
- _orderBy = "displayname"
+ _order_by = "displayname"
step_title = "Search"
@property
@@ -1982,7 +1974,7 @@ class PillarVocabularyBase(NamedStormHugeVocabulary):
store = IStore(PillarName)
origin = [
PillarName,
- LeftJoin(Product, Product.id == PillarName.productID),
+ LeftJoin(Product, Product.id == PillarName.product_id),
]
base_clauses = [
ProductSet.getProductPrivacyFilter(getUtility(ILaunchBag).user)
diff --git a/lib/lp/scripts/harness.py b/lib/lp/scripts/harness.py
index 26ca7fe..d6dc0d0 100644
--- a/lib/lp/scripts/harness.py
+++ b/lib/lp/scripts/harness.py
@@ -75,7 +75,7 @@ def _get_locals():
d = Distribution.get(1)
p = Person.get(1)
ds = DistroSeries.get(1)
- prod = Product.get(1)
+ prod = store.get(Product, 1)
proj = store.get(ProjectGroup, 1)
b2 = store.get(Bug, 2)
b1 = store.get(Bug, 1)
diff --git a/lib/lp/services/database/bulk.py b/lib/lp/services/database/bulk.py
index bec5499..a90dc0f 100644
--- a/lib/lp/services/database/bulk.py
+++ b/lib/lp/services/database/bulk.py
@@ -185,7 +185,7 @@ def load_related(object_type, owning_objects, foreign_keys):
:param object_type: The object type to load - e.g. Person.
:param owning_objects: The objects holding the references. E.g. Bug.
:param foreign_keys: A list of attributes that should be inspected for
- keys. e.g. ['ownerID']
+ keys. e.g. ['owner_id']
"""
keys = set()
for owning_object in owning_objects:
diff --git a/lib/lp/services/statistics/model/statistics.py b/lib/lp/services/statistics/model/statistics.py
index 11b5376..205b05c 100644
--- a/lib/lp/services/statistics/model/statistics.py
+++ b/lib/lp/services/statistics/model/statistics.py
@@ -18,12 +18,15 @@ from lp.answers.enums import QuestionStatus
from lp.answers.model.question import Question
from lp.app.enums import ServiceUsage
from lp.blueprints.interfaces.specification import ISpecificationSet
+from lp.blueprints.model.specification import Specification
from lp.bugs.model.bug import Bug
from lp.bugs.model.bugtask import BugTask
from lp.code.interfaces.branchcollection import IAllBranches
from lp.code.interfaces.gitcollection import IAllGitRepositories
+from lp.code.model.branch import Branch
from lp.registry.interfaces.person import IPersonSet
from lp.registry.model.product import Product
+from lp.registry.model.productseries import ProductSeries
from lp.services.database.constants import UTC_NOW
from lp.services.database.interfaces import IStore
from lp.services.database.sqlbase import cursor
@@ -115,7 +118,7 @@ class LaunchpadStatisticSet:
self.update(
"products_using_malone",
- Product.selectBy(official_malone=True).count(),
+ store.find(Product, official_malone=True).count(),
)
ztm.commit()
@@ -139,65 +142,59 @@ class LaunchpadStatisticSet:
ztm.commit()
def _updateRegistryStatistics(self, ztm):
+ store = IStore(Product)
self.update(
"active_products",
- Product.select("active IS TRUE", distinct=True).count(),
+ store.find(Product, Product.active).config(distinct=True).count(),
)
self.update(
"products_with_translations",
- Product.select(
- """
- POTemplate.productseries = ProductSeries.id AND
- Product.id = ProductSeries.product AND
- Product.active = TRUE
- """,
- clauseTables=["ProductSeries", "POTemplate"],
- distinct=True,
- ).count(),
+ store.find(
+ Product,
+ POTemplate.productseries == ProductSeries.id,
+ ProductSeries.product == Product.id,
+ Product.active,
+ )
+ .config(distinct=True)
+ .count(),
)
self.update(
"products_with_blueprints",
- Product.select(
- "Specification.product=Product.id AND Product.active IS TRUE",
- distinct=True,
- clauseTables=["Specification"],
- ).count(),
+ store.find(
+ Product, Specification.product == Product.id, Product.active
+ )
+ .config(distinct=True)
+ .count(),
)
self.update(
"products_with_branches",
- Product.select(
- "Branch.product=Product.id AND Product.active IS TRUE",
- distinct=True,
- clauseTables=["Branch"],
- ).count(),
+ store.find(Product, Branch.product == Product.id, Product.active)
+ .config(distinct=True)
+ .count(),
)
self.update(
"products_with_bugs",
- Product.select(
- "BugTask.product=Product.id AND Product.active IS TRUE",
- distinct=True,
- clauseTables=["BugTask"],
- ).count(),
+ store.find(Product, BugTask.product == Product.id, Product.active)
+ .config(distinct=True)
+ .count(),
)
self.update(
"products_with_questions",
- Product.select(
- "Question.product=Product.id AND Product.active IS TRUE",
- distinct=True,
- clauseTables=["Question"],
- ).count(),
+ store.find(Product, Question.product == Product.id, Product.active)
+ .config(distinct=True)
+ .count(),
)
self.update(
"reviewed_products",
- Product.selectBy(project_reviewed=True, active=True).count(),
+ store.find(Product, project_reviewed=True, active=True).count(),
)
def _updateRosettaStatistics(self, ztm):
self.update(
"products_using_rosetta",
- Product.selectBy(
- translations_usage=ServiceUsage.LAUNCHPAD
- ).count(),
+ IStore(Product)
+ .find(Product, translations_usage=ServiceUsage.LAUNCHPAD)
+ .count(),
)
self.update(
"potemplate_count", IStore(POTemplate).find(POTemplate).count()
diff --git a/lib/lp/services/webapp/vocabulary.py b/lib/lp/services/webapp/vocabulary.py
index 057ecbf..294dd8c 100644
--- a/lib/lp/services/webapp/vocabulary.py
+++ b/lib/lp/services/webapp/vocabulary.py
@@ -329,19 +329,19 @@ class SQLObjectVocabularyBase(FilteredVocabularyBase):
# Sometimes this method is called with an SQLBase instance, but
# z3 form machinery sends through integer ids. This might be due
# to a bug somewhere.
- if zisinstance(obj, SQLBase):
- clause = self._table.q.id == obj.id
+ if zisinstance(obj, (SQLBase, Storm)): # noqa: B1
+ clause = self._table.id == obj.id
if self._filter:
# XXX kiko 2007-01-16: this code is untested.
clause = AND(clause, self._filter)
- found_obj = self._table.selectOne(clause)
+ found_obj = IStore(self._table).find(self._table, clause).one()
return found_obj is not None and found_obj == obj
else:
- clause = self._table.q.id == int(obj)
+ clause = self._table.id == int(obj)
if self._filter:
# XXX kiko 2007-01-16: this code is untested.
clause = AND(clause, self._filter)
- found_obj = self._table.selectOne(clause)
+ found_obj = IStore(self._table).find(self._table, clause).one()
return found_obj is not None
def getTerm(self, value):
diff --git a/lib/lp/translations/model/translationsoverview.py b/lib/lp/translations/model/translationsoverview.py
index 76c1a3f..793b1e5 100644
--- a/lib/lp/translations/model/translationsoverview.py
+++ b/lib/lp/translations/model/translationsoverview.py
@@ -8,6 +8,7 @@ from zope.interface import implementer
from lp.app.enums import InformationType, ServiceUsage
from lp.registry.model.distribution import Distribution
from lp.registry.model.product import Product
+from lp.services.database.interfaces import IStore
from lp.services.database.sqlbase import cursor, sqlvalues
from lp.services.utils import round_half_up
from lp.translations.interfaces.translationsoverview import (
@@ -94,7 +95,7 @@ class TranslationsOverview:
if maximum is None or relative_karma > maximum:
maximum = relative_karma
if product_id is not None:
- pillar = Product.get(product_id)
+ pillar = IStore(Product).get(Product, product_id)
elif distro_id is not None:
pillar = Distribution.get(distro_id)
else:
diff --git a/lib/lp/translations/stories/productseries/xx-productseries-translations.rst b/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
index 55d0792..d39fa47 100644
--- a/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
+++ b/lib/lp/translations/stories/productseries/xx-productseries-translations.rst
@@ -180,7 +180,8 @@ Launchpad Translations.
# Use the raw DB object to bypass the security proxy.
>>> from lp.registry.model.product import Product
- >>> product = Product.byName("bazaar")
+ >>> from lp.services.database.interfaces import IStore
+ >>> product = IStore(Product).find(Product, name="bazaar").one()
>>> product.translations_usage = ServiceUsage.NOT_APPLICABLE
When the owner now visits the upload page for trunk, there's a notice.
@@ -416,9 +417,8 @@ project admin does not see the link for configuring other branches.
A new series is added.
>>> from lp.registry.interfaces.series import SeriesStatus
- >>> from lp.registry.model.product import Product
>>> login("foo.bar@xxxxxxxxxxxxx")
- >>> evolution = Product.byName("evolution")
+ >>> evolution = IStore(Product).find(Product, name="evolution").one()
>>> series = factory.makeProductSeries(product=evolution, name="evo-new")
>>> series.status = SeriesStatus.EXPERIMENTAL
>>> logout()
diff --git a/lib/lp/translations/stories/standalone/xx-product-export.rst b/lib/lp/translations/stories/standalone/xx-product-export.rst
index 73edc9c..d0f7bce 100644
--- a/lib/lp/translations/stories/standalone/xx-product-export.rst
+++ b/lib/lp/translations/stories/standalone/xx-product-export.rst
@@ -47,9 +47,10 @@ Use the DB classes directly to avoid having to setup a zope interaction
>>> from lp.app.enums import ServiceUsage
>>> from lp.registry.model.product import Product
- >>> product = Product.byName("evolution")
+ >>> from lp.services.database.interfaces import IStore
+ >>> product = IStore(Product).find(Product, name="evolution").one()
>>> product.translations_usage = ServiceUsage.NOT_APPLICABLE
- >>> product.sync()
+ >>> IStore(product).flush()
>>> browser.open("http://translations.launchpad.test/evolution")
>>> browser.getLink("download")
Traceback (most recent call last):
@@ -59,7 +60,7 @@ Use the DB classes directly to avoid having to setup a zope interaction
Restore previous state for subsequent tests, and verify.
>>> product.translations_usage = ServiceUsage.LAUNCHPAD
- >>> product.sync()
+ >>> IStore(product).flush()
>>> browser.open("http://translations.launchpad.test/evolution")
>>> browser.getLink("download") is not None
True
diff --git a/lib/lp/translations/tests/test_autoapproval.py b/lib/lp/translations/tests/test_autoapproval.py
index de59805..eeadbb0 100644
--- a/lib/lp/translations/tests/test_autoapproval.py
+++ b/lib/lp/translations/tests/test_autoapproval.py
@@ -1203,7 +1203,7 @@ class TestCleanup(TestCaseWithFactory, GardenerDbUserMixin):
self.assertTrue(self._exists(entry_id))
entry.productseries.product.active = False
- entry.productseries.product.syncUpdate()
+ self.store.flush()
self.becomeTheGardener()
self.queue._cleanUpInactiveProductEntries(self.store)