← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Convert Poll and friends to Storm

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/386813
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-poll into launchpad:master.
diff --git a/lib/lp/registry/adapters.py b/lib/lp/registry/adapters.py
index ac453fe..e5016c4 100644
--- a/lib/lp/registry/adapters.py
+++ b/lib/lp/registry/adapters.py
@@ -89,29 +89,29 @@ class PollSubset:
         """See IPollSubset."""
         assert self.team is not None, (
             'team cannot be None to call this method.')
-        return getUtility(IPollSet).selectByTeam(self.team)
+        return getUtility(IPollSet).findByTeam(self.team)
 
     def getOpenPolls(self, when=None):
         """See IPollSubset."""
         assert self.team is not None, (
             'team cannot be None to call this method.')
-        return getUtility(IPollSet).selectByTeam(
-            self.team, [PollStatus.OPEN], orderBy='datecloses', when=when)
+        return getUtility(IPollSet).findByTeam(
+            self.team, [PollStatus.OPEN], order_by='datecloses', when=when)
 
     def getClosedPolls(self, when=None):
         """See IPollSubset."""
         assert self.team is not None, (
             'team cannot be None to call this method.')
-        return getUtility(IPollSet).selectByTeam(
-            self.team, [PollStatus.CLOSED], orderBy='datecloses', when=when)
+        return getUtility(IPollSet).findByTeam(
+            self.team, [PollStatus.CLOSED], order_by='datecloses', when=when)
 
     def getNotYetOpenedPolls(self, when=None):
         """See IPollSubset."""
         assert self.team is not None, (
             'team cannot be None to call this method.')
-        return getUtility(IPollSet).selectByTeam(
+        return getUtility(IPollSet).findByTeam(
             self.team, [PollStatus.NOT_YET_OPENED],
-            orderBy='dateopens', when=when)
+            order_by='dateopens', when=when)
 
 
 def productseries_to_product(productseries):
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index e46cce4..8ece6c2 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -1676,17 +1676,17 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
     @cachedproperty
     def openpolls(self):
         assert self.context.is_team
-        return IPollSubset(self.context).getOpenPolls()
+        return list(IPollSubset(self.context).getOpenPolls())
 
     @cachedproperty
     def closedpolls(self):
         assert self.context.is_team
-        return IPollSubset(self.context).getClosedPolls()
+        return list(IPollSubset(self.context).getClosedPolls())
 
     @cachedproperty
     def notyetopenedpolls(self):
         assert self.context.is_team
-        return IPollSubset(self.context).getNotYetOpenedPolls()
+        return list(IPollSubset(self.context).getNotYetOpenedPolls())
 
     @cachedproperty
     def contributions(self):
diff --git a/lib/lp/registry/browser/tests/poll-views.txt b/lib/lp/registry/browser/tests/poll-views.txt
index 2b67312..b9e1e65 100644
--- a/lib/lp/registry/browser/tests/poll-views.txt
+++ b/lib/lp/registry/browser/tests/poll-views.txt
@@ -60,7 +60,7 @@ has not opened.
     >>> close_date = open_date + timedelta(weeks=1)
     >>> poll_subset = IPollSubset(team)
     >>> poll = poll_subset.new(
-    ...     'name', 'title', 'proposition', open_date, close_date,
+    ...     u'name', u'title', u'proposition', open_date, close_date,
     ...     PollSecrecy.OPEN, False)
 
     >>> ignored = login_person(user)
diff --git a/lib/lp/registry/browser/tests/poll-views_0.txt b/lib/lp/registry/browser/tests/poll-views_0.txt
index b55c376..a89220f 100644
--- a/lib/lp/registry/browser/tests/poll-views_0.txt
+++ b/lib/lp/registry/browser/tests/poll-views_0.txt
@@ -53,7 +53,8 @@ Now we successfully create a poll which starts 12h from now.
 
 == Displaying results of condorcet polls ==
 
-  >>> poll = getUtility(IPollSet).getByTeamAndName(ubuntu_team, 'director-2004')
+  >>> poll = getUtility(IPollSet).getByTeamAndName(
+  ...     ubuntu_team, u'director-2004')
   >>> poll.type.title
   'Condorcet Voting'
 
