launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #25265
[Merge] ~cjwatson/launchpad:stormify-sprint into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:stormify-sprint into launchpad:master.
Commit message:
Convert Sprint and SprintSpecification to Storm
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/390578
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-sprint into launchpad:master.
diff --git a/lib/lp/blueprints/browser/sprint.py b/lib/lp/blueprints/browser/sprint.py
index aae704a..da94d43 100644
--- a/lib/lp/blueprints/browser/sprint.py
+++ b/lib/lp/blueprints/browser/sprint.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 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).
"""Sprint views."""
@@ -27,8 +27,8 @@ from collections import defaultdict
import csv
from lazr.restful.utils import smartquote
-import six
import pytz
+import six
from zope.component import getUtility
from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.widgets import TextAreaWidget
@@ -462,6 +462,7 @@ class SprintTopicSetView(HasSpecificationsView, LaunchpadView):
# only a single item was selected, but we want to deal with a
# list for the general case, so convert it to a list
selected_specs = [selected_specs]
+ selected_specs = [int(speclink) for speclink in selected_specs]
if action == 'Accepted':
action_fn = self.context.acceptSpecificationLinks
diff --git a/lib/lp/blueprints/browser/tests/test_views.py b/lib/lp/blueprints/browser/tests/test_views.py
index 5b68b37..cbbe2e5 100644
--- a/lib/lp/blueprints/browser/tests/test_views.py
+++ b/lib/lp/blueprints/browser/tests/test_views.py
@@ -110,7 +110,8 @@ def test_suite():
for filename in filenames:
path = filename
one_test = LayeredDocFileSuite(
- path, setUp=setUp, tearDown=tearDown,
+ path,
+ setUp=lambda test: setUp(test, future=True), tearDown=tearDown,
layer=DatabaseFunctionalLayer,
stdout_logging_level=logging.WARNING)
suite.addTest(one_test)
diff --git a/lib/lp/blueprints/model/specification.py b/lib/lp/blueprints/model/specification.py
index 40cf6ab..4b37d10 100644
--- a/lib/lp/blueprints/model/specification.py
+++ b/lib/lp/blueprints/model/specification.py
@@ -23,14 +23,15 @@ from sqlobject import (
SQLRelatedJoin,
StringCol,
)
-from storm.expr import (
+from storm.locals import (
Count,
Desc,
Join,
Or,
+ ReferenceSet,
SQL,
+ Store,
)
-from storm.store import Store
from zope.component import getUtility
from zope.event import notify
from zope.interface import implementer
@@ -237,11 +238,13 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
joinColumn='specification', otherColumn='person',
intermediateTable='SpecificationSubscription',
orderBy=['display_name', 'name'])
- sprint_links = SQLMultipleJoin('SprintSpecification', orderBy='id',
- joinColumn='specification')
- sprints = SQLRelatedJoin('Sprint', orderBy='name',
- joinColumn='specification', otherColumn='sprint',
- intermediateTable='SprintSpecification')
+ sprint_links = ReferenceSet(
+ '<primary key>', 'SprintSpecification.specification_id',
+ order_by='SprintSpecification.id')
+ sprints = ReferenceSet(
+ '<primary key>', 'SprintSpecification.specification_id',
+ 'SprintSpecification.sprint_id', 'Sprint.id',
+ order_by='Sprint.name')
spec_dependency_links = SQLMultipleJoin('SpecificationDependency',
joinColumn='specification', orderBy='id')
@@ -827,13 +830,11 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
def unlinkSprint(self, sprint):
"""See ISpecification."""
- from lp.blueprints.model.sprintspecification import (
- SprintSpecification)
for sprint_link in self.sprint_links:
# sprints have unique names
if sprint_link.sprint.name == sprint.name:
- SprintSpecification.delete(sprint_link.id)
- return sprint_link
+ sprint_link.destroySelf()
+ return
# dependencies
def createDependency(self, specification):
@@ -1060,8 +1061,8 @@ class SpecificationSet(HasSpecificationsMixin):
def coming_sprints(self):
"""See ISpecificationSet."""
from lp.blueprints.model.sprint import Sprint
- return Sprint.select("time_ends > 'NOW'", orderBy='time_starts',
- limit=5)
+ rows = IStore(Sprint).find(Sprint, Sprint.time_ends > UTC_NOW)
+ return rows.order_by(Sprint.time_starts).config(limit=5)
def new(self, name, title, specurl, summary, definition_status,
owner, target, approver=None, assignee=None, drafter=None,
diff --git a/lib/lp/blueprints/model/sprint.py b/lib/lp/blueprints/model/sprint.py
index 2446437..ed6748a 100644
--- a/lib/lp/blueprints/model/sprint.py
+++ b/lib/lp/blueprints/model/sprint.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -8,17 +8,17 @@ __all__ = [
'HasSprintsMixin',
]
-
-from sqlobject import (
- BoolCol,
- ForeignKey,
- StringCol,
- )
+import pytz
from storm.locals import (
+ Bool,
+ DateTime,
Desc,
+ Int,
Join,
Or,
+ Reference,
Store,
+ Unicode,
)
from zope.component import getUtility
from zope.interface import implementer
@@ -38,7 +38,10 @@ from lp.blueprints.interfaces.sprint import (
ISprint,
ISprintSet,
)
-from lp.blueprints.model.specification import HasSpecificationsMixin
+from lp.blueprints.model.specification import (
+ HasSpecificationsMixin,
+ Specification,
+ )
from lp.blueprints.model.specificationsearch import (
get_specification_active_product_filter,
get_specification_filters,
@@ -51,46 +54,66 @@ from lp.registry.interfaces.person import (
validate_public_person,
)
from lp.registry.model.hasdrivers import HasDriversMixin
-from lp.services.database.constants import DEFAULT
-from lp.services.database.datetimecol import UtcDateTimeCol
-from lp.services.database.sqlbase import (
- flush_database_updates,
- quote,
- SQLBase,
+from lp.services.database.constants import (
+ DEFAULT,
+ UTC_NOW,
)
+from lp.services.database.interfaces import IStore
+from lp.services.database.sqlbase import flush_database_updates
+from lp.services.database.stormbase import StormBase
from lp.services.propertycache import cachedproperty
@implementer(ISprint, IHasLogo, IHasMugshot, IHasIcon)
-class Sprint(SQLBase, HasDriversMixin, HasSpecificationsMixin):
+class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
"""See `ISprint`."""
- _defaultOrder = ['name']
+ __storm_table__ = 'Sprint'
+ __storm_order__ = ['name']
# db field names
- owner = ForeignKey(
- dbName='owner', foreignKey='Person',
- storm_validator=validate_public_person, notNull=True)
- name = StringCol(notNull=True, alternateID=True)
- title = StringCol(notNull=True)
- summary = StringCol(notNull=True)
- driver = ForeignKey(
- dbName='driver', foreignKey='Person',
- storm_validator=validate_public_person)
- home_page = StringCol(notNull=False, default=None)
- homepage_content = StringCol(default=None)
- icon = ForeignKey(
- dbName='icon', foreignKey='LibraryFileAlias', default=None)
- logo = ForeignKey(
- dbName='logo', foreignKey='LibraryFileAlias', default=None)
- mugshot = ForeignKey(
- dbName='mugshot', foreignKey='LibraryFileAlias', default=None)
- address = StringCol(notNull=False, default=None)
- datecreated = UtcDateTimeCol(notNull=True, default=DEFAULT)
- time_zone = StringCol(notNull=True)
- time_starts = UtcDateTimeCol(notNull=True)
- time_ends = UtcDateTimeCol(notNull=True)
- is_physical = BoolCol(notNull=True, default=True)
+ id = Int(primary=True)
+ owner_id = Int(
+ name='owner', validator=validate_public_person, allow_none=False)
+ owner = Reference(owner_id, 'Person.id')
+ name = Unicode(allow_none=False)
+ title = Unicode(allow_none=False)
+ summary = Unicode(allow_none=False)
+ driver_id = Int(name='driver', validator=validate_public_person)
+ driver = Reference(driver_id, 'Person.id')
+ home_page = Unicode(allow_none=True, default=None)
+ homepage_content = Unicode(default=None)
+ icon_id = Int(name='icon', default=None)
+ icon = Reference(icon_id, 'LibraryFileAlias.id')
+ logo_id = Int(name='logo', default=None)
+ logo = Reference(logo_id, 'LibraryFileAlias.id')
+ mugshot_id = Int(name='mugshot', default=None)
+ mugshot = Reference(mugshot_id, 'LibraryFileAlias.id')
+ address = Unicode(allow_none=True, default=None)
+ datecreated = DateTime(tzinfo=pytz.UTC, allow_none=False, default=DEFAULT)
+ time_zone = Unicode(allow_none=False)
+ time_starts = DateTime(tzinfo=pytz.UTC, allow_none=False)
+ time_ends = DateTime(tzinfo=pytz.UTC, allow_none=False)
+ is_physical = Bool(allow_none=False, default=True)
+
+ def __init__(self, owner, name, title, time_zone, time_starts, time_ends,
+ summary, address=None, driver=None, home_page=None,
+ mugshot=None, logo=None, icon=None, is_physical=True):
+ super(Sprint, self).__init__()
+ self.owner = owner
+ self.name = name
+ self.title = title
+ self.time_zone = time_zone
+ self.time_starts = time_starts
+ self.time_ends = time_ends
+ self.summary = summary
+ self.address = address
+ self.driver = driver
+ self.home_page = home_page
+ self.mugshot = mugshot
+ self.logo = logo
+ self.icon = icon
+ self.is_physical = is_physical
# attributes
@@ -128,7 +151,7 @@ class Sprint(SQLBase, HasDriversMixin, HasSpecificationsMixin):
tables.append(Join(
SprintSpecification,
SprintSpecification.specification == Specification.id))
- query.append(SprintSpecification.sprintID == self.id)
+ query.append(SprintSpecification.sprint == self)
if not filter:
# filter could be None or [] then we decide the default
@@ -209,7 +232,7 @@ class Sprint(SQLBase, HasDriversMixin, HasSpecificationsMixin):
context. Here we are a sprint that could cover many products and/or
distros.
"""
- speclink = SprintSpecification.get(speclink_id)
+ speclink = Store.of(self).get(SprintSpecification, speclink_id)
assert (speclink.sprint.id == self.id)
return speclink
@@ -303,15 +326,16 @@ class SprintSet:
def __getitem__(self, name):
"""See `ISprintSet`."""
- return Sprint.selectOneBy(name=name)
+ return IStore(Sprint).find(Sprint, name=name).one()
def __iter__(self):
"""See `ISprintSet`."""
- return iter(Sprint.select("time_ends > 'NOW'", orderBy='time_starts'))
+ return iter(IStore(Sprint).find(
+ Sprint, Sprint.time_ends > UTC_NOW).order_by(Sprint.time_starts))
@property
def all(self):
- return Sprint.select(orderBy='-time_starts')
+ return IStore(Sprint).find(Sprint).order_by(Sprint.time_starts)
def new(self, owner, name, title, time_zone, time_starts, time_ends,
summary, address=None, driver=None, home_page=None,
@@ -329,48 +353,50 @@ class HasSprintsMixin:
implementing IHasSprints.
"""
- def _getBaseQueryAndClauseTablesForQueryingSprints(self):
- """Return the base SQL query and the clauseTables to be used when
- querying sprints related to this object.
+ def _getBaseClausesForQueryingSprints(self):
+ """Return the base Storm clauses to be used when querying sprints
+ related to this object.
Subclasses must overwrite this method if it doesn't suit them.
"""
- query = """
- Specification.%s = %s
- AND Specification.id = SprintSpecification.specification
- AND SprintSpecification.sprint = Sprint.id
- AND SprintSpecification.status = %s
- """ % (self._table, self.id,
- quote(SprintSpecificationStatus.ACCEPTED))
- return query, ['Specification', 'SprintSpecification']
+ try:
+ table = getattr(self, "__storm_table__")
+ except AttributeError:
+ # XXX cjwatson 2020-09-10: Remove this once all inheritors have
+ # been converted from SQLObject to Storm.
+ table = getattr(self, "_table")
+ return [
+ getattr(Specification, table.lower()) == self,
+ Specification.id == SprintSpecification.specification_id,
+ SprintSpecification.sprint == Sprint.id,
+ SprintSpecification.status == SprintSpecificationStatus.ACCEPTED,
+ ]
def getSprints(self):
- query, tables = self._getBaseQueryAndClauseTablesForQueryingSprints()
- return Sprint.select(
- query, clauseTables=tables, orderBy='-time_starts', distinct=True)
+ clauses = self._getBaseClausesForQueryingSprints()
+ return IStore(Sprint).find(Sprint, *clauses).order_by(
+ Desc(Sprint.time_starts)).config(distinct=True)
@cachedproperty
def sprints(self):
"""See IHasSprints."""
return list(self.getSprints())
- def getComingSprings(self):
- query, tables = self._getBaseQueryAndClauseTablesForQueryingSprints()
- query += " AND Sprint.time_ends > 'NOW'"
- return Sprint.select(
- query, clauseTables=tables, orderBy='time_starts',
- distinct=True, limit=5)
+ def getComingSprints(self):
+ clauses = self._getBaseClausesForQueryingSprints()
+ clauses.append(Sprint.time_ends > UTC_NOW)
+ return IStore(Sprint).find(Sprint, *clauses).order_by(
+ Sprint.time_starts).config(distinct=True, limit=5)
@cachedproperty
def coming_sprints(self):
"""See IHasSprints."""
- return list(self.getComingSprings())
+ return list(self.getComingSprints())
@property
def past_sprints(self):
"""See IHasSprints."""
- query, tables = self._getBaseQueryAndClauseTablesForQueryingSprints()
- query += " AND Sprint.time_ends <= 'NOW'"
- return Sprint.select(
- query, clauseTables=tables, orderBy='-time_starts',
- distinct=True)
+ clauses = self._getBaseClausesForQueryingSprints()
+ clauses.append(Sprint.time_ends <= UTC_NOW)
+ return IStore(Sprint).find(Sprint, *clauses).order_by(
+ Desc(Sprint.time_starts)).config(distinct=True)
diff --git a/lib/lp/blueprints/model/sprintspecification.py b/lib/lp/blueprints/model/sprintspecification.py
index 46e691a..eed7649 100644
--- a/lib/lp/blueprints/model/sprintspecification.py
+++ b/lib/lp/blueprints/model/sprintspecification.py
@@ -1,13 +1,17 @@
-# 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).
__metaclass__ = type
__all__ = ['SprintSpecification']
-from sqlobject import (
- ForeignKey,
- StringCol,
+import pytz
+from storm.locals import (
+ DateTime,
+ Int,
+ Reference,
+ Store,
+ Unicode,
)
from zope.interface import implementer
@@ -18,32 +22,41 @@ from lp.services.database.constants import (
DEFAULT,
UTC_NOW,
)
-from lp.services.database.datetimecol import UtcDateTimeCol
-from lp.services.database.enumcol import EnumCol
-from lp.services.database.sqlbase import SQLBase
+from lp.services.database.enumcol import DBEnum
+from lp.services.database.stormbase import StormBase
@implementer(ISprintSpecification)
-class SprintSpecification(SQLBase):
+class SprintSpecification(StormBase):
"""A link between a sprint and a specification."""
- _table = 'SprintSpecification'
+ __storm_table__ = 'SprintSpecification'
- sprint = ForeignKey(dbName='sprint', foreignKey='Sprint',
- notNull=True)
- specification = ForeignKey(dbName='specification',
- foreignKey='Specification', notNull=True)
- status = EnumCol(schema=SprintSpecificationStatus, notNull=True,
+ id = Int(primary=True)
+
+ sprint_id = Int(name='sprint', allow_none=False)
+ sprint = Reference(sprint_id, 'Sprint.id')
+ specification_id = Int(name='specification', allow_none=False)
+ specification = Reference(specification_id, 'Specification.id')
+ status = DBEnum(
+ enum=SprintSpecificationStatus, allow_none=False,
default=SprintSpecificationStatus.PROPOSED)
- whiteboard = StringCol(notNull=False, default=None)
- registrant = ForeignKey(
- dbName='registrant', foreignKey='Person',
- storm_validator=validate_public_person, notNull=True)
- date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
- decider = ForeignKey(
- dbName='decider', foreignKey='Person',
- storm_validator=validate_public_person, notNull=False, default=None)
- date_decided = UtcDateTimeCol(notNull=False, default=None)
+ whiteboard = Unicode(allow_none=True, default=None)
+ registrant_id = Int(
+ name='registrant', validator=validate_public_person, allow_none=False)
+ registrant = Reference(registrant_id, 'Person.id')
+ date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=DEFAULT)
+ decider_id = Int(
+ name='decider', validator=validate_public_person, allow_none=True,
+ default=None)
+ decider = Reference(decider_id, 'Person.id')
+ date_decided = DateTime(tzinfo=pytz.UTC, allow_none=True, default=None)
+
+ def __init__(self, sprint, specification, registrant):
+ super(SprintSpecification, self).__init__()
+ self.sprint = sprint
+ self.specification = specification
+ self.registrant = registrant
@property
def is_confirmed(self):
@@ -66,3 +79,6 @@ class SprintSpecification(SQLBase):
self.status = SprintSpecificationStatus.DECLINED
self.decider = decider
self.date_decided = UTC_NOW
+
+ def destroySelf(self):
+ Store.of(self).remove(self)
diff --git a/lib/lp/blueprints/vocabularies/sprint.py b/lib/lp/blueprints/vocabularies/sprint.py
index f300b62..f98df43 100644
--- a/lib/lp/blueprints/vocabularies/sprint.py
+++ b/lib/lp/blueprints/vocabularies/sprint.py
@@ -9,21 +9,17 @@ __all__ = [
'SprintVocabulary',
]
-
from lp.blueprints.model.sprint import Sprint
-from lp.services.webapp.vocabulary import NamedSQLObjectVocabulary
+from lp.services.database.constants import UTC_NOW
+from lp.services.webapp.vocabulary import NamedStormVocabulary
-class FutureSprintVocabulary(NamedSQLObjectVocabulary):
+class FutureSprintVocabulary(NamedStormVocabulary):
"""A vocab of all sprints that have not yet finished."""
_table = Sprint
-
- def __iter__(self):
- future_sprints = Sprint.select("time_ends > 'NOW'")
- for sprint in future_sprints:
- yield(self.toTerm(sprint))
+ _clauses = [Sprint.time_ends > UTC_NOW]
-class SprintVocabulary(NamedSQLObjectVocabulary):
+class SprintVocabulary(NamedStormVocabulary):
_table = Sprint
diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
index 8a22fa8..bc6c5e5 100644
--- a/lib/lp/registry/model/projectgroup.py
+++ b/lib/lp/registry/model/projectgroup.py
@@ -50,7 +50,11 @@ from lp.blueprints.model.specification import (
Specification,
)
from lp.blueprints.model.specificationsearch import search_specifications
-from lp.blueprints.model.sprint import HasSprintsMixin
+from lp.blueprints.model.sprint import (
+ HasSprintsMixin,
+ Sprint,
+ )
+from lp.blueprints.model.sprintspecification import SprintSpecification
from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
from lp.bugs.model.bugtarget import (
BugTargetBase,
@@ -239,15 +243,14 @@ class ProjectGroup(SQLBase, BugTargetBase, HasSpecificationsMixin,
""" See `IProjectGroup`."""
return not self.getBranches().is_empty()
- def _getBaseQueryAndClauseTablesForQueryingSprints(self):
- query = """
- Product.project = %s
- AND Specification.product = Product.id
- AND Specification.id = SprintSpecification.specification
- AND SprintSpecification.sprint = Sprint.id
- AND SprintSpecification.status = %s
- """ % sqlvalues(self, SprintSpecificationStatus.ACCEPTED)
- return query, ['Product', 'Specification', 'SprintSpecification']
+ def _getBaseClausesForQueryingSprints(self):
+ return [
+ Product.projectgroup == self,
+ Specification.product == Product.id,
+ Specification.id == SprintSpecification.specification_id,
+ SprintSpecification.sprint == Sprint.id,
+ SprintSpecification.status == SprintSpecificationStatus.ACCEPTED,
+ ]
def specifications(self, user, sort=None, quantity=None, filter=None,
series=None, need_people=True, need_branches=True,
diff --git a/lib/lp/services/worlddata/vocabularies.py b/lib/lp/services/worlddata/vocabularies.py
index 58d2c8e..be963f4 100644
--- a/lib/lp/services/worlddata/vocabularies.py
+++ b/lib/lp/services/worlddata/vocabularies.py
@@ -1,6 +1,8 @@
# Copyright 2009 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+from __future__ import absolute_import, print_function, unicode_literals
+
__all__ = [
'CountryNameVocabulary',
'LanguageVocabulary',
@@ -10,6 +12,7 @@ __all__ = [
__metaclass__ = type
import pytz
+import six
from sqlobject import SQLObjectNotFound
from zope.interface import alsoProvides
from zope.schema.vocabulary import (
@@ -24,7 +27,7 @@ from lp.services.worlddata.model.country import Country
from lp.services.worlddata.model.language import Language
# create a sorted list of the common time zone names, with UTC at the start
-_values = sorted(pytz.common_timezones)
+_values = sorted(six.ensure_text(tz) for tz in pytz.common_timezones)
_values.remove('UTC')
_values.insert(0, 'UTC')
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index f5ad237..c109014 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -1121,14 +1121,14 @@ class BareLaunchpadObjectFactory(ObjectFactory):
def makeSprint(self, title=None, name=None):
"""Make a sprint."""
if title is None:
- title = self.getUniqueString('title')
+ title = self.getUniqueUnicode('title')
owner = self.makePerson()
if name is None:
- name = self.getUniqueString('name')
+ name = self.getUniqueUnicode('name')
time_starts = datetime(2009, 1, 1, tzinfo=pytz.UTC)
time_ends = datetime(2009, 1, 2, tzinfo=pytz.UTC)
- time_zone = 'UTC'
- summary = self.getUniqueString('summary')
+ time_zone = u'UTC'
+ summary = self.getUniqueUnicode('summary')
return getUtility(ISprintSet).new(
owner=owner, name=name, title=title, time_zone=time_zone,
time_starts=time_starts, time_ends=time_ends, summary=summary)