diff --git a/lib/lp/registry/browser/tests/test_breadcrumbs.py b/lib/lp/registry/browser/tests/test_breadcrumbs.py
index 020dfe5..6c30093 100644
--- a/lib/lp/registry/browser/tests/test_breadcrumbs.py
+++ b/lib/lp/registry/browser/tests/test_breadcrumbs.py
@@ -1,12 +1,15 @@
 # Copyright 2009-2011 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
+
 __metaclass__ = type
 
 from zope.component import getUtility
 
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.registry.browser.tests.test_pillar_sharing import SharingBaseTestCase
+from lp.registry.interfaces.nameblacklist import INameBlacklistSet
 from lp.services.webapp.publisher import canonical_url
 from lp.testing import login_person
 from lp.testing.breadcrumbs import BaseBreadcrumbTestCase
@@ -153,8 +156,6 @@ class TestPollBreadcrumb(BaseBreadcrumbTestCase):
         last_crumb = crumbs[-1]
         self.assertEqual(self.poll.title, last_crumb.text)
 
-from lp.registry.interfaces.nameblacklist import INameBlacklistSet
-
 
 class TestNameblacklistBreadcrumb(BaseBreadcrumbTestCase):
     """Test breadcrumbs for +nameblacklist."""
diff --git a/lib/lp/registry/browser/tests/test_poll.py b/lib/lp/registry/browser/tests/test_poll.py
index 8dc202a..825bb81 100644
--- a/lib/lp/registry/browser/tests/test_poll.py
+++ b/lib/lp/registry/browser/tests/test_poll.py
@@ -3,6 +3,8 @@
 
 """Tests for IPoll views."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
 import os
diff --git a/lib/lp/registry/doc/person-merge.txt b/lib/lp/registry/doc/person-merge.txt
index ffb12b9..40b7030 100644
--- a/lib/lp/registry/doc/person-merge.txt
+++ b/lib/lp/registry/doc/person-merge.txt
@@ -384,7 +384,7 @@ hand, are carried over just like when merging people.
     >>> today = datetime.now(pytz.timezone('UTC'))
     >>> tomorrow = today + timedelta(days=1)
     >>> poll = IPollSubset(test_team).new(
-    ...     'test-poll', 'Title', 'Proposition', today, tomorrow,
+    ...     u'test-poll', u'Title', u'Proposition', today, tomorrow,
     ...     PollSecrecy.OPEN, allowspoilt=True)
 
     # test_team has a superteam, one active member and a poll.
@@ -399,7 +399,7 @@ hand, are carried over just like when merging people.
     [u'name12']
 
     >>> list(IPollSubset(test_team).getAll())
-    [<Poll at ...]
+    [<lp.registry.model.poll.Poll object at ...]
 
     # Landscape-developers has no super teams, two members and no polls.
 
diff --git a/lib/lp/registry/doc/poll-preconditions.txt b/lib/lp/registry/doc/poll-preconditions.txt
index a4b5374..f1fcc1c 100644
--- a/lib/lp/registry/doc/poll-preconditions.txt
+++ b/lib/lp/registry/doc/poll-preconditions.txt
@@ -18,9 +18,9 @@ should be threated as so.
 
   >>> pollset = getUtility(IPollSet)
   >>> director_election = pollset.getByTeamAndName(ubuntu_team,
-  ...                                              'director-2004')
+  ...                                              u'director-2004')
   >>> director_options = director_election.getActiveOptions()
-  >>> leader_election = pollset.getByTeamAndName(ubuntu_team, 'leader-2004')
+  >>> leader_election = pollset.getByTeamAndName(ubuntu_team, u'leader-2004')
   >>> leader_options = leader_election.getActiveOptions()
   >>> opendate = leader_election.dateopens
   >>> onesec = timedelta(seconds=1)
diff --git a/lib/lp/registry/doc/poll.txt b/lib/lp/registry/doc/poll.txt
index 269b9ec..6ef79a1 100644
--- a/lib/lp/registry/doc/poll.txt
+++ b/lib/lp/registry/doc/poll.txt
@@ -41,12 +41,12 @@ a given team (in our case, the 'Ubuntu Team')
 Now we create a new poll on this team.
   >>> opendate = datetime(2005, 1, 1, tzinfo=pytz.timezone('UTC'))
   >>> closedate = opendate + timedelta(weeks=2)
-  >>> title = "2005 Leader's Elections"
-  >>> proposition = "Who's going to be the next leader?"
+  >>> title = u"2005 Leader's Elections"
+  >>> proposition = u"Who's going to be the next leader?"
   >>> type = PollAlgorithm.SIMPLE
   >>> secrecy = PollSecrecy.SECRET
   >>> allowspoilt = True
-  >>> poll = pollsubset.new("leader-election", title, proposition, opendate,
+  >>> poll = pollsubset.new(u"leader-election", title, proposition, opendate,
   ...                       closedate, secrecy, allowspoilt, type)
 
 Now we test the if the poll is open or closed in some specific dates.
@@ -82,9 +82,9 @@ start with zero options. We're responsible for adding new ones.
   0
 
 Let's add some options to this poll, so people can start voting. :)
-  >>> will = poll.newOption('wgraham', 'Will Graham')
-  >>> jack = poll.newOption('jcrawford', 'Jack Crawford')
-  >>> francis = poll.newOption('fd', 'Francis Dolarhyde')
+  >>> will = poll.newOption(u'wgraham', u'Will Graham')
+  >>> jack = poll.newOption(u'jcrawford', u'Jack Crawford')
+  >>> francis = poll.newOption(u'fd', u'Francis Dolarhyde')
   >>> [o.title for o in poll.getActiveOptions()]
   [u'Francis Dolarhyde', u'Jack Crawford', u'Will Graham']
 
@@ -111,17 +111,17 @@ still open.
 Now we create a Condorcet poll on this team and add some options to it, so
 people can start voting.
 
-  >>> title = "2005 Director's Elections"
-  >>> proposition = "Who's going to be the next director?"
+  >>> title = u"2005 Director's Elections"
+  >>> proposition = u"Who's going to be the next director?"
   >>> type = PollAlgorithm.CONDORCET
   >>> secrecy = PollSecrecy.SECRET
   >>> allowspoilt = True
-  >>> poll2 = pollsubset.new("director-election", title, proposition, opendate,
-  ...                        closedate, secrecy, allowspoilt, type)
-  >>> a = poll2.newOption('A', 'Option A')
-  >>> b = poll2.newOption('B', 'Option B')
-  >>> c = poll2.newOption('C', 'Option C')
-  >>> d = poll2.newOption('D', 'Option D')
+  >>> poll2 = pollsubset.new(u"director-election", title, proposition,
+  ...                        opendate, closedate, secrecy, allowspoilt, type)
+  >>> a = poll2.newOption(u'A', u'Option A')
+  >>> b = poll2.newOption(u'B', u'Option B')
+  >>> c = poll2.newOption(u'C', u'Option C')
+  >>> d = poll2.newOption(u'D', u'Option D')
 
   >>> options = {b: 1, d: 2, c: 3}
   >>> votes = poll2.storeCondorcetVote(member, options, when=opendate)
diff --git a/lib/lp/registry/interfaces/poll.py b/lib/lp/registry/interfaces/poll.py
index a6dd40d..8fab288 100644
--- a/lib/lp/registry/interfaces/poll.py
+++ b/lib/lp/registry/interfaces/poll.py
@@ -296,16 +296,16 @@ class IPollSet(Interface):
             secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
         """Create a new Poll for the given team."""
 
-    def selectByTeam(team, status=PollStatus.ALL, orderBy=None, when=None):
+    def findByTeam(team, status=PollStatus.ALL, order_by=None, when=None):
         """Return all Polls for the given team, filtered by status.
 
         :status: is a sequence containing as many values as you want from
         PollStatus.
 
-        :orderBy: can be either a string with the column name you want to sort
-        or a list of column names as strings.
-        If no orderBy is specified the results will be ordered using the
-        default ordering specified in Poll._defaultOrder.
+        :order_by: can be either a string with the column name you want to
+        sort or a list of column names as strings.
+        If no order_by is specified the results will be ordered using the
+        default ordering specified in Poll.sortingColumns.
 
         The optional :when argument is used only by our tests, to test if the
         poll is/was/will-be open at a specific date.
@@ -407,7 +407,7 @@ class IPollOptionSet(Interface):
     def new(poll, name, title, active=True):
         """Create a new PollOption."""
 
-    def selectByPoll(poll, only_active=False):
+    def findByPoll(poll, only_active=False):
         """Return all PollOptions of the given poll.
 
         If :only_active is True, then return only the active polls.
diff --git a/lib/lp/registry/model/poll.py b/lib/lp/registry/model/poll.py
index b75852a..b34786b 100644
--- a/lib/lp/registry/model/poll.py
+++ b/lib/lp/registry/model/poll.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
+
 __metaclass__ = type
 __all__ = [
     'Poll',
@@ -16,16 +18,16 @@ __all__ = [
 from datetime import datetime
 
 import pytz
-from sqlobject import (
-    AND,
-    BoolCol,
-    ForeignKey,
-    IntCol,
-    OR,
-    SQLObjectNotFound,
-    StringCol,
+from storm.locals import (
+    And,
+    Bool,
+    DateTime,
+    Int,
+    Or,
+    Reference,
+    Store,
+    Unicode,
     )
-from storm.store import Store
 from zope.component import getUtility
 from zope.interface import implementer
 
@@ -44,43 +46,57 @@ from lp.registry.interfaces.poll import (
     PollSecrecy,
     PollStatus,
     )
-from lp.services.database.datetimecol import UtcDateTimeCol
-from lp.services.database.enumcol import EnumCol
-from lp.services.database.sqlbase import (
-    SQLBase,
-    sqlvalues,
-    )
+from lp.services.database.enumcol import DBEnum
+from lp.services.database.interfaces import IStore
+from lp.services.database.sqlbase import sqlvalues
+from lp.services.database.stormbase import StormBase
 from lp.services.tokens import create_token
 
 
 @implementer(IPoll)
-class Poll(SQLBase):
+class Poll(StormBase):
     """See IPoll."""
-    _table = 'Poll'
+    __storm_table__ = 'Poll'
     sortingColumns = ['title', 'id']
-    _defaultOrder = sortingColumns
+    __storm_order__ = sortingColumns
+
+    id = Int(primary=True)
+
+    team_id = Int(
+        name='team', validator=validate_public_person, allow_none=False)
+    team = Reference(team_id, 'Person.id')
 
-    team = ForeignKey(
-        dbName='team', foreignKey='Person',
-        storm_validator=validate_public_person, notNull=True)
+    name = Unicode(name='name', allow_none=False)
 
-    name = StringCol(dbName='name', notNull=True)
+    title = Unicode(name='title', allow_none=False)
 
-    title = StringCol(dbName='title', notNull=True, unique=True)
+    dateopens = DateTime(tzinfo=pytz.UTC, name='dateopens', allow_none=False)
 
-    dateopens = UtcDateTimeCol(dbName='dateopens', notNull=True)
+    datecloses = DateTime(tzinfo=pytz.UTC, name='datecloses', allow_none=False)
 
-    datecloses = UtcDateTimeCol(dbName='datecloses', notNull=True)
+    proposition = Unicode(name='proposition', allow_none=False)
 
-    proposition = StringCol(dbName='proposition',  notNull=True)
+    type = DBEnum(
+        name='type', enum=PollAlgorithm, default=PollAlgorithm.SIMPLE)
 
-    type = EnumCol(dbName='type', enum=PollAlgorithm,
-                   default=PollAlgorithm.SIMPLE)
+    allowspoilt = Bool(name='allowspoilt', default=True, allow_none=False)
 
-    allowspoilt = BoolCol(dbName='allowspoilt', default=True, notNull=True)
+    secrecy = DBEnum(
+        name='secrecy', enum=PollSecrecy, default=PollSecrecy.SECRET)
 
-    secrecy = EnumCol(dbName='secrecy', enum=PollSecrecy,
-                      default=PollSecrecy.SECRET)
+    def __init__(self, team, name, title, proposition, dateopens, datecloses,
+                 secrecy=PollSecrecy.SECRET, allowspoilt=True,
+                 type=PollAlgorithm.SIMPLE):
+        super(Poll, self).__init__()
+        self.team = team
+        self.name = name
+        self.title = title
+        self.proposition = proposition
+        self.dateopens = dateopens
+        self.datecloses = datecloses
+        self.secrecy = secrecy
+        self.allowspoilt = allowspoilt
+        self.type = type
 
     def newOption(self, name, title, active=True):
         """See IPoll."""
@@ -116,20 +132,20 @@ class Poll(SQLBase):
 
     def getAllOptions(self):
         """See IPoll."""
-        return getUtility(IPollOptionSet).selectByPoll(self)
+        return getUtility(IPollOptionSet).findByPoll(self)
 
     def getActiveOptions(self):
         """See IPoll."""
-        return getUtility(IPollOptionSet).selectByPoll(self, only_active=True)
+        return getUtility(IPollOptionSet).findByPoll(self, only_active=True)
 
     def getVotesByPerson(self, person):
         """See IPoll."""
-        return Vote.selectBy(person=person, poll=self)
+        return IStore(Vote).find(Vote, person=person, poll=self)
 
     def personVoted(self, person):
         """See IPoll."""
-        results = VoteCast.selectBy(person=person, poll=self)
-        return bool(results.count())
+        results = IStore(VoteCast).find(VoteCast, person=person, poll=self)
+        return not results.is_empty()
 
     def removeOption(self, option, when=None):
         """See IPoll."""
@@ -141,7 +157,7 @@ class Poll(SQLBase):
 
     def getOptionByName(self, name):
         """See IPoll."""
-        return PollOption.selectOneBy(poll=self, name=name)
+        return IStore(PollOption).find(PollOption, poll=self, name=name).one()
 
     def _assertEverythingOkAndGetVoter(self, person, when=None):
         """Use assertions to Make sure all pre-conditions for a person to vote
@@ -210,7 +226,7 @@ class Poll(SQLBase):
     def getTotalVotes(self):
         """See IPoll."""
         assert self.isClosed()
-        return Vote.selectBy(poll=self).count()
+        return IStore(Vote).find(Vote, poll=self).count()
 
     def getWinners(self):
         """See IPoll."""
@@ -235,7 +251,7 @@ class Poll(SQLBase):
         results = Store.of(self).execute(query).get_all()
         if not results:
             return None
-        return [PollOption.get(id) for (id,) in results]
+        return [IStore(PollOption).get(PollOption, id) for (id,) in results]
 
     def getPairwiseMatrix(self):
         """See IPoll."""
@@ -278,59 +294,72 @@ class PollSet:
     def new(self, team, name, title, proposition, dateopens, datecloses,
             secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
         """See IPollSet."""
-        return Poll(team=team, name=name, title=title,
-                proposition=proposition, dateopens=dateopens,
-                datecloses=datecloses, secrecy=secrecy,
-                allowspoilt=allowspoilt, type=poll_type)
-
-    def selectByTeam(self, team, status=PollStatus.ALL, orderBy=None,
-                     when=None):
+        poll = Poll(
+            team=team, name=name, title=title,
+            proposition=proposition, dateopens=dateopens,
+            datecloses=datecloses, secrecy=secrecy,
+            allowspoilt=allowspoilt, type=poll_type)
+        IStore(Poll).add(poll)
+        return poll
+
+    def findByTeam(self, team, status=PollStatus.ALL, order_by=None,
+                   when=None):
         """See IPollSet."""
         if when is None:
             when = datetime.now(pytz.timezone('UTC'))
 
-        if orderBy is None:
-            orderBy = Poll.sortingColumns
+        if order_by is None:
+            order_by = Poll.sortingColumns
 
         status = set(status)
         status_clauses = []
         if PollStatus.OPEN in status:
-            status_clauses.append(AND(Poll.q.dateopens <= when,
-                                    Poll.q.datecloses > when))
+            status_clauses.append(
+                And(Poll.dateopens <= when, Poll.datecloses > when))
         if PollStatus.CLOSED in status:
-            status_clauses.append(Poll.q.datecloses <= when)
+            status_clauses.append(Poll.datecloses <= when)
         if PollStatus.NOT_YET_OPENED in status:
-            status_clauses.append(Poll.q.dateopens > when)
+            status_clauses.append(Poll.dateopens > when)
 
         assert len(status_clauses) > 0, "No poll statuses were selected"
 
-        results = Poll.select(AND(Poll.q.teamID == team.id,
-                                  OR(*status_clauses)))
+        results = IStore(Poll).find(
+            Poll, Poll.team == team, Or(*status_clauses))
 
-        return results.orderBy(orderBy)
+        return results.order_by(order_by)
 
     def getByTeamAndName(self, team, name, default=None):
         """See IPollSet."""
-        query = AND(Poll.q.teamID == team.id, Poll.q.name == name)
-        try:
-            return Poll.selectOne(query)
-        except SQLObjectNotFound:
-            return default
+        poll = IStore(Poll).find(Poll, team=team, name=name).one()
+        return poll if poll is not None else default
 
 
 @implementer(IPollOption)
-class PollOption(SQLBase):
+class PollOption(StormBase):
     """See IPollOption."""
-    _table = 'PollOption'
-    _defaultOrder = ['title', 'id']
+    __storm_table__ = 'PollOption'
+    __storm_order__ = ['title', 'id']
+
+    id = Int(primary=True)
+
+    poll_id = Int(name='poll', allow_none=False)
+    poll = Reference(poll_id, 'Poll.id')
 
-    poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
+    name = Unicode(allow_none=False)
 
-    name = StringCol(notNull=True)
+    title = Unicode(allow_none=False)
 
-    title = StringCol(notNull=True)
+    active = Bool(allow_none=False, default=False)
 
-    active = BoolCol(notNull=True, default=False)
+    def __init__(self, poll, name, title, active=False):
+        super(PollOption, self).__init__()
+        self.poll = poll
+        self.name = name
+        self.title = title
+        self.active = active
+
+    def destroySelf(self):
+        IStore(PollOption).remove(self)
 
 
 @implementer(IPollOptionSet)
@@ -339,36 +368,43 @@ class PollOptionSet:
 
     def new(self, poll, name, title, active=True):
         """See IPollOptionSet."""
-        return PollOption(poll=poll, name=name, title=title, active=active)
+        option = PollOption(poll=poll, name=name, title=title, active=active)
+        IStore(PollOption).add(option)
+        return option
 
-    def selectByPoll(self, poll, only_active=False):
+    def findByPoll(self, poll, only_active=False):
         """See IPollOptionSet."""
-        query = PollOption.q.pollID == poll.id
+        clauses = [PollOption.poll == poll]
         if only_active:
-            query = AND(query, PollOption.q.active == True)
-        return PollOption.select(query)
+            clauses.append(PollOption.active)
+        return IStore(PollOption).find(PollOption, *clauses)
 
     def getByPollAndId(self, poll, option_id, default=None):
         """See IPollOptionSet."""
-        query = AND(PollOption.q.pollID == poll.id,
-                    PollOption.q.id == option_id)
-        try:
-            return PollOption.selectOne(query)
-        except SQLObjectNotFound:
-            return default
+        option = IStore(PollOption).find(
+            PollOption, poll=poll, id=option_id).one()
+        return option if option is not None else default
 
 
 @implementer(IVoteCast)
-class VoteCast(SQLBase):
+class VoteCast(StormBase):
     """See IVoteCast."""
-    _table = 'VoteCast'
-    _defaultOrder = 'id'
+    __storm_table__ = 'VoteCast'
+    __storm_order__ = 'id'
+
+    id = Int(primary=True)
+
+    person_id = Int(
+        name='person', validator=validate_public_person, allow_none=False)
+    person = Reference(person_id, 'Person.id')
 
-    person = ForeignKey(
-        dbName='person', foreignKey='Person',
-        storm_validator=validate_public_person, notNull=True)
+    poll_id = Int(name='poll', allow_none=False)
+    poll = Reference(poll_id, 'Poll.id')
 
-    poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
+    def __init__(self, person, poll):
+        super(VoteCast, self).__init__()
+        self.person = person
+        self.poll = poll
 
 
 @implementer(IVoteCastSet)
@@ -377,26 +413,39 @@ class VoteCastSet:
 
     def new(self, poll, person):
         """See IVoteCastSet."""
-        return VoteCast(poll=poll, person=person)
+        vote_cast = VoteCast(poll=poll, person=person)
+        IStore(VoteCast).add(vote_cast)
+        return vote_cast
 
 
 @implementer(IVote)
-class Vote(SQLBase):
+class Vote(StormBase):
     """See IVote."""
-    _table = 'Vote'
-    _defaultOrder = ['preference', 'id']
+    __storm_table__ = 'Vote'
+    __storm_order__ = ['preference', 'id']
 
-    person = ForeignKey(
-        dbName='person', foreignKey='Person',
-        storm_validator=validate_public_person)
+    id = Int(primary=True)
 
-    poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
+    person_id = Int(name='person', validator=validate_public_person)
+    person = Reference(person_id, 'Person.id')
 
-    option = ForeignKey(dbName='option', foreignKey='PollOption')
+    poll_id = Int(name='poll', allow_none=False)
+    poll = Reference(poll_id, 'Poll.id')
 
-    preference = IntCol(dbName='preference')
+    option_id = Int(name='option')
+    option = Reference(option_id, 'PollOption.id')
 
-    token = StringCol(dbName='token', notNull=True, unique=True)
+    preference = Int(name='preference')
+
+    token = Unicode(name='token', allow_none=False)
+
+    def __init__(self, poll, token, person=None, option=None, preference=None):
+        super(Vote, self).__init__()
+        self.poll = poll
+        self.token = token
+        self.person = person
+        self.option = option
+        self.preference = preference
 
 
 @implementer(IVoteSet)
@@ -405,16 +454,19 @@ class VoteSet:
 
     def new(self, poll, option, preference, token, person):
         """See IVoteSet."""
-        return Vote(poll=poll, option=option, preference=preference,
-                    token=token, person=person)
+        vote = Vote(
+            poll=poll, option=option, preference=preference, token=token,
+            person=person)
+        IStore(Vote).add(vote)
+        return vote
 
     def getByToken(self, token):
         """See IVoteSet."""
-        return Vote.selectBy(token=token)
+        return IStore(Vote).find(Vote, token=token)
 
     def getVotesByOption(self, option):
         """See IVoteSet."""
         if option.poll.type != PollAlgorithm.SIMPLE:
             raise OptionIsNotFromSimplePoll(
                 '%r is not an option of a simple-style poll.' % option)
-        return Vote.selectBy(option=option).count()
+        return IStore(Vote).find(Vote, option=option).count()
diff --git a/lib/lp/registry/stories/team-polls/create-poll-options.txt b/lib/lp/registry/stories/team-polls/create-poll-options.txt
index 2a9711a..2af52e0 100644
--- a/lib/lp/registry/stories/team-polls/create-poll-options.txt
+++ b/lib/lp/registry/stories/team-polls/create-poll-options.txt
@@ -9,8 +9,8 @@ First we create a new poll to use throughout this test.
     >>> from zope.component import getUtility
     >>> from lp.registry.interfaces.person import IPersonSet
     >>> factory.makePoll(getUtility(IPersonSet).getByName('ubuntu-team'),
-    ...                  'dpl-2080', 'dpl-2080', 'dpl-2080')
-    <Poll...
+    ...                  u'dpl-2080', u'dpl-2080', u'dpl-2080')
+    <lp.registry.model.poll.Poll...
     >>> logout()
 
 Our poll is not yet open, so new options can be added to it.
diff --git a/lib/lp/registry/stories/team-polls/edit-poll.txt b/lib/lp/registry/stories/team-polls/edit-poll.txt
index 2590f56..7265ff3 100644
--- a/lib/lp/registry/stories/team-polls/edit-poll.txt
+++ b/lib/lp/registry/stories/team-polls/edit-poll.txt
@@ -9,8 +9,8 @@ First we create a new poll to use throughout this test.
     >>> from zope.component import getUtility
     >>> from lp.registry.interfaces.person import IPersonSet
     >>> factory.makePoll(getUtility(IPersonSet).getByName('ubuntu-team'),
-    ...                  'dpl-2080', 'dpl-2080', 'dpl-2080')
-    <Poll...
+    ...                  u'dpl-2080', u'dpl-2080', u'dpl-2080')
+    <lp.registry.model.poll.Poll...
     >>> logout()
 
 Now we'll try to change its name to something that is already in use.
diff --git a/lib/lp/registry/templates/poll-index.pt b/lib/lp/registry/templates/poll-index.pt
index de586e1..69a60f5 100644
--- a/lib/lp/registry/templates/poll-index.pt
+++ b/lib/lp/registry/templates/poll-index.pt
@@ -22,7 +22,7 @@
   />
   <br />
 
-  <p tal:condition="not: context/getActiveOptions">
+  <p tal:condition="python: context.getActiveOptions().is_empty()">
     This poll does not yet have any voting options. Please <a
     href="+newoption">add an option</a>. Note, you need more than one option
     for a real poll, of course :-)
diff --git a/lib/lp/registry/tests/test_poll.py b/lib/lp/registry/tests/test_poll.py
index ad5386c..e2297fc 100644
--- a/lib/lp/registry/tests/test_poll.py
+++ b/lib/lp/registry/tests/test_poll.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
+
 from datetime import (
     datetime,
     timedelta,