launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02175
[Merge] lp:~bac/launchpad/bug-689719 into lp:launchpad
Brad Crittenden has proposed merging lp:~bac/launchpad/bug-689719 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#689719 Remove team polls
https://bugs.launchpad.net/bugs/689719
= Summary =
Remove the polls feature from Launchpad.
== Proposed fix ==
Rip, rip, rip.
== Pre-implementation notes ==
Talks and querying with Curtis regarding existing data.
== Implementation details ==
N/A
== Tests ==
bin/test -vvm lp.registry
== Demo and Q/A ==
http://dev.launchpad.net/~ubuntu-team/+polls
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/registry/browser/configure.zcml
lib/lp/registry/adapters.py
lib/lp/registry/templates/team-index.pt
lib/lp/registry/browser/tests/test_breadcrumbs.py
lib/canonical/launchpad/security.py
lib/canonical/launchpad/pagetitles.py
lib/lp/registry/templates/team-polls.pt
lib/lp/registry/configure.zcml
lib/lp/testing/factory.py
lib/lp/registry/doc/person-merge.txt
lib/lp/registry/stories/team/xx-team-home.txt
lib/lp/registry/browser/person.py
lib/lp/registry/model/person.py
These are all bogus.
./lib/canonical/launchpad/security.py
742: E302 expected 2 blank lines, found 1
./lib/canonical/launchpad/pagetitles.py
58: E302 expected 2 blank lines, found 3
60: E301 expected 1 blank line, found 0
69: E301 expected 1 blank line, found 0
75: E301 expected 1 blank line, found 0
81: E301 expected 1 blank line, found 0
87: E301 expected 1 blank line, found 0
100: E301 expected 1 blank line, found 0
121: E302 expected 2 blank lines, found 1
147: E302 expected 2 blank lines, found 1
151: E302 expected 2 blank lines, found 1
158: E302 expected 2 blank lines, found 1
193: E302 expected 2 blank lines, found 1
202: E302 expected 2 blank lines, found 1
239: E302 expected 2 blank lines, found 1
256: E302 expected 2 blank lines, found 1
--
https://code.launchpad.net/~bac/launchpad/bug-689719/+merge/43840
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~bac/launchpad/bug-689719 into lp:launchpad.
=== modified file 'lib/canonical/launchpad/pagetitles.py'
--- lib/canonical/launchpad/pagetitles.py 2010-11-08 12:52:43 +0000
+++ lib/canonical/launchpad/pagetitles.py 2010-12-15 22:26:18 +0000
@@ -285,20 +285,6 @@
person_translations_to_review = ContextDisplayName(
'Translations for review by %s')
-poll_edit = ContextTitle(smartquote('Edit poll "%s"'))
-
-poll_index = ContextTitle(smartquote('Poll: "%s"'))
-
-poll_newoption = ContextTitle(smartquote('New option for poll "%s"'))
-
-def polloption_edit(context, view):
- """Return the page title to edit a poll's option."""
- return 'Edit option: %s' % context.title
-
-poll_vote_condorcet = ContextTitle(smartquote('Vote in poll "%s"'))
-
-poll_vote_simple = ContextTitle(smartquote('Vote in poll "%s"'))
-
product_cvereport = ContextTitle('CVE reports for %s')
product_index = ContextTitle('%s in Launchpad')
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-12-14 00:03:18 +0000
+++ lib/canonical/launchpad/security.py 2010-12-15 22:26:18 +0000
@@ -130,11 +130,6 @@
PersonVisibility,
)
from lp.registry.interfaces.pillar import IPillar
-from lp.registry.interfaces.poll import (
- IPoll,
- IPollOption,
- IPollSubset,
- )
from lp.registry.interfaces.product import (
IProduct,
IProductSet,
@@ -858,26 +853,6 @@
return False
-class EditPollByTeamOwnerOrTeamAdminsOrAdmins(
- EditTeamMembershipByTeamOwnerOrTeamAdminsOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = IPoll
-
-
-class EditPollSubsetByTeamOwnerOrTeamAdminsOrAdmins(
- EditPollByTeamOwnerOrTeamAdminsOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = IPollSubset
-
-
-class EditPollOptionByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
- permission = 'launchpad.Edit'
- usedfor = IPollOption
-
- def checkAuthenticated(self, user):
- return can_edit_team(self.obj.poll.team, user)
-
-
class AdminDistribution(AdminByAdminsTeam):
"""Soyuz involves huge chunks of data in the archive and librarian,
so for the moment we are locking down admin and edit on distributions
=== removed file 'lib/canonical/launchpad/tests/test_poll.py'
--- lib/canonical/launchpad/tests/test_poll.py 2010-10-04 19:50:45 +0000
+++ lib/canonical/launchpad/tests/test_poll.py 1970-01-01 00:00:00 +0000
@@ -1,34 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-from datetime import (
- datetime,
- timedelta,
- )
-import unittest
-
-import pytz
-
-from canonical.launchpad.ftests import login
-from canonical.testing.layers import LaunchpadFunctionalLayer
-from lp.testing import TestCaseWithFactory
-
-
-class TestPoll(TestCaseWithFactory):
- layer = LaunchpadFunctionalLayer
-
- def test_getWinners_handle_polls_with_only_spoilt_votes(self):
- login('mark@xxxxxxxxxxx')
- owner = self.factory.makePerson()
- team = self.factory.makeTeam(owner)
- poll = self.factory.makePoll(team, 'name', 'title', 'proposition')
- # Force opening of poll so that we can vote.
- poll.dateopens = datetime.now(pytz.UTC) - timedelta(minutes=2)
- poll.storeSimpleVote(owner, None)
- # Force closing of the poll so that we can call getWinners().
- poll.datecloses = datetime.now(pytz.UTC)
- self.failUnless(poll.getWinners() is None, poll.getWinners())
-
-
-def test_suite():
- return unittest.TestLoader().loadTestsFromName(__name__)
=== modified file 'lib/lp/registry/adapters.py'
--- lib/lp/registry/adapters.py 2010-09-21 13:39:17 +0000
+++ lib/lp/registry/adapters.py 2010-12-15 22:26:18 +0000
@@ -6,24 +6,15 @@
__metaclass__ = type
__all__ = [
- 'distroseries_to_launchpadusage',
- 'distroseries_to_serviceusage',
- 'PollSubset',
+ 'distroseries_to_distribution',
+ 'person_from_principal',
'productseries_to_product',
]
-from zope.component import getUtility
from zope.component.interfaces import ComponentLookupError
-from zope.interface import implements
from canonical.launchpad.webapp.interfaces import ILaunchpadPrincipal
-from lp.registry.interfaces.poll import (
- IPollSet,
- IPollSubset,
- PollAlgorithm,
- PollStatus,
- )
def distroseries_to_distribution(distroseries):
@@ -51,60 +42,6 @@
raise ComponentLookupError
-class PollSubset:
- """Adapt an `IPoll` to an `IPollSubset`."""
- implements(IPollSubset)
-
- title = 'Team polls'
-
- def __init__(self, team=None):
- self.team = team
-
- def new(self, name, title, proposition, dateopens, datecloses,
- secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
- """See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
- return getUtility(IPollSet).new(
- self.team, name, title, proposition, dateopens,
- datecloses, secrecy, allowspoilt, poll_type)
-
- def getByName(self, name, default=None):
- """See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
- pollset = getUtility(IPollSet)
- return pollset.getByTeamAndName(self.team, name, default)
-
- def getAll(self):
- """See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
- return getUtility(IPollSet).selectByTeam(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)
-
- 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)
-
- 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(
- self.team, [PollStatus.NOT_YET_OPENED],
- orderBy='dateopens', when=when)
-
-
def productseries_to_product(productseries):
"""Adapts `IProductSeries` object to `IProduct`.
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2010-12-10 23:36:06 +0000
+++ lib/lp/registry/browser/configure.zcml 2010-12-15 22:26:18 +0000
@@ -600,75 +600,6 @@
name="karmacontext-macros"
template="../templates/karmacontext-macros.pt"/>
</facet>
- <facet
- facet="overview">
- <browser:menus
- module="lp.registry.browser.poll"
- classes="
- PollOverviewMenu
- PollActionNavigationMenu
- PollEditNavigationMenu"/>
- <browser:defaultView
- for="lp.registry.interfaces.poll.IPoll"
- name="+index"/>
- <browser:navigation
- module="lp.registry.browser.poll"
- classes="
- PollNavigation"/>
- <browser:url
- for="lp.registry.interfaces.poll.IPoll"
- path_expression="string:+poll/${name}"
- attribute_to_parent="team"/>
- <browser:pages
- for="lp.registry.interfaces.poll.IPoll"
- permission="zope.Public"
- class="lp.registry.browser.poll.PollView">
- <browser:page
- name="+index"
- template="../templates/poll-index.pt"/>
- </browser:pages>
- <browser:pages
- for="lp.registry.interfaces.poll.IPoll"
- permission="zope.Public"
- class="lp.registry.browser.poll.BasePollView">
- <browser:page
- name="+portlet-details"
- template="../templates/poll-portlet-details.pt"/>
- <browser:page
- name="+portlet-options"
- template="../templates/poll-portlet-options.pt"/>
- </browser:pages>
- <browser:page
- name="+vote"
- for="lp.registry.interfaces.poll.IPoll"
- permission="launchpad.AnyPerson"
- class="lp.registry.browser.poll.PollVoteView"/>
- <browser:page
- name="+edit"
- for="lp.registry.interfaces.poll.IPoll"
- class="lp.registry.browser.poll.PollEditView"
- permission="launchpad.Edit"
- template="../templates/poll-edit.pt"/>
- <browser:page
- name="+newoption"
- for="lp.registry.interfaces.poll.IPoll"
- class="lp.registry.browser.poll.PollOptionAddView"
- permission="launchpad.Edit"
- template="../templates/poll-newoption.pt"/>
- <browser:defaultView
- for="lp.registry.interfaces.poll.IPollOption"
- name="+edit"/>
- <browser:url
- for="lp.registry.interfaces.poll.IPollOption"
- path_expression="string:+option/${id}"
- attribute_to_parent="poll"/>
- <browser:page
- name="+edit"
- for="lp.registry.interfaces.poll.IPollOption"
- class="lp.registry.browser.poll.PollOptionEditView"
- permission="launchpad.Edit"
- template="../templates/polloption-edit.pt"/>
- </facet>
<browser:url
for="lp.registry.interfaces.announcement.IAnnouncement"
path_expression="string:+announcement/${id}"
@@ -1080,12 +1011,6 @@
template="../templates/team-index.pt"/>
<browser:page
for="lp.registry.interfaces.person.ITeam"
- class="lp.registry.browser.person.TeamIndexView"
- permission="zope.Public"
- name="+portlet-polls"
- template="../templates/team-portlet-polls.pt"/>
- <browser:page
- for="lp.registry.interfaces.person.ITeam"
permission="zope.Public"
class="lp.registry.browser.team.TeamMapView"
name="+map"
@@ -1198,12 +1123,6 @@
name="+polls"
template="../templates/team-polls.pt"/>
<browser:page
- name="+newpoll"
- for="lp.registry.interfaces.person.ITeam"
- class="canonical.launchpad.browser.PollAddView"
- permission="launchpad.Edit"
- template="../templates/team-newpoll.pt"/>
- <browser:page
name="+members"
for="lp.registry.interfaces.person.ITeam"
permission="zope.Public"
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2010-12-10 23:10:09 +0000
+++ lib/lp/registry/browser/person.py 2010-12-15 22:26:18 +0000
@@ -284,10 +284,6 @@
)
from lp.registry.interfaces.personproduct import IPersonProductFactory
from lp.registry.interfaces.pillar import IPillarNameSet
-from lp.registry.interfaces.poll import (
- IPollSet,
- IPollSubset,
- )
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.ssh import (
ISSHKeySet,
@@ -562,10 +558,6 @@
usedfor = ITeam
- @stepthrough('+poll')
- def traverse_poll(self, name):
- return getUtility(IPollSet).getByTeamAndName(self.context, name)
-
@stepthrough('+invitation')
def traverse_invitation(self, name):
# Return the found membership regardless of its status as we know
@@ -1334,17 +1326,6 @@
text = 'Show member photos'
return Link(target, text, icon='team')
- def polls(self):
- target = '+polls'
- text = 'Show polls'
- return Link(target, text, icon='info')
-
- @enabled_with_permission('launchpad.Edit')
- def add_poll(self):
- target = '+newpoll'
- text = 'Create a poll'
- return Link(target, text, icon='add')
-
@enabled_with_permission('launchpad.Edit')
def editemail(self):
target = '+contactaddress'
@@ -1429,8 +1410,6 @@
'moderate_mailing_list',
'editlanguages',
'map',
- 'polls',
- 'add_poll',
'join',
'leave',
'add_my_teams',
@@ -1451,7 +1430,7 @@
usedfor = ITeam
facet = 'overview'
- links = ['profile', 'polls', 'members', 'ppas']
+ links = ['profile', 'members', 'ppas']
class TeamMembershipView(LaunchpadView):
@@ -2941,21 +2920,6 @@
return ''
@cachedproperty
- def openpolls(self):
- assert self.context.isTeam()
- return IPollSubset(self.context).getOpenPolls()
-
- @cachedproperty
- def closedpolls(self):
- assert self.context.isTeam()
- return IPollSubset(self.context).getClosedPolls()
-
- @cachedproperty
- def notyetopenedpolls(self):
- assert self.context.isTeam()
- return IPollSubset(self.context).getNotYetOpenedPolls()
-
- @cachedproperty
def contributions(self):
"""Cache the results of getProjectsAndCategoriesContributedTo()."""
return self.context.getProjectsAndCategoriesContributedTo(
@@ -3124,19 +3088,6 @@
else:
raise AssertionError('Unknown group to contact.')
- @property
- def should_show_polls_portlet(self):
- menu = TeamOverviewMenu(self.context)
- return (
- self.has_current_polls or self.closedpolls
- or menu.add_poll().enabled)
-
- @property
- def has_current_polls(self):
- """Return True if this team has any non-closed polls."""
- assert self.context.isTeam()
- return bool(self.openpolls) or bool(self.notyetopenedpolls)
-
def userIsOwner(self):
"""Return True if the user is the owner of this Team."""
if self.user is None:
=== removed file 'lib/lp/registry/browser/poll.py'
--- lib/lp/registry/browser/poll.py 2010-11-23 23:22:27 +0000
+++ lib/lp/registry/browser/poll.py 1970-01-01 00:00:00 +0000
@@ -1,469 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-__metaclass__ = type
-
-__all__ = [
- 'BasePollView',
- 'PollAddView',
- 'PollEditNavigationMenu',
- 'PollEditView',
- 'PollNavigation',
- 'PollOptionAddView',
- 'PollOptionEditView',
- 'PollOverviewMenu',
- 'PollView',
- 'PollVoteView',
- 'PollBreadcrumb',
- ]
-
-from z3c.ptcompat import ViewPageTemplateFile
-from zope.app.form.browser import TextWidget
-from zope.component import getUtility
-from zope.event import notify
-from zope.interface import (
- implements,
- Interface,
- )
-from zope.lifecycleevent import ObjectCreatedEvent
-
-from canonical.launchpad.helpers import shortlist
-from canonical.launchpad.webapp import (
- ApplicationMenu,
- canonical_url,
- enabled_with_permission,
- LaunchpadView,
- Link,
- Navigation,
- NavigationMenu,
- stepthrough,
- )
-from canonical.launchpad.webapp.breadcrumb import TitleBreadcrumb
-from lp.app.browser.launchpadform import (
- action,
- custom_widget,
- LaunchpadEditFormView,
- LaunchpadFormView,
- )
-from lp.registry.interfaces.poll import (
- IPoll,
- IPollOption,
- IPollOptionSet,
- IPollSubset,
- IVoteSet,
- PollAlgorithm,
- PollSecrecy,
- )
-
-
-class PollEditLinksMixin:
-
- @enabled_with_permission('launchpad.Edit')
- def addnew(self):
- text = 'Add new option'
- return Link('+newoption', text, icon='add')
-
- @enabled_with_permission('launchpad.Edit')
- def edit(self):
- text = 'Change details'
- return Link('+edit', text, icon='edit')
-
-
-class PollOverviewMenu(ApplicationMenu, PollEditLinksMixin):
- usedfor = IPoll
- facet = 'overview'
- links = ['addnew']
-
-
-class IPollEditMenu(Interface):
- """A marker interface for the edit navigation menu."""
-
-
-class PollEditNavigationMenu(NavigationMenu, PollEditLinksMixin):
- usedfor = IPollEditMenu
- facet = 'overview'
- links = ['addnew', 'edit']
-
-
-class IPollActionMenu(Interface):
- """A marker interface for the action menu."""
-
-
-class PollActionNavigationMenu(PollEditNavigationMenu):
- usedfor = IPollActionMenu
- links = ['edit']
-
-
-class PollNavigation(Navigation):
-
- usedfor = IPoll
-
- @stepthrough('+option')
- def traverse_option(self, name):
- return getUtility(IPollOptionSet).getByPollAndId(
- self.context, int(name))
-
-
-class BasePollView(LaunchpadView):
- """A base view class to be used in other poll views."""
-
- token = None
- gotTokenAndVotes = False
- feedback = ""
-
- def setUpTokenAndVotes(self):
- """Set up the token and votes to be displayed."""
- if not self.userVoted():
- return
-
- # For secret polls we can only display the votes after the token
- # is submitted.
- if self.request.method == 'POST' and self.isSecret():
- self.setUpTokenAndVotesForSecretPolls()
- elif not self.isSecret():
- self.setUpTokenAndVotesForNonSecretPolls()
-
- def setUpTokenAndVotesForNonSecretPolls(self):
- """Get the votes of the logged in user in this poll.
-
- Set the votes in instance variables and also set self.gotTokenAndVotes
- to True, so the templates know they can display the vote.
-
- This method should be used only on non-secret polls and if the logged
- in user has voted on this poll.
- """
- assert not self.isSecret() and self.userVoted()
- votes = self.context.getVotesByPerson(self.user)
- assert votes, (
- "User %r hasn't voted on poll %r" % (self.user, self.context))
- if self.isSimple():
- # Here we have only one vote.
- self.currentVote = votes[0]
- self.token = self.currentVote.token
- elif self.isCondorcet():
- # Here we have multiple votes, and the token is the same in
- # all of them.
- self.currentVotes = sorted(votes, key=lambda v: v.preference)
- self.token = self.currentVotes[0].token
- self.gotTokenAndVotes = True
-
- def setUpTokenAndVotesForSecretPolls(self):
- """Get the votes with the token provided in the form.
-
- Set the votes, together with the token in instance variables. Also
- set self.gotTokenAndVotes to True, so the templates know they can
- display the vote.
-
- Return True if there's any vote with the given token and the votes
- are on this poll.
-
- This method should be used only on secret polls and if the logged
- in user has voted on this poll.
- """
- assert self.isSecret() and self.userVoted()
- token = self.request.form.get('token')
- # Only overwrite self.token if the request contains a 'token'
- # variable.
- if token is not None:
- self.token = token
- votes = getUtility(IVoteSet).getByToken(self.token)
- if not votes:
- self.feedback = ("There's no vote associated with the token %s"
- % self.token)
- return False
-
- # All votes with a given token must be on the same poll. That means
- # checking the poll of the first vote is enough.
- if votes[0].poll != self.context:
- self.feedback = ("The vote associated with the token %s is not "
- "a vote on this poll." % self.token)
- return False
-
- if self.isSimple():
- # A simple poll has only one vote, because you can choose only one
- # option.
- self.currentVote = votes[0]
- elif self.isCondorcet():
- self.currentVotes = sorted(votes, key=lambda v: v.preference)
- self.gotTokenAndVotes = True
- return True
-
- def userCanVote(self):
- """Return True if the user is/was eligible to vote on this poll."""
- return (self.user and self.user.inTeam(self.context.team))
-
- def userVoted(self):
- """Return True if the user voted on this poll."""
- return (self.user and self.context.personVoted(self.user))
-
- def isCondorcet(self):
- """Return True if this poll's type is Condorcet."""
- return self.context.type == PollAlgorithm.CONDORCET
-
- def isSimple(self):
- """Return True if this poll's type is Simple."""
- return self.context.type == PollAlgorithm.SIMPLE
-
- def isSecret(self):
- """Return True if this is a secret poll."""
- return self.context.secrecy == PollSecrecy.SECRET
-
-
-class PollBreadcrumb(TitleBreadcrumb):
- """Breadcrumb for polls."""
-
-
-class PollView(BasePollView):
- """A view class to display the results of a poll."""
- implements(IPollActionMenu)
-
- def initialize(self):
- super(PollView, self).initialize()
- request = self.request
- if (self.userCanVote() and self.context.isOpen() and
- self.context.getActiveOptions()):
- vote_url = canonical_url(self.context, view_name='+vote')
- request.response.redirect(vote_url)
-
- def getVotesByOption(self, option):
- """Return the number of votes the given option received."""
- return getUtility(IVoteSet).getVotesByOption(option)
-
- def getPairwiseMatrixWithHeaders(self):
- """Return the pairwise matrix, with headers being the option's
- names.
- """
- # XXX: kiko 2006-03-13:
- # The list() call here is necessary because, lo and behold,
- # it gives us a non-security-proxied list object! Someone come
- # in and fix this!
- pairwise_matrix = list(self.context.getPairwiseMatrix())
- headers = [None]
- for idx, option in enumerate(self.context.getAllOptions()):
- headers.append(option.title)
- # Get a mutable row.
- row = list(pairwise_matrix[idx])
- row.insert(0, option.title)
- pairwise_matrix[idx] = row
- pairwise_matrix.insert(0, headers)
- return pairwise_matrix
-
-
-class PollVoteView(BasePollView):
- """A view class to where the user can vote on a poll.
-
- If the user already voted, the current vote is displayed and the user can
- change it. Otherwise he can register his vote.
- """
-
- default_template = ViewPageTemplateFile(
- '../templates/poll-vote-simple.pt')
- condorcet_template = ViewPageTemplateFile(
- '../templates/poll-vote-condorcet.pt')
-
- @property
- def template(self):
- if self.isCondorcet():
- return self.condorcet_template
- else:
- return self.default_template
-
- def initialize(self):
- """Process the form, if it was submitted."""
- super(PollVoteView, self).initialize()
- if not self.isSecret() and self.userVoted():
- # For non-secret polls, the user's vote is always displayed
- self.setUpTokenAndVotesForNonSecretPolls()
-
- if self.request.method != 'POST':
- return
-
- if self.isSecret() and self.userVoted():
- if not self.setUpTokenAndVotesForSecretPolls():
- # Not possible to get the votes. Probably the token was wrong.
- return
-
- if 'showvote' in self.request.form:
- # The user only wants to see the vote.
- return
-
- if not self.context.isOpen():
- self.feedback = "This poll is not open."
- return
-
- if self.isSimple():
- self.processSimpleVotingForm()
- else:
- self.processCondorcetVotingForm()
-
- # User may have voted, so we need to setup the vote to display again.
- self.setUpTokenAndVotes()
-
- def processSimpleVotingForm(self):
- """Process the simple-voting form to change a user's vote or register
- a new one.
-
- This method must not be called if the poll is not open.
- """
- assert self.context.isOpen()
- context = self.context
- newoption_id = self.request.form.get('newoption')
- if newoption_id == 'donotchange':
- self.feedback = "Your vote was not changed."
- return
- elif newoption_id == 'donotvote':
- self.feedback = "You chose not to vote yet."
- return
- elif newoption_id == 'none':
- newoption = None
- else:
- newoption = getUtility(IPollOptionSet).getByPollAndId(
- context, int(newoption_id))
-
- if self.userVoted():
- self.currentVote.option = newoption
- self.feedback = "Your vote was changed successfully."
- else:
- self.currentVote = context.storeSimpleVote(self.user, newoption)
- self.token = self.currentVote.token
- self.currentVote = self.currentVote
- if self.isSecret():
- self.feedback = (
- "Your vote has been recorded. If you want to view or "
- "change it later you must write down this key: %s"
- % self.token)
- else:
- self.feedback = (
- "Your vote was stored successfully. You can come back to "
- "this page at any time before this poll closes to view "
- "or change your vote, if you want.")
-
- def processCondorcetVotingForm(self):
- """Process the condorcet-voting form to change a user's vote or
- register a new one.
-
- This method must not be called if the poll is not open.
- """
- assert self.context.isOpen()
- form = self.request.form
- activeoptions = shortlist(self.context.getActiveOptions())
- newvotes = {}
- for option in activeoptions:
- try:
- preference = int(form.get('option_%d' % option.id))
- except ValueError:
- # XXX: Guilherme Salgado 2005-09-14:
- # User tried to specify a value which we can't convert to
- # an integer. Better thing to do would be to notify the user
- # and ask him to fix it.
- preference = None
- newvotes[option] = preference
-
- if self.userVoted():
- # This is a vote change.
- # For now it's not possible to have votes in an inactive option,
- # but it'll be in the future as we'll allow people to make options
- # inactive after a poll opens.
- assert len(activeoptions) == len(self.currentVotes)
- for vote in self.currentVotes:
- vote.preference = newvotes.get(vote.option)
- self.currentVotes.sort(key=lambda v: v.preference)
- self.feedback = "Your vote was changed successfully."
- else:
- # This is a new vote.
- votes = self.context.storeCondorcetVote(self.user, newvotes)
- self.token = votes[0].token
- self.currentVotes = sorted(votes, key=lambda v: v.preference)
- if self.isSecret():
- self.feedback = (
- "Your vote has been recorded. If you want to view or "
- "change it later you must write down this key: %s"
- % self.token)
- else:
- self.feedback = (
- "Your vote was stored successfully. You can come back to "
- "this page at any time before this poll closes to view "
- "or change your vote, if you want.")
-
-
-class PollAddView(LaunchpadFormView):
- """The view class to create a new poll in a given team."""
-
- schema = IPoll
- field_names = ["name", "title", "proposition", "allowspoilt", "dateopens",
- "datecloses"]
-
- @property
- def cancel_url(self):
- """See `LaunchpadFormView`."""
- return canonical_url(self.context)
-
- @action("Continue", name="continue")
- def continue_action(self, action, data):
- # XXX: salgado, 2008-10-08: Only secret polls can be created until we
- # fix https://launchpad.net/bugs/80596.
- secrecy = PollSecrecy.SECRET
- poll = IPollSubset(self.context).new(
- data['name'], data['title'], data['proposition'],
- data['dateopens'], data['datecloses'], secrecy,
- data['allowspoilt'])
- self.next_url = canonical_url(poll)
- notify(ObjectCreatedEvent(poll))
-
-
-class PollEditView(LaunchpadEditFormView):
-
- implements(IPollEditMenu)
- schema = IPoll
- label = "Edit poll details"
- field_names = ["name", "title", "proposition", "allowspoilt", "dateopens",
- "datecloses"]
-
- @property
- def cancel_url(self):
- """See `LaunchpadFormView`."""
- return canonical_url(self.context)
-
- @action("Save", name="save")
- def save_action(self, action, data):
- self.updateContextFromData(data)
- self.next_url = canonical_url(self.context)
-
-
-class PollOptionEditView(LaunchpadEditFormView):
- """Edit one of a poll's options."""
-
- schema = IPollOption
- label = "Edit option details"
- field_names = ["name", "title"]
- custom_widget("title", TextWidget, width=30)
-
- @property
- def cancel_url(self):
- """See `LaunchpadFormView`."""
- return canonical_url(self.context.poll)
-
- @action("Save", name="save")
- def save_action(self, action, data):
- self.updateContextFromData(data)
- self.next_url = canonical_url(self.context.poll)
-
-
-class PollOptionAddView(LaunchpadFormView):
- """Create a new option in a given poll."""
-
- schema = IPollOption
- label = "Create new poll option"
- field_names = ["name", "title"]
- custom_widget("title", TextWidget, width=30)
-
- @property
- def cancel_url(self):
- """See `LaunchpadFormView`."""
- return canonical_url(self.context)
-
- @action("Create", name="create")
- def create_action(self, action, data):
- polloption = self.context.newOption(data['name'], data['title'])
- self.next_url = canonical_url(self.context)
- notify(ObjectCreatedEvent(polloption))
=== removed file 'lib/lp/registry/browser/tests/poll-views.txt'
--- lib/lp/registry/browser/tests/poll-views.txt 2009-12-15 20:33:49 +0000
+++ lib/lp/registry/browser/tests/poll-views.txt 1970-01-01 00:00:00 +0000
@@ -1,134 +0,0 @@
-Poll views
-----------
-
-The polls portlet shows the state of current polls, and links to past
-polls.
-
- >>> from canonical.launchpad.testing.pages import extract_text
-
- >>> user = factory.makePerson()
- >>> team = factory.makeTeam(name='team')
- >>> owner = team.teamowner
-
- >>> def create_team_view(team, name=None, principal=None):
- ... # XRDS inheritance requires a lot of setup.
- ... path_info = '/~%s' % team.name
- ... server_url = 'http://launchpad.dev'
- ... view = create_view(
- ... team, name=name, principal=principal,
- ... server_url=server_url, path_info=path_info)
- ... view.initialize()
- ... return view
-
-The portlet does not render any markup when there are no polls...
-
- >>> login_person(user)
- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
- >>> view.has_current_polls
- False
-
- >>> view.should_show_polls_portlet
- False
-
- >>> print extract_text(view.render())
- <BLANKLINE>
-
-Unless the user is a team owner.
-
- >>> login_person(owner)
- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
- >>> view.has_current_polls
- False
-
- >>> view.should_show_polls_portlet
- True
-
- >>> print extract_text(view.render())
- Polls
- No current polls.
- Show polls
- Create a poll
-
-The portlet shows a link to polls to all users when there is a poll, but it
-has not opened.
-
- >>> import pytz
- >>> from datetime import datetime, timedelta
- >>> from lp.registry.interfaces.poll import IPollSubset, PollSecrecy
-
- >>> open_date = datetime.now().replace(tzinfo=pytz.timezone('UTC'))
- >>> close_date = open_date + timedelta(weeks=1)
- >>> poll_subset = IPollSubset(team)
- >>> poll = poll_subset.new(
- ... 'name', 'title', 'proposition', open_date, close_date,
- ... PollSecrecy.OPEN, False)
-
- >>> login_person(user)
- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
- >>> view.has_current_polls
- True
-
- >>> view.should_show_polls_portlet
- True
-
- >>> print extract_text(view.render())
- Polls
- Show polls
-
-The portlet shows more details to the poll owner.
-
- >>> login_person(owner)
- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
- >>> view.has_current_polls
- True
-
- >>> view.should_show_polls_portlet
- True
-
- >>> print extract_text(view.render())
- Polls
- title - opens in 5 hours
- Show polls
- Create a poll
-
-Current polls are listed in the portlet, the only difference between a user
-and an owner is the owner has a link to create more polls.
-
- >>> poll.dateopens = open_date - timedelta(weeks=2)
-
- >>> login_person(user)
- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
- >>> print extract_text(view.render())
- Polls
- title - closes on ...
- You have seven days left to vote in this poll.
- Show polls
-
- >>> login_person(owner)
- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
- >>> print extract_text(view.render())
- Polls
- title - closes on ...
- You have seven days left to vote in this poll.
- Show polls
- Create a poll
-
-When all the polls are closed, the portlet states the case and has a link to
-see the polls.
-
- >>> poll.datecloses = close_date - timedelta(weeks=2)
-
- >>> login_person(user)
- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
- >>> print extract_text(view.render())
- Polls
- No current polls.
- Show polls
-
- >>> login_person(owner)
- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
- >>> print extract_text(view.render())
- Polls
- No current polls.
- Show polls
- Create a poll
=== removed file 'lib/lp/registry/browser/tests/poll-views_0.txt'
--- lib/lp/registry/browser/tests/poll-views_0.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/registry/browser/tests/poll-views_0.txt 1970-01-01 00:00:00 +0000
@@ -1,92 +0,0 @@
-= Poll Pages =
-
-First import some stuff and setup some things we'll use in this test.
-
- >>> from zope.component import getUtility, getMultiAdapter
- >>> from zope.publisher.browser import TestRequest
- >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
- >>> from lp.registry.interfaces.person import IPersonSet
- >>> from lp.registry.interfaces.poll import IPollSet
- >>> from datetime import datetime, timedelta
- >>> login("test@xxxxxxxxxxxxx")
- >>> ubuntu_team = getUtility(IPersonSet).getByName('ubuntu-team')
-
-
-== Creating new polls ==
-
-When creating a new poll, its start date must be at least 12h after it is
-created.
-
-First we attempt to create a poll which starts 11h from now. That will fail
-with a proper explanation of why it failed.
-
- >>> eleven_hours_from_now = datetime.now() + timedelta(hours=11)
- >>> eleven_hours_from_now = eleven_hours_from_now.strftime(
- ... '%Y-%m-%d %H:%M:%S')
- >>> form = {
- ... 'field.name': 'test-poll',
- ... 'field.title': 'test-poll',
- ... 'field.proposition': 'test-poll',
- ... 'field.allowspoilt': '1',
- ... 'field.secrecy': 'SECRET',
- ... 'field.dateopens': eleven_hours_from_now,
- ... 'field.datecloses': '2025-06-04',
- ... 'field.actions.continue': 'Continue'}
- >>> request = LaunchpadTestRequest(method='POST', form=form)
- >>> new_poll = getMultiAdapter((ubuntu_team, request), name="+newpoll")
- >>> new_poll.initialize()
- >>> print "\n".join(new_poll.errors)
- A poll cannot open less than 12 hours after it's created.
-
-Now we successfully create a poll which starts 12h from now.
-
- >>> twelve_hours_from_now = datetime.now() + timedelta(hours=12)
- >>> twelve_hours_from_now = twelve_hours_from_now.strftime(
- ... '%Y-%m-%d %H:%M:%S')
- >>> form['field.dateopens'] = twelve_hours_from_now
- >>> request = LaunchpadTestRequest(method='POST', form=form)
- >>> new_poll = getMultiAdapter((ubuntu_team, request), name="+newpoll")
- >>> new_poll.initialize()
- >>> new_poll.errors
- []
-
-
-== Displaying results of condorcet polls ==
-
- >>> poll = getUtility(IPollSet).getByTeamAndName(ubuntu_team, 'director-2004')
- >>> poll.type.title
- 'Condorcet Voting'
-
-Although condorcet polls are disabled now, everything is implemented and we're
-using a pairwise matrix to display the results. It's very trick to create this
-matrix on page templates, so the view provides a method wich return this
-matrix as a python list, with the necessary headers (the option's names).
-
- >>> poll_results = getMultiAdapter((poll, TestRequest()), name="+index")
- >>> for row in poll_results.getPairwiseMatrixWithHeaders():
- ... print row
- [None, u'A', u'B', u'C', u'D']
- [u'A', None, 2L, 2L, 2L]
- [u'B', 2L, None, 2L, 2L]
- [u'C', 1L, 1L, None, 1L]
- [u'D', 2L, 1L, 2L, None]
-
-== Voting on closed polls ==
-
-This is not allowed, and apart from not linking to the +vote page and not
-even displaying its content for a closed poll, we also have some lower
-level checks.
-
- >>> request = TestRequest(form={'changevote': 'Change Vote'})
- >>> request.method = 'POST'
- >>> voting_page = getMultiAdapter((poll, request), name="+vote")
- >>> form_processed = False
- >>> def form_processing():
- ... form_processed = True
- >>> voting_page.processCondorcetVotingForm = form_processing
- >>> voting_page.initialize()
-
- >>> form_processed
- False
- >>> voting_page.feedback
- 'This poll is not open.'
=== modified file 'lib/lp/registry/browser/tests/test_breadcrumbs.py'
--- lib/lp/registry/browser/tests/test_breadcrumbs.py 2010-11-30 13:41:48 +0000
+++ lib/lp/registry/browser/tests/test_breadcrumbs.py 2010-12-15 22:26:18 +0000
@@ -105,26 +105,6 @@
self.assertEqual(self.milestone.name, last_crumb.text)
-class TestPollBreadcrumb(BaseBreadcrumbTestCase):
- """Test breadcrumbs for an `IPoll`."""
-
- def setUp(self):
- super(TestPollBreadcrumb, self).setUp()
- self.team = self.factory.makeTeam(displayname="Poll Team")
- name = "pollo-poll"
- title = "Marco Pollo"
- proposition = "Be mine"
- self.poll = self.factory.makePoll(
- team=self.team,
- name=name,
- title=title,
- proposition=proposition)
-
- def test_poll(self):
- crumbs = self.getBreadcrumbsForObject(self.poll)
- last_crumb = crumbs[-1]
- self.assertEqual(self.poll.title, last_crumb.text)
-
from lp.registry.interfaces.nameblacklist import INameBlacklistSet
=== removed file 'lib/lp/registry/browser/tests/test_poll.py'
--- lib/lp/registry/browser/tests/test_poll.py 2010-10-11 17:44:05 +0000
+++ lib/lp/registry/browser/tests/test_poll.py 1970-01-01 00:00:00 +0000
@@ -1,38 +0,0 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for IPoll views."""
-
-__metaclass__ = type
-
-import os
-from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.registry.interfaces.poll import PollAlgorithm
-from lp.testing import TestCaseWithFactory
-from lp.testing.views import create_view
-
-
-class TestPollVoteView(TestCaseWithFactory):
-
- layer = DatabaseFunctionalLayer
-
- def setUp(self):
- super(TestPollVoteView, self).setUp()
- self.team = self.factory.makeTeam()
-
- def test_simple_poll_template(self):
- poll = self.factory.makePoll(
- self.team, 'name', 'title', 'proposition',
- poll_type=PollAlgorithm.SIMPLE)
- view = create_view(poll, name='+vote')
- self.assertEqual(
- 'poll-vote-simple.pt', os.path.basename(view.template.filename))
-
- def test_condorcet_poll_template(self):
- poll = self.factory.makePoll(
- self.team, 'name', 'title', 'proposition',
- poll_type=PollAlgorithm.CONDORCET)
- view = create_view(poll, name='+vote')
- self.assertEqual(
- 'poll-vote-condorcet.pt',
- os.path.basename(view.template.filename))
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2010-12-06 17:33:19 +0000
+++ lib/lp/registry/configure.zcml 2010-12-15 22:26:18 +0000
@@ -689,109 +689,6 @@
interface="lp.registry.interfaces.karma.IKarmaActionSet"/>
</securedutility>
</facet>
- <facet
- facet="overview">
-
- <!-- Poll -->
-
- <class
- class="lp.registry.model.poll.Poll">
- <allow
- interface="lp.registry.interfaces.poll.IPoll"/>
- <require
- permission="launchpad.Edit"
- set_schema="lp.registry.interfaces.poll.IPoll"/>
- </class>
-
- <adapter
- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
- for="lp.registry.interfaces.poll.IPoll"
- factory="lp.registry.browser.poll.PollBreadcrumb"
- permission="zope.Public"/>
-
- <!-- PollOption -->
-
- <class
- class="lp.registry.model.poll.PollOption">
- <allow
- interface="lp.registry.interfaces.poll.IPollOption"/>
- <require
- permission="launchpad.Edit"
- set_schema="lp.registry.interfaces.poll.IPollOption"/>
- </class>
-
- <!-- Vote -->
-
-
- <!-- Can't require launchpad.Edit to set_attributes because in most cases
- the vote won't be associated with a person, and thus we can't check it
- against the logged in user. -->
-
- <class
- class="lp.registry.model.poll.Vote">
- <allow
- interface="lp.registry.interfaces.poll.IVote"/>
- <require
- permission="zope.Public"
- set_attributes="option preference"/>
- </class>
-
- <!-- VoteCast -->
-
- <class
- class="lp.registry.model.poll.VoteCast">
- <allow
- interface="lp.registry.interfaces.poll.IVoteCast"/>
- </class>
-
- <!-- PollSet -->
-
- <class
- class="lp.registry.model.poll.PollSet">
- <allow
- interface="lp.registry.interfaces.poll.IPollSet"/>
- </class>
- <securedutility
- class="lp.registry.model.poll.PollSet"
- provides="lp.registry.interfaces.poll.IPollSet">
- <allow
- interface="lp.registry.interfaces.poll.IPollSet"/>
- </securedutility>
-
- <!-- PollSubset -->
-
- <adapter
- for="lp.registry.interfaces.person.ITeam"
- provides="lp.registry.interfaces.poll.IPollSubset"
- factory="lp.registry.adapters.PollSubset"
- permission="zope.Public"/>
-
- <!-- PollOptionSet -->
-
- <class
- class="lp.registry.model.poll.PollOptionSet">
- <allow
- interface="lp.registry.interfaces.poll.IPollOptionSet"/>
- </class>
- <securedutility
- class="lp.registry.model.poll.PollOptionSet"
- provides="lp.registry.interfaces.poll.IPollOptionSet">
- <allow
- interface="lp.registry.interfaces.poll.IPollOptionSet"/>
- </securedutility>
- <securedutility
- class="lp.registry.model.poll.VoteSet"
- provides="lp.registry.interfaces.poll.IVoteSet">
- <allow
- interface="lp.registry.interfaces.poll.IVoteSet"/>
- </securedutility>
- <securedutility
- class="lp.registry.model.poll.VoteCastSet"
- provides="lp.registry.interfaces.poll.IVoteCastSet">
- <allow
- interface="lp.registry.interfaces.poll.IVoteCastSet"/>
- </securedutility>
- </facet>
<!-- Announcement -->
=== modified file 'lib/lp/registry/doc/person-merge.txt'
--- lib/lp/registry/doc/person-merge.txt 2010-12-01 23:39:05 +0000
+++ lib/lp/registry/doc/person-merge.txt 2010-12-15 22:26:18 +0000
@@ -1,4 +1,5 @@
-= Merging =
+Merging
+=======
For many reasons (i.e. a gina run) we could have duplicated accounts in
Launchpad. Once a duplicated account is identified, we need to allow the user
@@ -19,7 +20,8 @@
>>> marilize = personset.getByName('marilize')
-== Sanity checks ==
+Sanity checks
+-------------
We can't merge an account that still has email addresses attached to it
@@ -29,7 +31,8 @@
AssertionError: ...
-== Preparing test person for the merge ==
+Preparing test person for the merge
+-----------------------------------
Merging people involves updating the merged person relationships. Let's
put the person we will merge into some of those.
@@ -57,10 +60,11 @@
marilize
>>> sampleperson_old_karma = sample.karma
-Branches whose owner is being merged are uniquified by appending '-N' where N
-is a unique integer. We create "peoplemerge" and "peoplemerge-1" branches owned
-by marilize, and a "peoplemerge" and "peoplemerge-1" branches owned by 'Sample
-Person' to test that branch name uniquifying works.
+Branches whose owner is being merged are uniquified by appending '-N'
+where N is a unique integer. We create "peoplemerge" and
+"peoplemerge-1" branches owned by marilize, and a "peoplemerge" and
+"peoplemerge-1" branches owned by 'Sample Person' to test that branch
+name uniquifying works.
Branches with smaller IDs will be processed first, so we create "peoplemerge"
first, and it will be renamed "peoplemerge-2". The extant "peoplemerge-1"
@@ -81,9 +85,9 @@
>>> peoplemerge11 = factory.makePersonalBranch(
... name='peoplemerge-1', owner=marilize)
-'Sample Person' is a deactivated member of the 'Ubuntu Translators' team,
-while marilize is an active member. After the merge, 'Sample Person' will be an
-active member of that team.
+'Sample Person' is a deactivated member of the 'Ubuntu Translators'
+team, while marilize is an active member. After the merge, 'Sample
+Person' will be an active member of that team.
>>> sample in ubuntu_translators.inactivemembers
True
@@ -102,7 +106,8 @@
u'Marilize Coetzee'
-== Do the merge! ==
+Do the merge!
+-------------
# Now we remove the only email address marilize had, so that we can merge
# it. First we need to change its status, though, because we can't delete
@@ -119,7 +124,8 @@
>>> personset.merge(marilize, sample)
-== Merge results ==
+Merge results
+-------------
Check that 'Sample Person' has indeed become an active member of 'Ubuntu
Translators'
@@ -336,37 +342,30 @@
loser, winner,
-== Merging teams ==
+Merging teams
+-------------
Merging of teams is also possible and uses the same API used for merging
-people. Note, though, that when merging teams, its polls will not be
-carried over to the remaining team. Team memberships, on the other hand,
-are carried over just like when merging people.
+people. Team memberships are carried over just like when merging people.
>>> from datetime import datetime, timedelta
>>> import pytz
- >>> from lp.registry.interfaces.poll import IPollSubset, PollSecrecy
>>> test_team = personset.newTeam(sample, 'test-team', 'Test team')
>>> launchpad_devs = personset.getByName('launchpad')
>>> ignored = launchpad_devs.addMember(
... test_team, reviewer=launchpad_devs.teamowner, force_team_add=True)
- >>> today = datetime.now(pytz.timezone('UTC'))
+ >>> today = datetime.now(pytz.UTC)
>>> tomorrow = today + timedelta(days=1)
- >>> poll = IPollSubset(test_team).new(
- ... 'test-poll', 'Title', 'Proposition', today, tomorrow,
- ... PollSecrecy.OPEN, allowspoilt=True)
- # test_team has a superteam, one active member and a poll.
+ # test_team has a superteam and one active member.
>>> [team.name for team in test_team.super_teams]
[u'launchpad']
>>> test_team.teamowner.name
u'name12'
>>> [member.name for member in test_team.allmembers]
[u'name12']
- >>> list(IPollSubset(test_team).getAll())
- [<Poll at ...]
- # Landscape-developers has no super teams, two members and no polls.
+ # Landscape-developers has no super teams and two members.
>>> landscape = personset.getByName('landscape-developers')
>>> [team.name for team in landscape.super_teams]
[]
@@ -374,8 +373,6 @@
u'name12'
>>> [member.name for member in landscape.allmembers]
[u'salgado', u'name12']
- >>> list(IPollSubset(landscape).getAll())
- []
Now we try to merge them, but since test_team has active members it can't be
merged.
@@ -427,14 +424,11 @@
... test_team.retractTeamMembership(team, test_team.teamowner)
>>> personset.merge(test_team, landscape)
- # The resulting Landscape-developers no new super teams, has
- # no polls and its members are still the same two from before the
- # merge.
+ # The resulting Landscape-developers no new super teams and its
+ # members are still the same two from before the merge.
>>> landscape.teamowner.name
u'name12'
>>> [member.name for member in landscape.allmembers]
[u'salgado', u'name12']
>>> [team.name for team in landscape.super_teams]
[]
- >>> list(IPollSubset(landscape).getAll())
- []
=== removed file 'lib/lp/registry/doc/poll-preconditions.txt'
--- lib/lp/registry/doc/poll-preconditions.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/registry/doc/poll-preconditions.txt 1970-01-01 00:00:00 +0000
@@ -1,74 +0,0 @@
-Poll preconditions
-==================
-
-There's some preconditions that we need to meet to vote in polls and remove
-options from them, Not meeting these preconditions is a programming error and
-should be threated as so.
-
- >>> from zope.component import getUtility
- >>> from canonical.database.sqlbase import flush_database_updates
- >>> from canonical.launchpad.ftests import login
- >>> from datetime import timedelta
- >>> from lp.registry.interfaces.person import IPersonSet
- >>> from lp.registry.interfaces.poll import IPollSet
-
- >>> ubuntu_team = getUtility(IPersonSet).get(17)
- >>> ubuntu_team_member = getUtility(IPersonSet).get(1)
- >>> ubuntu_team_nonmember = getUtility(IPersonSet).get(12)
-
- >>> pollset = getUtility(IPollSet)
- >>> director_election = pollset.getByTeamAndName(ubuntu_team,
- ... 'director-2004')
- >>> director_options = director_election.getActiveOptions()
- >>> leader_election = pollset.getByTeamAndName(ubuntu_team, 'leader-2004')
- >>> leader_options = leader_election.getActiveOptions()
- >>> opendate = leader_election.dateopens
- >>> onesec = timedelta(seconds=1)
-
-
-If the poll is already opened, it's impossible to remove an option.
-
- >>> leader_election.removeOption(leader_options[0], when=opendate)
- Traceback (most recent call last):
- ...
- AssertionError
-
-
-Trying to vote two times is a programming error.
-
- >>> votes = leader_election.storeSimpleVote(
- ... ubuntu_team_member, leader_options[0], when=opendate)
-
- >>> votes = leader_election.storeSimpleVote(
- ... ubuntu_team_member, leader_options[0], when=opendate)
- Traceback (most recent call last):
- ...
- AssertionError: Can't vote twice in the same poll
-
-
-It's not possible for a non-member to vote, neither to vote when the poll is
-not open.
-
- >>> votes = leader_election.storeSimpleVote(
- ... ubuntu_team_nonmember, leader_options[0], when=opendate)
- Traceback (most recent call last):
- ...
- AssertionError: Person ... is not a member of this poll's team.
-
- >>> votes = leader_election.storeSimpleVote(
- ... ubuntu_team_member, leader_options[0], when=opendate - onesec)
- Traceback (most recent call last):
- ...
- AssertionError: This poll is not open
-
-
-It's not possible to vote on an option that doesn't belong to the poll you're
-voting in.
-
- >>> options = {leader_options[0]: 1}
- >>> votes = director_election.storeCondorcetVote(
- ... ubuntu_team_member, options, when=opendate)
- Traceback (most recent call last):
- ...
- AssertionError: The option ... doesn't belong to this poll
-
=== removed file 'lib/lp/registry/doc/poll.txt'
--- lib/lp/registry/doc/poll.txt 2010-10-19 18:44:31 +0000
+++ lib/lp/registry/doc/poll.txt 1970-01-01 00:00:00 +0000
@@ -1,140 +0,0 @@
-Polls
-=====
-
-In Launchpad, we have teams as a way to group free software
-developers/contributors usually based on the free software
-product/project/distribution they're involved in. This is the case with teams
-like the 'Gnome Team' and the 'Ubuntu Team'. These teams often have leaders
-whose ellection depends on the vote of all members, and this is one of the
-reasons why we teams can have polls attached to them.
-
- >>> import pytz
- >>> from datetime import datetime, timedelta
- >>> from zope.component import getUtility
- >>> from canonical.database.sqlbase import flush_database_updates
- >>> from canonical.launchpad.ftests import login
- >>> from lp.registry.interfaces.person import IPersonSet
- >>> from lp.registry.interfaces.poll import (
- ... IPollSubset,
- ... PollAlgorithm,
- ... PollSecrecy,
- ... )
-
- >>> team = getUtility(IPersonSet).getByName('ubuntu-team')
- >>> member = getUtility(IPersonSet).getByName('stevea')
- >>> member2 = getUtility(IPersonSet).getByName('jdub')
- >>> member3 = getUtility(IPersonSet).getByName('kamion')
- >>> member4 = getUtility(IPersonSet).getByName('name16')
- >>> member5 = getUtility(IPersonSet).getByName('limi')
- >>> nonmember = getUtility(IPersonSet).getByName('justdave')
- >>> now = datetime.now(pytz.timezone('UTC'))
- >>> onesec = timedelta(seconds=1)
-
-We need to login with one of the administrators of the team named
-'ubuntu-team' to be able to create/edit polls.
- >>> login('colin.watson@xxxxxxxxxxxxxxx')
-
-First we get an object implementing IPollSubset, which is the set of polls for
-a given team (in our case, the 'Ubuntu Team')
- >>> pollsubset = IPollSubset(team)
-
-Now we create a new poll on this team.
- >>> opendate = datetime(2005, 01, 01, tzinfo=pytz.timezone('UTC'))
- >>> closedate = opendate + timedelta(weeks=2)
- >>> title = "2005 Leader's Elections"
- >>> proposition = "Who's going to be the next leader?"
- >>> type = PollAlgorithm.SIMPLE
- >>> secrecy = PollSecrecy.SECRET
- >>> allowspoilt = True
- >>> poll = pollsubset.new("leader-election", title, proposition, opendate,
- ... closedate, secrecy, allowspoilt, type)
-
-Now we test the if the poll is open or closed in some specific dates.
- >>> poll.isOpen(when=opendate)
- True
- >>> poll.isOpen(when=opendate - onesec)
- False
- >>> poll.isOpen(when=closedate)
- True
- >>> poll.isOpen(when=closedate + onesec)
- False
-
-To know what polls are open/closed/not-yet-opened in a team, you can use the
-methods of PollSubset.
-Here we'll query using three different dates:
-
-Query for open polls in the exact second the poll is opening.
- >>> [p.name for p in pollsubset.getOpenPolls(when=opendate)]
- [u'leader-election', u'never-closes', u'never-closes2', u'never-closes3',
- u'never-closes4']
-
-Query for closed polls, one second after the poll closes.
- >>> [p.name for p in pollsubset.getClosedPolls(when=closedate + onesec)]
- [u'director-2004', u'leader-2004', u'leader-election']
-
-Query for not-yet-opened polls, one second before the poll opens.
- >>> [p.name for p in pollsubset.getNotYetOpenedPolls(when=opendate - onesec)]
- [u'leader-election', u'not-yet-opened']
-
-All polls must have a set of options for people to choose, and they'll always
-start with zero options. We're responsible for adding new ones.
- >>> poll.getAllOptions().count()
- 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')
- >>> [o.title for o in poll.getActiveOptions()]
- [u'Francis Dolarhyde', u'Jack Crawford', u'Will Graham']
-
-Now, what happens if the poll is already open and, let's say, Francis Dolarhyde
-is convicted and thus becomes ineligible? We'll have to mark that option as
-inactive, so people can't vote on it.
- >>> francis.active = False
- >>> flush_database_updates()
- >>> [o.title for o in poll.getActiveOptions()]
- [u'Jack Crawford', u'Will Graham']
-
-If the poll is not yet opened, it's possible to simply remove a given option.
- >>> poll.removeOption(will, when=opendate - onesec)
- >>> [o.title for o in poll.getAllOptions()]
- [u'Francis Dolarhyde', u'Jack Crawford']
-
-Any member of the team this poll refers to is eligible to vote, if the poll is
-still open.
-
- >>> vote1 = poll.storeSimpleVote(member, jack, when=opendate)
- >>> vote2 = poll.storeSimpleVote(member2, None, when=opendate)
-
-
-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?"
- >>> 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')
-
- >>> options = {b: 1, d: 2, c: 3}
- >>> votes = poll2.storeCondorcetVote(member, options, when=opendate)
- >>> options = {d: 1, b: 2}
- >>> votes = poll2.storeCondorcetVote(member2, options, when=opendate)
- >>> options = {a: 1, c: 2, b: 3}
- >>> votes = poll2.storeCondorcetVote(member3, options, when=opendate)
- >>> options = {a: 1}
- >>> votes = poll2.storeCondorcetVote(member4, options, when=opendate)
- >>> for row in poll2.getPairwiseMatrix():
- ... print row
- [None, 2L, 2L, 2L]
- [2L, None, 2L, 2L]
- [1L, 1L, None, 1L]
- [2L, 1L, 2L, None]
-
=== removed file 'lib/lp/registry/interfaces/poll.py'
--- lib/lp/registry/interfaces/poll.py 2010-08-20 20:31:18 +0000
+++ lib/lp/registry/interfaces/poll.py 1970-01-01 00:00:00 +0000
@@ -1,500 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# pylint: disable-msg=E0211,E0213
-
-__all__ = [
- 'IPoll',
- 'IPollSet',
- 'IPollSubset',
- 'IPollOption',
- 'IPollOptionSet',
- 'IVote',
- 'IVoteCast',
- 'PollAlgorithm',
- 'PollSecrecy',
- 'PollStatus',
- 'IVoteSet',
- 'IVoteCastSet',
- 'OptionIsNotFromSimplePoll'
- ]
-
-from datetime import (
- datetime,
- timedelta,
- )
-
-from lazr.enum import (
- DBEnumeratedType,
- DBItem,
- )
-import pytz
-from zope.component import getUtility
-from zope.interface import (
- Attribute,
- Interface,
- )
-from zope.interface.exceptions import Invalid
-from zope.interface.interface import invariant
-from zope.schema import (
- Bool,
- Choice,
- Datetime,
- Int,
- Text,
- TextLine,
- )
-
-from canonical.launchpad import _
-from canonical.launchpad.validators.name import name_validator
-from lp.registry.interfaces.person import ITeam
-from lp.services.fields import ContentNameField
-
-
-class PollNameField(ContentNameField):
-
- errormessage = _("%s is already in use by another poll in this team.")
-
- @property
- def _content_iface(self):
- return IPoll
-
- def _getByName(self, name):
- team = ITeam(self.context, None)
- if team is None:
- team = self.context.team
- return getUtility(IPollSet).getByTeamAndName(team, name)
-
-
-class PollAlgorithm(DBEnumeratedType):
- """The algorithm used to accept and calculate the results."""
-
- SIMPLE = DBItem(1, """
- Simple Voting
-
- The most simple method for voting; you just choose a single option.
- """)
-
- CONDORCET = DBItem(2, """
- Condorcet Voting
-
- One of various methods used for calculating preferential votes. See
- http://www.electionmethods.org/CondorcetEx.htm for more information.
- """)
-
-
-class PollSecrecy(DBEnumeratedType):
- """The secrecy of a given Poll."""
-
- OPEN = DBItem(1, """
- Public Votes (Anyone can see a person's vote)
-
- Everyone who wants will be able to see a person's vote.
- """)
-
- ADMIN = DBItem(2, """
- Semi-secret Votes (Only team administrators can see a person's vote)
-
- All team owners and administrators will be able to see a person's vote.
- """)
-
- SECRET = DBItem(3, """
- Secret Votes (It's impossible to track a person's vote)
-
- We don't store the option a person voted in our database,
- """)
-
-
-class PollStatus:
- """This class stores the constants used when searching for polls."""
-
- OPEN = 'open'
- CLOSED = 'closed'
- NOT_YET_OPENED = 'not-yet-opened'
- ALL = frozenset([OPEN, CLOSED, NOT_YET_OPENED])
-
-
-class IPoll(Interface):
- """A poll for a given proposition in a team."""
-
- id = Int(title=_('The unique ID'), required=True, readonly=True)
-
- team = Int(
- title=_('The team that this poll refers to.'), required=True,
- readonly=True)
-
- name = PollNameField(
- title=_('The unique name of this poll'),
- description=_('A short unique name, beginning with a lower-case '
- 'letter or number, and containing only letters, '
- 'numbers, dots, hyphens, or plus signs.'),
- required=True, readonly=False, constraint=name_validator)
-
- title = TextLine(
- title=_('The title of this poll'), required=True, readonly=False)
-
- dateopens = Datetime(
- title=_('The date and time when this poll opens'), required=True,
- readonly=False)
-
- datecloses = Datetime(
- title=_('The date and time when this poll closes'), required=True,
- readonly=False)
-
- proposition = Text(
- title=_('The proposition that is going to be voted'), required=True,
- readonly=False)
-
- type = Choice(
- title=_('The type of this poll'), required=True,
- readonly=False, vocabulary=PollAlgorithm,
- default=PollAlgorithm.CONDORCET)
-
- allowspoilt = Bool(
- title=_('Users can spoil their votes?'),
- description=_(
- 'Allow users to leave the ballot blank (i.e. cast a vote for '
- '"None of the above")'),
- required=True, readonly=False, default=True)
-
- secrecy = Choice(
- title=_('The secrecy of the Poll'), required=True,
- readonly=False, vocabulary=PollSecrecy,
- default=PollSecrecy.SECRET)
-
- @invariant
- def saneDates(poll):
- """Ensure the poll's dates are sane.
-
- A poll's end date must be after its start date and its start date must
- be at least 12h from now.
- """
- if poll.dateopens >= poll.datecloses:
- raise Invalid(
- "A poll cannot close at the time (or before) it opens.")
- now = datetime.now(pytz.UTC)
- twelve_hours_ahead = now + timedelta(hours=12)
- start_date = poll.dateopens.astimezone(pytz.UTC)
- if start_date < twelve_hours_ahead:
- raise Invalid(
- "A poll cannot open less than 12 hours after it's created.")
-
- def isOpen(when=None):
- """Return True if this Poll is still open.
-
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will be open at a specific date.
- """
-
- def isClosed(when=None):
- """Return True if this Poll is already closed.
-
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will be closed at a specific date.
- """
-
- def isNotYetOpened(when=None):
- """Return True if this Poll is not yet opened.
-
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will be not-yet-opened at a specific date.
- """
-
- def closesIn():
- """Return a timedelta object of the interval between now and the date
- when this poll closes."""
-
- def opensIn():
- """Return a timedelta object of the interval between now and the date
- when this poll opens."""
-
- def newOption(name, title=None, active=True):
- """Create a new PollOption for this poll.
-
- If title is None it'll be the same as name.
- """
-
- def getActiveOptions():
- """Return all PollOptions of this poll that are active."""
-
- def getAllOptions():
- """Return all Options of this poll."""
-
- def personVoted(person):
- """Return True if :person has already voted in this poll."""
-
- def getVotesByPerson(person):
- """Return the votes of the given person in this poll.
-
- The return value will always be a list of Vote objects. That's for
- consistency because on simple polls there'll be always a single vote,
- but for condorcet poll, there'll always be a list.
- """
-
- def getTotalVotes():
- """Return the total number of votes this poll had.
-
- This must be used only on closed polls.
- """
-
- def getWinners():
- """Return the options which won this poll.
-
- This should be used only on closed polls.
- """
-
- def removeOption(option, when=None):
- """Remove the given option from this poll.
-
- A ValueError is raised if the given option doesn't belong to this poll.
- This method can be used only on polls that are not yet opened.
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will be not-yet-opened at a specific date.
- """
-
- def getOptionByName(name):
- """Return the PollOption by the given name."""
-
- def storeSimpleVote(person, option, when=None):
- """Store and return the vote of a given person in a this poll.
-
- This method can be used only if this poll is still open and if this is
- a Simple-style poll.
-
- :option: The choosen option.
-
- :when: Optional argument used only by our tests, to test if the poll
- is/was/will be open at a specific date.
- """
-
- def storeCondorcetVote(person, options, when=None):
- """Store and return the votes of a given person in this poll.
-
- This method can be used only if this poll is still open and if this is
- a Condorcet-style poll.
-
- :options: A dictionary, where the options are the keys and the
- preferences of each option are the values.
-
- :when: Optional argument used only by our tests, to test if the poll
- is/was/will be open at a specific date.
- """
-
- def getPairwiseMatrix():
- """Return the pairwise matrix for this poll.
-
- This method is only available for condorcet-style polls.
- See http://www.electionmethods.org/CondorcetEx.htm for an example of a
- pairwise matrix.
- """
-
-
-class IPollSet(Interface):
- """The set of Poll objects."""
-
- def new(team, name, title, proposition, dateopens, datecloses,
- secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
- """Create a new Poll for the given team."""
-
- def selectByTeam(team, status=PollStatus.ALL, orderBy=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.
-
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will-be open at a specific date.
- """
-
- def getByTeamAndName(team, name, default=None):
- """Return the Poll for the given team with the given name.
-
- Return :default if there's no Poll with this name for that team.
- """
-
-
-class IPollSubset(Interface):
- """The set of Poll objects for a given team."""
-
- team = Attribute(_("The team of these polls."))
-
- title = Attribute('Polls Page Title')
-
- def new(name, title, proposition, dateopens, datecloses, secrecy,
- allowspoilt, poll_type=PollAlgorithm.SIMPLE):
- """Create a new Poll for this team."""
-
- def getAll():
- """Return all Polls of this team."""
-
- def getOpenPolls(when=None):
- """Return all Open Polls for this team ordered by the date they'll
- close.
-
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will be open at a specific date.
- """
-
- def getNotYetOpenedPolls(when=None):
- """Return all Not-Yet-Opened Polls for this team ordered by the date
- they'll open.
-
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will be open at a specific date.
- """
-
- def getClosedPolls(when=None):
- """Return all Closed Polls for this team ordered by the date they
- closed.
-
- The optional :when argument is used only by our tests, to test if the
- poll is/was/will be open at a specific date.
- """
-
-
-class PollOptionNameField(ContentNameField):
-
- errormessage = _("%s is already in use by another option in this poll.")
-
- @property
- def _content_iface(self):
- return IPollOption
-
- def _getByName(self, name):
- if IPollOption.providedBy(self.context):
- poll = self.context.poll
- else:
- poll = self.context
- return poll.getOptionByName(name)
-
-
-class IPollOption(Interface):
- """An option to be voted in a given Poll."""
-
- id = Int(title=_('The unique ID'), required=True, readonly=True)
-
- poll = Int(
- title=_('The Poll to which this option refers to.'), required=True,
- readonly=True)
-
- name = PollOptionNameField(
- title=_('Name'), required=True, readonly=False)
-
- title = TextLine(
- title=_('Title'),
- description=_(
- 'The title of this option. A single brief sentence that '
- 'summarises the outcome for which people are voting if '
- 'they select this option.'),
- required=True, readonly=False)
-
- active = Bool(
- title=_('Is this option active?'), required=True, readonly=False,
- default=True)
-
- def destroySelf():
- """Remove this option from the database."""
-
-
-class IPollOptionSet(Interface):
- """The set of PollOption objects."""
-
- def new(poll, name, title, active=True):
- """Create a new PollOption."""
-
- def selectByPoll(poll, only_active=False):
- """Return all PollOptions of the given poll.
-
- If :only_active is True, then return only the active polls.
- """
-
- def getByPollAndId(poll, id, default=None):
- """Return the PollOption with the given id.
-
- Return :default if there's no PollOption with the given id or if that
- PollOption is not in the given poll.
- """
-
-
-class IVoteCast(Interface):
- """Here we store who voted in a Poll, but not their votes."""
-
- id = Int(title=_('The unique ID'), required=True, readonly=True)
-
- person = Int(
- title=_('The Person that voted.'), required=False, readonly=True)
-
- poll = Int(
- title=_('The Poll in which the person voted.'), required=True,
- readonly=True)
-
-
-class IVoteCastSet(Interface):
- """The set of all VoteCast objects."""
-
- def new(poll, person):
- """Create a new VoteCast."""
-
-
-class IVote(Interface):
- """Here we store the vote itself, linked to a special token.
-
- This token is given to the user when he votes, so he can change his vote
- later.
- """
-
- id = Int(
- title=_('The unique ID'), required=True, readonly=True)
-
- person = Int(
- title=_('The Person that voted.'), required=False, readonly=True)
-
- poll = Int(
- title=_('The Poll in which the person voted.'), required=True,
- readonly=True)
-
- option = Int(
- title=_('The PollOption choosen.'), required=True, readonly=False)
-
- preference = Int(
- title=_('The preference of the choosen PollOption'), required=True,
- readonly=False)
-
- token = Text(
- title=_('The token we give to the user.'),
- required=True, readonly=True)
-
-
-class OptionIsNotFromSimplePoll(Exception):
- """Someone tried use an option from a non-SIMPLE poll as if it was from a
- SIMPLE one."""
-
-
-class IVoteSet(Interface):
- """The set of all Vote objects."""
-
- def newToken():
- """Return a token that was never used in the Vote table."""
-
- def new(poll, option, preference, token, person):
- """Create a new Vote."""
-
- def getByToken(token):
- """Return the list of votes with the given token.
-
- For polls whose type is SIMPLE, this list will contain a single vote,
- because in SIMPLE poll only one option can be choosen.
- """
-
- def getVotesByOption(option):
- """Return the number of votes the given option received.
-
- Raises a TypeError if the given option doesn't belong to a
- simple-style poll.
- """
-
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2010-12-10 14:58:31 +0000
+++ lib/lp/registry/model/person.py 2010-12-15 22:26:18 +0000
@@ -3871,20 +3871,10 @@
('personlanguage', 'person'),
('person', 'merged'),
('emailaddress', 'person'),
- # Polls are not carried over when merging teams.
- ('poll', 'team'),
# We can safely ignore the mailinglist table as there's a sanity
# check above which prevents teams with associated mailing lists
# from being merged.
('mailinglist', 'team'),
- # I don't think we need to worry about the votecast and vote
- # tables, because a real human should never have two profiles
- # in Launchpad that are active members of a given team and voted
- # in a given poll. -- GuilhermeSalgado 2005-07-07
- # We also can't afford to change poll results after they are
- # closed -- StuartBishop 20060602
- ('votecast', 'person'),
- ('vote', 'person'),
('translationrelicensingagreement', 'person'),
]
=== removed file 'lib/lp/registry/model/poll.py'
--- lib/lp/registry/model/poll.py 2010-08-20 20:31:18 +0000
+++ lib/lp/registry/model/poll.py 1970-01-01 00:00:00 +0000
@@ -1,440 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# pylint: disable-msg=E0611,W0212
-
-__metaclass__ = type
-__all__ = [
- 'Poll',
- 'PollOption',
- 'PollOptionSet',
- 'PollSet',
- 'VoteCast',
- 'Vote',
- 'VoteSet',
- 'VoteCastSet',
- ]
-
-from datetime import datetime
-import random
-
-import pytz
-from sqlobject import (
- AND,
- BoolCol,
- ForeignKey,
- IntCol,
- OR,
- SQLObjectNotFound,
- StringCol,
- )
-from storm.store import Store
-from zope.component import getUtility
-from zope.interface import implements
-
-from canonical.database.datetimecol import UtcDateTimeCol
-from canonical.database.enumcol import EnumCol
-from canonical.database.sqlbase import (
- SQLBase,
- sqlvalues,
- )
-from lp.registry.interfaces.person import validate_public_person
-from lp.registry.interfaces.poll import (
- IPoll,
- IPollOption,
- IPollOptionSet,
- IPollSet,
- IVote,
- IVoteCast,
- IVoteCastSet,
- IVoteSet,
- OptionIsNotFromSimplePoll,
- PollAlgorithm,
- PollSecrecy,
- PollStatus,
- )
-
-
-class Poll(SQLBase):
- """See IPoll."""
-
- implements(IPoll)
- _table = 'Poll'
- sortingColumns = ['title', 'id']
- _defaultOrder = sortingColumns
-
- team = ForeignKey(
- dbName='team', foreignKey='Person',
- storm_validator=validate_public_person, notNull=True)
-
- name = StringCol(dbName='name', notNull=True)
-
- title = StringCol(dbName='title', notNull=True, unique=True)
-
- dateopens = UtcDateTimeCol(dbName='dateopens', notNull=True)
-
- datecloses = UtcDateTimeCol(dbName='datecloses', notNull=True)
-
- proposition = StringCol(dbName='proposition', notNull=True)
-
- type = EnumCol(dbName='type', enum=PollAlgorithm,
- default=PollAlgorithm.SIMPLE)
-
- allowspoilt = BoolCol(dbName='allowspoilt', default=True, notNull=True)
-
- secrecy = EnumCol(dbName='secrecy', enum=PollSecrecy,
- default=PollSecrecy.SECRET)
-
- def newOption(self, name, title, active=True):
- """See IPoll."""
- return getUtility(IPollOptionSet).new(self, name, title, active)
-
- def isOpen(self, when=None):
- """See IPoll."""
- if when is None:
- when = datetime.now(pytz.timezone('UTC'))
- return (self.datecloses >= when and self.dateopens <= when)
-
- @property
- def closesIn(self):
- """See IPoll."""
- return self.datecloses - datetime.now(pytz.timezone('UTC'))
-
- @property
- def opensIn(self):
- """See IPoll."""
- return self.dateopens - datetime.now(pytz.timezone('UTC'))
-
- def isClosed(self, when=None):
- """See IPoll."""
- if when is None:
- when = datetime.now(pytz.timezone('UTC'))
- return self.datecloses <= when
-
- def isNotYetOpened(self, when=None):
- """See IPoll."""
- if when is None:
- when = datetime.now(pytz.timezone('UTC'))
- return self.dateopens > when
-
- def getAllOptions(self):
- """See IPoll."""
- return getUtility(IPollOptionSet).selectByPoll(self)
-
- def getActiveOptions(self):
- """See IPoll."""
- return getUtility(IPollOptionSet).selectByPoll(self, only_active=True)
-
- def getVotesByPerson(self, person):
- """See IPoll."""
- return Vote.selectBy(person=person, poll=self)
-
- def personVoted(self, person):
- """See IPoll."""
- results = VoteCast.selectBy(person=person, poll=self)
- return bool(results.count())
-
- def removeOption(self, option, when=None):
- """See IPoll."""
- assert self.isNotYetOpened(when=when)
- if option.poll != self:
- raise ValueError(
- "Can't remove an option that doesn't belong to this poll")
- option.destroySelf()
-
- def getOptionByName(self, name):
- """See IPoll."""
- return PollOption.selectOneBy(poll=self, name=name)
-
- def _assertEverythingOkAndGetVoter(self, person, when=None):
- """Use assertions to Make sure all pre-conditions for a person to vote
- are met.
-
- Return the person if this is not a secret poll or None if it's a
- secret one.
- """
- assert self.isOpen(when=when), "This poll is not open"
- assert not self.personVoted(person), "Can't vote twice in the same poll"
- assert person.inTeam(self.team), (
- "Person %r is not a member of this poll's team." % person)
-
- # We only associate the option with the person if the poll is not a
- # SECRET one.
- if self.secrecy == PollSecrecy.SECRET:
- voter = None
- else:
- voter = person
- return voter
-
- def storeCondorcetVote(self, person, options, when=None):
- """See IPoll."""
- voter = self._assertEverythingOkAndGetVoter(person, when=when)
- assert self.type == PollAlgorithm.CONDORCET
- voteset = getUtility(IVoteSet)
-
- token = voteset.newToken()
- votes = []
- activeoptions = self.getActiveOptions()
- for option, preference in options.items():
- assert option.poll == self, (
- "The option %r doesn't belong to this poll" % option)
- assert option.active, "Option %r is not active" % option
- votes.append(voteset.new(self, option, preference, token, voter))
-
- # Store a vote with preference = None for each active option of this
- # poll that wasn't in the options argument.
- for option in activeoptions:
- if option not in options:
- votes.append(voteset.new(self, option, None, token, voter))
-
- getUtility(IVoteCastSet).new(self, person)
- return votes
-
- def storeSimpleVote(self, person, option, when=None):
- """See IPoll."""
- voter = self._assertEverythingOkAndGetVoter(person, when=when)
- assert self.type == PollAlgorithm.SIMPLE
- voteset = getUtility(IVoteSet)
-
- if option is None and not self.allowspoilt:
- raise ValueError("This poll doesn't allow spoilt votes.")
- elif option is not None:
- assert option.poll == self, (
- "The option %r doesn't belong to this poll" % option)
- assert option.active, "Option %r is not active" % option
- token = voteset.newToken()
- # This is a simple-style poll, so you can vote only on a single option
- # and this option's preference must be 1
- preference = 1
- vote = voteset.new(self, option, preference, token, voter)
- getUtility(IVoteCastSet).new(self, person)
- return vote
-
- def getTotalVotes(self):
- """See IPoll."""
- assert self.isClosed()
- return Vote.selectBy(poll=self).count()
-
- def getWinners(self):
- """See IPoll."""
- assert self.isClosed()
- # XXX: GuilhermeSalgado 2005-08-24:
- # For now, this method works only for SIMPLE-style polls. This is
- # not a problem as CONDORCET-style polls are disabled.
- assert self.type == PollAlgorithm.SIMPLE
- query = """
- SELECT option
- FROM Vote
- WHERE poll = %d AND option IS NOT NULL
- GROUP BY option
- HAVING COUNT(*) = (
- SELECT COUNT(*)
- FROM Vote
- WHERE poll = %d
- GROUP BY option
- ORDER BY COUNT(*) DESC LIMIT 1
- )
- """ % (self.id, self.id)
- results = Store.of(self).execute(query).get_all()
- if not results:
- return None
- return [PollOption.get(id) for (id,) in results]
-
- def getPairwiseMatrix(self):
- """See IPoll."""
- assert self.type == PollAlgorithm.CONDORCET
- options = list(self.getAllOptions())
- pairwise_matrix = []
- for option1 in options:
- pairwise_row = []
- for option2 in options:
- points_query = """
- SELECT COUNT(*) FROM Vote as v1, Vote as v2 WHERE
- v1.token = v2.token AND
- v1.option = %s AND v2.option = %s AND
- (
- (
- v1.preference IS NOT NULL AND
- v2.preference IS NOT NULL AND
- v1.preference < v2.preference
- )
- OR
- (
- v1.preference IS NOT NULL AND
- v2.preference IS NULL
- )
- )
- """ % sqlvalues(option1.id, option2.id)
- if option1 == option2:
- pairwise_row.append(None)
- else:
- points = Store.of(self).execute(points_query).get_one()[0]
- pairwise_row.append(points)
- pairwise_matrix.append(pairwise_row)
- return pairwise_matrix
-
-
-class PollSet:
- """See IPollSet."""
-
- implements(IPollSet)
-
- 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):
- """See IPollSet."""
- if when is None:
- when = datetime.now(pytz.timezone('UTC'))
-
- if orderBy is None:
- orderBy = Poll.sortingColumns
-
-
- status = set(status)
- status_clauses = []
- if PollStatus.OPEN in status:
- status_clauses.append(AND(Poll.q.dateopens <= when,
- Poll.q.datecloses > when))
- if PollStatus.CLOSED in status:
- status_clauses.append(Poll.q.datecloses <= when)
- if PollStatus.NOT_YET_OPENED in status:
- status_clauses.append(Poll.q.dateopens > when)
-
- assert len(status_clauses) > 0, "No poll statuses were selected"
-
- results = Poll.select(AND(Poll.q.teamID == team.id,
- OR(*status_clauses)))
-
- return results.orderBy(orderBy)
-
- 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
-
-
-class PollOption(SQLBase):
- """See IPollOption."""
-
- implements(IPollOption)
- _table = 'PollOption'
- _defaultOrder = ['title', 'id']
-
- poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
-
- name = StringCol(notNull=True)
-
- title = StringCol(notNull=True)
-
- active = BoolCol(notNull=True, default=False)
-
-
-class PollOptionSet:
- """See IPollOptionSet."""
-
- implements(IPollOptionSet)
-
- def new(self, poll, name, title, active=True):
- """See IPollOptionSet."""
- return PollOption(poll=poll, name=name, title=title, active=active)
-
- def selectByPoll(self, poll, only_active=False):
- """See IPollOptionSet."""
- query = PollOption.q.pollID == poll.id
- if only_active:
- query = AND(query, PollOption.q.active == True)
- return PollOption.select(query)
-
- 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
-
-
-class VoteCast(SQLBase):
- """See IVoteCast."""
-
- implements(IVoteCast)
- _table = 'VoteCast'
- _defaultOrder = 'id'
-
- person = ForeignKey(
- dbName='person', foreignKey='Person',
- storm_validator=validate_public_person, notNull=True)
-
- poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
-
-
-class VoteCastSet:
- """See IVoteCastSet."""
-
- implements(IVoteCastSet)
-
- def new(self, poll, person):
- """See IVoteCastSet."""
- return VoteCast(poll=poll, person=person)
-
-
-class Vote(SQLBase):
- """See IVote."""
-
- implements(IVote)
- _table = 'Vote'
- _defaultOrder = ['preference', 'id']
-
- person = ForeignKey(
- dbName='person', foreignKey='Person',
- storm_validator=validate_public_person)
-
- poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
-
- option = ForeignKey(dbName='option', foreignKey='PollOption')
-
- preference = IntCol(dbName='preference')
-
- token = StringCol(dbName='token', notNull=True, unique=True)
-
-
-class VoteSet:
- """See IVoteSet."""
-
- implements(IVoteSet)
-
- def newToken(self):
- """See IVoteSet."""
- chars = '23456789bcdfghjkmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ'
- length = 10
- token = ''.join([random.choice(chars) for c in range(length)])
- while self.getByToken(token):
- token = ''.join([random.choice(chars) for c in range(length)])
- return token
-
- def new(self, poll, option, preference, token, person):
- """See IVoteSet."""
- return Vote(poll=poll, option=option, preference=preference,
- token=token, person=person)
-
- def getByToken(self, token):
- """See IVoteSet."""
- return Vote.selectBy(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()
-
=== removed directory 'lib/lp/registry/stories/team-polls'
=== removed file 'lib/lp/registry/stories/team-polls/create-poll-options.txt'
--- lib/lp/registry/stories/team-polls/create-poll-options.txt 2009-08-19 19:48:09 +0000
+++ lib/lp/registry/stories/team-polls/create-poll-options.txt 1970-01-01 00:00:00 +0000
@@ -1,85 +0,0 @@
-= Poll options =
-
-A poll can have any number of options, but these must be created
-before the poll has opened.
-
-First we create a new poll to use throughout this test.
-
- >>> login('jeff.waugh@xxxxxxxxxxxxxxx')
- >>> 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...
- >>> logout()
-
-Our poll is not yet open, so new options can be added to it.
-
- >>> team_admin_browser = setupBrowser(
- ... auth='Basic jeff.waugh@xxxxxxxxxxxxxxx:jdub')
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080')
- >>> team_admin_browser.getLink('Add new option').click()
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080/+newoption'
-
- >>> bill_name = (
- ... 'bill-amazingly-huge-middle-name-almost-impossible-to-read-graham')
- >>> team_admin_browser.getControl('Name').value = bill_name
- >>> team_admin_browser.getControl('Title').value = 'Bill Graham'
- >>> team_admin_browser.getControl('Create').click()
-
-After adding an options we're taken back to the poll's home page.
-
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080'
-
-And here we see the option listed as one of the active options for this
-poll.
-
- >>> print extract_text(
- ... find_tag_by_id(team_admin_browser.contents, 'options'))
- Name Title Active
- bill... Bill Graham Yes
-
-If we try to add a new option without providing a title, we'll get an error
-message because the title is required.
-
- >>> team_admin_browser.getLink('Add new option').click()
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080/+newoption'
-
- >>> will_name = (
- ... 'will-amazingly-huge-middle-name-almost-impossible-to-read-graham')
- >>> team_admin_browser.getControl('Name').value = will_name
- >>> team_admin_browser.getControl('Title').value = ''
- >>> team_admin_browser.getControl('Create').click()
-
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- Required input is missing.
-
-If we try to add a new option with the same name of a existing option, we
-should get a nice error message
-
- >>> team_admin_browser.getControl('Name').value = bill_name
- >>> team_admin_browser.getControl('Title').value = 'Bill Again'
- >>> team_admin_browser.getControl('Create').click()
-
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- ...is already in use by another option in this poll.
-
-It's not possible to add/edit a poll option after a poll is open or closed.
-That's only possible when the poll is not yet open.
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/director-2004/+newoption')
-
- >>> "\n".join(get_feedback_messages(team_admin_browser.contents))
- u'You can’t add new options because the poll is already closed.'
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+newoption')
- >>> "\n".join(get_feedback_messages(team_admin_browser.contents))
- u'You can’t add new options because the poll is already open.'
=== removed file 'lib/lp/registry/stories/team-polls/create-polls.txt'
--- lib/lp/registry/stories/team-polls/create-polls.txt 2010-05-19 05:47:50 +0000
+++ lib/lp/registry/stories/team-polls/create-polls.txt 1970-01-01 00:00:00 +0000
@@ -1,163 +0,0 @@
-Let's first setup some objects that we'll need.
-
- >>> no_priv_browser = setupBrowser(
- ... auth='Basic no-priv@xxxxxxxxxxxxx:test')
- >>> team_admin_browser = setupBrowser(
- ... 'Basic jeff.waugh@xxxxxxxxxxxxxxx:jdub')
-
-If you're not logged in and go to the +polls page of the "Ubuntu Team"
-you'll see a link to login as a team administrator.
-
- >>> anon_browser.open('http://launchpad.dev/~ubuntu-team')
- >>> anon_browser.getLink('Show polls').click()
- >>> anon_browser.url
- 'http://launchpad.dev/~ubuntu-team/+polls'
- >>> anon_browser.getLink('Log in as an admin to set up a new poll').url
- 'http://launchpad.dev/~ubuntu-team/+login'
-
-Try to create a new poll logged in as 'no-priv', which is not a team
-administrator. There's no link leading to the +newpoll page, but the user can
-easily guess it.
-
- >>> no_priv_browser.open('http://launchpad.dev/~ubuntu-team/+newpoll')
- Traceback (most recent call last):
- ...
- Unauthorized:...
-
-Now we're logged in as Jeff Waugh which is a team administrator and thus can
-create a new poll.
-
- >>> team_admin_browser.open('http://launchpad.dev/~ubuntu-team')
- >>> team_admin_browser.getLink('Show polls').click()
- >>> team_admin_browser.getLink('Set up a new poll').click()
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+newpoll'
-
- >>> team_admin_browser.title
- 'New poll for team Ubuntu Team...
-
-First we try to create a poll with a invalid name to
-test the name field validator.
-
- >>> team_admin_browser.getControl(
- ... 'The unique name of this poll').value = 'election_2100'
- >>> team_admin_browser.getControl(
- ... 'The title of this poll').value = 'Presidential Election 2100'
- >>> proposition = 'Who is going to be the next president?'
- >>> team_admin_browser.getControl(
- ... 'The proposition that is going to be voted').value = proposition
- >>> team_admin_browser.getControl(
- ... 'Users can spoil their votes?').selected = True
- >>> team_admin_browser.getControl(
- ... name='field.dateopens').value = '2100-06-04 02:00:00+00:00'
- >>> team_admin_browser.getControl(
- ... name='field.datecloses').value = '2100-07-04 02:00:00+00:00'
- >>> team_admin_browser.getControl('Continue').click()
-
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- Invalid name 'election_2100'. Names must be at least two characters ...
-
-We fix the name, but swap the dates. Again a nice error message.
-
- >>> team_admin_browser.getControl(
- ... 'The unique name of this poll').value = 'election-2100'
- >>> team_admin_browser.getControl(
- ... name='field.dateopens').value = '2100-07-04 02:00:00+00:00'
- >>> team_admin_browser.getControl(
- ... name='field.datecloses').value = '2100-06-04 02:00:00+00:00'
- >>> team_admin_browser.getControl('Continue').click()
-
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- A poll cannot close at the time (or before) it opens.
-
-Now we get it right.
-
- >>> team_admin_browser.getControl(
- ... name='field.dateopens').value = '2100-06-04 02:00:00+00:00'
- >>> team_admin_browser.getControl(
- ... name='field.datecloses').value = '2100-07-04 02:00:00+00:00'
- >>> team_admin_browser.getControl('Continue').click()
-
-We're redirected to the newly created poll page.
-
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/election-2100'
-
-Create a new poll that starts in 2025-06-04 and will last until 2035.
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+newpoll')
- >>> team_admin_browser.getControl(
- ... 'The unique name of this poll').value = 'dpl-2080'
- >>> team_admin_browser.getControl(
- ... 'The title of this poll').value = 'Debian Project Leader Election 2080'
- >>> proposition = 'The next debian project leader'
- >>> team_admin_browser.getControl(
- ... 'The proposition that is going to be voted').value = proposition
- >>> team_admin_browser.getControl(
- ... 'Users can spoil their votes?').selected = True
- >>> team_admin_browser.getControl(
- ... name='field.dateopens').value = '2025-06-04 02:00:00+00:00'
- >>> team_admin_browser.getControl(
- ... name='field.datecloses').value = '2035-06-04 02:00:00+00:00'
- >>> team_admin_browser.getControl('Continue').click()
-
-We're redirected to the newly created poll
-
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080'
- >>> team_admin_browser.title
- 'Debian Project Leader Election 2080 : \xe2\x80\x9cUbuntu Team\xe2\x80\x9d team'
- >>> print_location(team_admin_browser.contents)
- Hierarchy: ?Ubuntu Team? team > Debian Project Leader Election 2080
- Tabs:
- * Overview (selected) - http://launchpad.dev/~ubuntu-team
- * Code - http://code.launchpad.dev/~ubuntu-team
- * Bugs - http://bugs.launchpad.dev/~ubuntu-team
- * Blueprints - http://blueprints.launchpad.dev/~ubuntu-team
- * Translations - http://translations.launchpad.dev/~ubuntu-team
- * Answers - http://answers.launchpad.dev/~ubuntu-team
- Main heading: Debian Project Leader Election 2080
- >>> team_admin_browser.getLink('add an option').url
- 'http://launchpad.dev/%7Eubuntu-team/+poll/dpl-2080/+newoption'
-
-Now lets try to insert a poll with the name of a existing one.
-
-# XXX matsubara 2006-07-17 bug=53302:
-# There's no link to get back to +polls.
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+newpoll')
- >>> team_admin_browser.getControl(
- ... 'The unique name of this poll').value = 'dpl-2080'
- >>> team_admin_browser.getControl(
- ... 'The title of this poll').value = 'Debian Project Leader Election 2080'
- >>> proposition = 'The next debian project leader'
- >>> team_admin_browser.getControl(
- ... 'The proposition that is going to be voted').value = proposition
- >>> team_admin_browser.getControl(
- ... 'Users can spoil their votes?').selected = True
- >>> team_admin_browser.getControl(
- ... name='field.dateopens').value = '2025-06-04 02:00:00+00:00'
- >>> team_admin_browser.getControl(
- ... name='field.datecloses').value = '2035-06-04 02:00:00+00:00'
- >>> team_admin_browser.getControl('Continue').click()
-
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- dpl-2080 is already in use by another poll in this team.
-
-When creating a new poll, its start date must be at least 12 hours from
-now, so that the user creating it has a chance to add some options before
-the poll opens -- at that point new options cannot be added.
-
- >>> team_admin_browser.getControl('The unique name').value = 'today'
- >>> from datetime import datetime
- >>> today = datetime.today().strftime('%Y-%m-%d')
- >>> team_admin_browser.getControl(name='field.dateopens').value = today
- >>> team_admin_browser.getControl('Continue').click()
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- A poll cannot open less than 12 hours after it's created.
=== removed file 'lib/lp/registry/stories/team-polls/edit-options.txt'
--- lib/lp/registry/stories/team-polls/edit-options.txt 2009-08-19 19:48:09 +0000
+++ lib/lp/registry/stories/team-polls/edit-options.txt 1970-01-01 00:00:00 +0000
@@ -1,59 +0,0 @@
-= Editing poll options =
-
-Changing the poll options detail is not possible if you are not one of the
-team's administrators:
-
- >>> user_browser.open('http://launchpad.dev/~ubuntu-team/+polls')
- >>> user_browser.getLink('A public poll that never closes').click()
- >>> user_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes4'
- >>> print extract_text(find_tag_by_id(user_browser.contents, 'options'))
- Name Title Active
- OptionA OptionA Yes
- ...
- >>> user_browser.getLink('[Edit]')
- Traceback (most recent call last):
- ...
- LinkNotFoundError
-
-And when the poll already started, administrators cannot change the options
-either:
-
- # Need to craft the URL manually because there's no link to it -- the
- # option can't be changed, after all.
- >>> browser = setupBrowser(
- ... auth='Basic jeff.waugh@xxxxxxxxxxxxxxx:jdub')
- >>> browser.open('http://launchpad.dev/~ubuntu-team/+poll/never-closes4/'
- ... '+option/20')
- >>> print "\n".join(get_feedback_messages(browser.contents))
- You can’t edit any options because the poll is already open.
-
-Since Jeff is an administrator of ubuntu-team and we have a poll that hasn't
-been opened yet, he should be able to edit its options.
-
- >>> browser.open('http://launchpad.dev/~ubuntu-team/+polls')
- >>> browser.getLink('A public poll that has not opened yet').click()
- >>> browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/not-yet-opened'
-
- >>> browser.getLink('[Edit]').click()
- >>> browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/not-yet-opened/+option/...'
-
- >>> browser.getControl('Name').value
- 'OptionX'
- >>> browser.getControl('Title').value
- 'OptionX'
- >>> browser.getControl('Name').value = 'option-z'
- >>> browser.getControl('Title').value = 'Option Z'
- >>> browser.getControl('Save').click()
-
- >>> browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/not-yet-opened'
- >>> print find_portlet(browser.contents, 'Voting options').renderContents()
- <BLANKLINE>
- <h2>Voting options</h2>
- ...
- ...option-z...
- ...
-
=== removed file 'lib/lp/registry/stories/team-polls/edit-poll.txt'
--- lib/lp/registry/stories/team-polls/edit-poll.txt 2010-10-11 17:36:14 +0000
+++ lib/lp/registry/stories/team-polls/edit-poll.txt 1970-01-01 00:00:00 +0000
@@ -1,97 +0,0 @@
-= Editing a poll =
-
-All attributes of a poll can be changed as long as the poll has not opened
-yet.
-
-First we create a new poll to use throughout this test.
-
- >>> login('jeff.waugh@xxxxxxxxxxxxxxx')
- >>> 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...
- >>> logout()
-
-Now we'll try to change its name to something that is already in use.
-
- >>> team_admin_browser = setupBrowser(
- ... auth='Basic jeff.waugh@xxxxxxxxxxxxxxx:jdub')
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080')
- >>> team_admin_browser.getLink('Change details').click()
-
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080/+edit'
-
- >>> team_admin_browser.getControl(
- ... 'The unique name of this poll').value = 'never-closes'
- >>> team_admin_browser.getControl('Save').click()
-
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- ...never-closes is already in use by another poll in this team.
-
-Entering an end date that precedes the start date returns a nice error
-message.
-
- >>> team_admin_browser.getControl(
- ... 'The unique name of this poll').value = 'dpl-2080'
- >>> team_admin_browser.getControl(
- ... name='field.dateopens').value = '3000-11-01 00:00:00+00:00'
- >>> team_admin_browser.getControl(
- ... name='field.datecloses').value = '3000-01-01 00:00:00+00:00'
- >>> team_admin_browser.getControl('Save').click()
-
- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
- There is 1 error.
- A poll cannot close at the time (or before) it opens.
-
-We successfully change the polls name
-
- >>> team_admin_browser.getControl(
- ... 'The unique name of this poll').value = 'election-3000'
- >>> team_admin_browser.getControl(
- ... name='field.dateopens').value = '3000-01-01 00:00:00+00:00'
- >>> team_admin_browser.getControl(
- ... name='field.datecloses').value = '3000-11-01 00:00:00+00:00'
- >>> team_admin_browser.getControl('Save').click()
-
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/election-3000'
-
-Trying to edit a poll that's already open isn't possible.
-
- >>> team_admin_browser.open('http://launchpad.dev/~ubuntu-team/')
- >>> team_admin_browser.getLink('Show polls').click()
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+polls'
-
- >>> team_admin_browser.getLink('A random poll that never closes').click()
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote'
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+edit')
- >>> print extract_text(
- ... find_tag_by_id(team_admin_browser.contents, 'not-editable'))
- This poll can't be edited...
-
-It's also not possible to edit a poll that's already closed.
-
- >>> team_admin_browser.open('http://launchpad.dev/~ubuntu-team/')
- >>> team_admin_browser.getLink('Show polls').click()
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+polls'
-
- >>> team_admin_browser.getLink("2004 Director's Elections").click()
- >>> team_admin_browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/director-2004'
-
- >>> 'Voting has closed' in team_admin_browser.contents
- True
-
- >>> team_admin_browser.getLink('Change details').click()
- >>> print extract_text(
- ... find_tag_by_id(team_admin_browser.contents, 'not-editable'))
- This poll can't be edited...
=== removed file 'lib/lp/registry/stories/team-polls/vote-poll.txt'
--- lib/lp/registry/stories/team-polls/vote-poll.txt 2010-10-11 17:36:14 +0000
+++ lib/lp/registry/stories/team-polls/vote-poll.txt 1970-01-01 00:00:00 +0000
@@ -1,167 +0,0 @@
-= Voting on polls =
-
-Foo Bar (a member of the ubuntu-team) wants to vote on the 'never-closes'
-poll, which is a poll with secret votes, which means he'll get a token that he
-must use to see/change his vote afterwards.
-
- >>> browser = setupBrowser(auth='Basic foo.bar@xxxxxxxxxxxxx:test')
- >>> browser.open('http://launchpad.dev/~ubuntu-team/+polls')
- >>> browser.getLink('A random poll that never closes').click()
- >>> browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote'
-
- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
- <BLANKLINE>
- ...
- <h2>Your current vote</h2>
- ...You have not yet voted in this poll...
- <h2>Vote now</h2>
- ...
-
- >>> browser.getControl('None of these options').selected = True
- >>> browser.getControl('Continue').click()
-
- >>> browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote'
-
- >>> tags = find_tags_by_class(browser.contents, "informational message")
- >>> for tag in tags:
- ... print tag.renderContents()
- Your vote has been recorded. If you want to view or change it later you
- must write down this key: ...
-
- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
- <BLANKLINE>
- ...
- <h2>Your current vote</h2>
- ...Your current vote is for <b> none of the options. </b>...
- ...
-
-Foo Bar will now vote on a poll with public votes.
-
- >>> browser.open('http://launchpad.dev/~ubuntu-team/+polls')
- >>> browser.getLink('A public poll that never closes').click()
- >>> browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes4/+vote'
-
- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
- <BLANKLINE>
- ...
- <h2>Your current vote</h2>
- ...You have not yet voted in this poll...
- <h2>Vote now</h2>
- ...
-
- >>> browser.getControl('OptionB').selected = True
- >>> browser.getControl('Continue').click()
-
- >>> browser.url
- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes4/+vote'
-
- >>> tags = find_tags_by_class(browser.contents, "informational message")
- >>> for tag in tags:
- ... print tag.renderContents()
- Your vote was stored successfully. You can come back to this page at any
- time before this poll closes to view or change your vote, if you want.
-
- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
- <BLANKLINE>
- ...
- <h2>Your current vote</h2>
- ...Your current vote is for <b>OptionB</b>...
- ...
-
-
-For convenience we provide an option for when the user doesn't want to vote
-yet.
-
- >>> team_admin_browser = setupBrowser(
- ... auth='Basic jeff.waugh@xxxxxxxxxxxxxxx:jdub')
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote')
- >>> not_yet_voted_message = 'You have not yet voted in this poll.'
- >>> not_yet_voted_message in team_admin_browser.contents
- True
-
- >>> team_admin_browser.getControl(name='newoption').value = ["donotvote"]
- >>> team_admin_browser.getControl(name='continue').click()
-
- >>> contents = team_admin_browser.contents
- >>> for tag in find_tags_by_class(contents, "informational message"):
- ... print tag.renderContents()
- You chose not to vote yet.
-
- >>> print find_tag_by_id(contents, 'your-vote').renderContents()
- <BLANKLINE>
- ...
- <h2>Your current vote</h2>
- ...You have not yet voted in this poll...
- ...
-
-
-== No permission to vote ==
-
-Only members of a given team can vote on that team's polls. Other users can't,
-even if they guess the URL for the voting page.
-
- >>> non_member_browser = setupBrowser(
- ... auth='Basic test@xxxxxxxxxxxxx:test')
- >>> non_member_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote')
- >>> for tag in find_tags_by_class(
- ... non_member_browser.contents, "informational message"):
- ... print tag.renderContents()
- You can’t vote in this poll because you’re not a member
- of Ubuntu Team.
-
-
-== Closed polls ==
-
-It's not possible to vote on closed polls, even if we manually craft the URL.
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/leader-2004')
- >>> print find_tag_by_id(
- ... team_admin_browser.contents, 'maincontent').renderContents()
- <BLANKLINE>
- ...
- <h2>Voting has closed</h2>
- ...
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/leader-2004/+vote')
- >>> print find_tag_by_id(
- ... team_admin_browser.contents, 'maincontent').renderContents()
- <BLANKLINE>
- ...
- <p class="informational message">
- This poll is already closed.
- </p>
- ...
-
- >>> team_admin_browser.getControl(name='continue')
- Traceback (most recent call last):
- ...
- LookupError: name 'continue'
-
-The same is true for condorcet polls too.
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/director-2004')
- >>> print find_tag_by_id(
- ... team_admin_browser.contents, 'maincontent').renderContents()
- <BLANKLINE>
- ...
- <h2>Voting has closed</h2>
- ...
-
- >>> team_admin_browser.getControl(name='continue')
- Traceback (most recent call last):
- ...
- LookupError: name 'continue'
-
- >>> team_admin_browser.open(
- ... 'http://launchpad.dev/~ubuntu-team/+poll/director-2004/+vote')
- >>> for message in get_feedback_messages(team_admin_browser.contents):
- ... print message
- This poll is already closed.
=== removed file 'lib/lp/registry/stories/team-polls/xx-poll-condorcet-voting.txt'
--- lib/lp/registry/stories/team-polls/xx-poll-condorcet-voting.txt 2010-10-11 17:36:14 +0000
+++ lib/lp/registry/stories/team-polls/xx-poll-condorcet-voting.txt 1970-01-01 00:00:00 +0000
@@ -1,229 +0,0 @@
-# XXX Guilherme Salgado, 2006-01-19:
-# Merge this test with team-polls/xx-votepoll.txt
-
- Go to a condorcet-style poll (which is still open) and check that apart
- from seeing our vote we can also change it.
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/never-closes2 HTTP/1.1
- ... Accept-Language: en-us,en;q=0.5
- ... Authorization: Basic foo.bar@xxxxxxxxxxxxx:test
- ... """)
- HTTP/1.1 303 See Other
- ...
- Location: http://localhost/~ubuntu-team/+poll/never-closes2/+vote
- ...
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
- ... Authorization: Basic foo.bar@xxxxxxxxxxxxx:test
- ... """)
- HTTP/1.1 200 Ok
- ...
- ...You must enter your vote key...
- ...This is a secret poll...
- ...your vote is identified only by the key you...
- ...were given when you voted. To view or change your vote you must enter...
- ...your key:...
- ...
-
-
- If a non-member (Sample Person) guesses the voting URL and tries to vote,
- he won't be allowed.
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
- ... Authorization: Basic dGVzdEBjYW5vbmljYWwuY29tOnRlc3Q=
- ... """)
- HTTP/1.1 200 Ok
- ...You can’t vote in this poll because you’re not...
- ...a member of Ubuntu Team...
-
-
- By providing the token we will be able to see our current vote.
-
- >>> print http(r"""
- ... POST /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
- ... Authorization: Basic foo.bar@xxxxxxxxxxxxx:test
- ... Content-Type: application/x-www-form-urlencoded
- ...
- ... token=xn9FDCTp4m&showvote=Show+My+Vote&option_12=&option_13=&option_14=&option_15=""")
- HTTP/1.1 200 Ok
- ...
- <p>Your current vote is as follows:</p>
- <p>
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>1</b>.
- Option 1
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>2</b>.
- Option 2
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>3</b>.
- Option 4
- <BLANKLINE>
- </p>
- ...
-
-
- It's also possible to change the vote, if wanted.
-
- >>> print http(r"""
- ... POST /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
- ... Authorization: Basic foo.bar@xxxxxxxxxxxxx:test
- ... Content-Type: application/x-www-form-urlencoded
- ...
- ... token=xn9FDCTp4m&option_12=2&option_13=3&option_14=4&option_15=1&changevote=Change+Vote""")
- HTTP/1.1 200 Ok
- ...
- ...Your vote was changed successfully.</p>
- ...
- <p>Your current vote is as follows:</p>
- <p>
- <BLANKLINE>
- <b>1</b>.
- Option 4
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>2</b>.
- Option 1
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>3</b>.
- Option 2
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>4</b>.
- Option 3
- <BLANKLINE>
- </p>
- ...
-
-
- Now we go to another poll in which name16 voted. But this time it's a public
- one, so there's no need to provide the token to see the current vote.
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/never-closes3 HTTP/1.1
- ... Authorization: Basic foo.bar@xxxxxxxxxxxxx:test
- ... """)
- HTTP/1.1 303 See Other
- ...
- Location: http://localhost/~ubuntu-team/+poll/never-closes3/+vote
- ...
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/never-closes3/+vote HTTP/1.1
- ... Authorization: Basic foo.bar@xxxxxxxxxxxxx:test
- ... """)
- HTTP/1.1 200 Ok
- ...
- <p>Your current vote is as follows:</p>
- <p>
- <BLANKLINE>
- <b>1</b>.
- Option 1
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>2</b>.
- Option 2
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>3</b>.
- Option 3
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>4</b>.
- Option 4
- <BLANKLINE>
- </p>
- ...
-
-
- Now we change the vote and we see the new vote displayed as our current
- vote.
-
- >>> print http(r"""
- ... POST /~ubuntu-team/+poll/never-closes3/+vote HTTP/1.1
- ... Authorization: Basic foo.bar@xxxxxxxxxxxxx:test
- ... Content-Type: application/x-www-form-urlencoded
- ...
- ... option_16=4&option_17=2&option_18=1&option_19=3&changevote=Change+Vote""")
- HTTP/1.1 200 Ok
- ...
- <p>Your current vote is as follows:</p>
- <p>
- <BLANKLINE>
- <b>1</b>.
- Option 3
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>2</b>.
- Option 2
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>3</b>.
- Option 4
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>4</b>.
- Option 1
- <BLANKLINE>
- </p>
- ...
-
-
- Logged in as mark@xxxxxxxxxxx (which is a member of ubuntu-team), go to a public
- condorcet-style poll that's still open and get redirected to a page where
- it's possible to vote (and see the current vote).
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/never-closes3 HTTP/1.1
- ... Authorization: Basic mark@xxxxxxxxxxx:test
- ... """)
- HTTP/1.1 303 See Other
- ...
- Location: http://localhost/~ubuntu-team/+poll/never-closes3/+vote
- ...
-
-
- And here we'll see the form which says you haven't voted yet and allows you
- to vote.
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/never-closes3/+vote HTTP/1.1
- ... Authorization: Basic mark@xxxxxxxxxxx:test
- ... """)
- HTTP/1.1 200 Ok
- ...
- ...Your current vote...
- ...You have not yet voted in this poll...
- ...Rank options in order...
- ...
=== removed file 'lib/lp/registry/stories/team-polls/xx-poll-confirm-vote.txt'
--- lib/lp/registry/stories/team-polls/xx-poll-confirm-vote.txt 2009-08-21 18:46:34 +0000
+++ lib/lp/registry/stories/team-polls/xx-poll-confirm-vote.txt 1970-01-01 00:00:00 +0000
@@ -1,89 +0,0 @@
- Logged in as 'jdub' (which voted in the director-2004 poll), let's see the
- results of the director-2004 poll.
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/director-2004 HTTP/1.1
- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
- ... """)
- HTTP/1.1 200 Ok
- ...
- ...2004 Director's Elections...
- ...
- ...This was a secret poll: your vote is identified only by the key...
- ...you were given when you voted. To view your vote you must enter...
- ...your key:...
- ...Results...
- ...This is the pairwise matrix for this poll...
- ...
-
-
- Now let's see if jdub's vote was stored correctly, by entering the token he
- got when voting.
-
- >>> print http(r"""
- ... POST /~ubuntu-team/+poll/director-2004 HTTP/1.1
- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
- ... Content-Type: application/x-www-form-urlencoded
- ...
- ... token=9WjxQq2V9p&showvote=Show+My+Vote""")
- HTTP/1.1 200 Ok
- ...
- <p>Your vote was as follows:</p>
- <p>
- <BLANKLINE>
- <b>1</b>.
- D
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>2</b>.
- B
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>3</b>.
- A
- <BLANKLINE>
- </p>
- <p>
- <BLANKLINE>
- <b>3</b>.
- C
- <BLANKLINE>
- </p>
- ...
-
-
- Now we'll see the results of the leader-2004 poll, in which jdub also
- voted.
-
- >>> print http(r"""
- ... GET /~ubuntu-team/+poll/leader-2004 HTTP/1.1
- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
- ... """)
- HTTP/1.1 200 Ok
- ...
- ...2004 Leader's Elections...
- ...
- ...This was a secret poll: your vote is identified only by the key...
- ...you were given when you voted. To view your vote you must enter...
- ...your key:...
- ...
-
-
- And now we confirm his vote on this poll too.
-
- >>> print http(r"""
- ... POST /~ubuntu-team/+poll/leader-2004 HTTP/1.1
- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
- ... Content-Type: application/x-www-form-urlencoded
- ...
- ... token=W7gR5mjNrX&showvote=Show+My+Vote""")
- HTTP/1.1 200 Ok
- ...
- <p>Your vote was for
- <BLANKLINE>
- <b>Jack Crawford</b></p>
- ...
=== removed file 'lib/lp/registry/stories/team-polls/xx-poll-results.txt'
--- lib/lp/registry/stories/team-polls/xx-poll-results.txt 2009-11-15 18:21:10 +0000
+++ lib/lp/registry/stories/team-polls/xx-poll-results.txt 1970-01-01 00:00:00 +0000
@@ -1,69 +0,0 @@
-First we check all polls of 'ubuntu-team'.
-
- >>> anon_browser.open("http://launchpad.dev/~ubuntu-team")
- >>> anon_browser.getLink('Show polls').click()
- >>> print find_main_content(anon_browser.contents)
- <...
- ...Current polls...
- ...A random poll that never closes...
- ...A second random poll that never closes...
- ...A third random poll that never closes...
- ...Closed polls...
- ...2004 Director's Elections...
- ...2004 Leader's Elections...
-
-
- Check the results of a closed simple-style poll.
-
- >>> anon_browser.open("http://launchpad.dev/~ubuntu-team/+poll/leader-2004")
- >>> print find_main_content(anon_browser.contents)
- <...
- ...Who's going to be the next leader?...
- ...Results...
- ...
- <td>
- Francis Dolarhyde
- <BLANKLINE>
- </td>
- <td>1</td>
- ...
- <td>
- Jack Crawford
- <BLANKLINE>
- </td>
- <td>1</td>
- ...
- <td>
- Will Graham
- <BLANKLINE>
- </td>
- <td>2</td>
- ...
-
-
- Check the results of a closed condorcet-style poll.
-
- >>> anon_browser.open("http://launchpad.dev/~ubuntu-team/+poll/director-2004")
- >>> print find_main_content(anon_browser.contents)
- <...
- ...Who's going to be the next director?...
- ...Results...
- ...
- ...A...
- ...2...
- ...2...
- ...2...
- ...B...
- ...2...
- ...2...
- ...2...
- ...C...
- ...1...
- ...1...
- ...1...
- ...D...
- ...2...
- ...1...
- ...2...
- ...
-
=== modified file 'lib/lp/registry/stories/team/xx-team-home.txt'
--- lib/lp/registry/stories/team/xx-team-home.txt 2010-04-22 17:18:29 +0000
+++ lib/lp/registry/stories/team/xx-team-home.txt 2010-12-15 22:26:18 +0000
@@ -1,4 +1,5 @@
-= A team's home page =
+A team's home page
+==================
The home page of a public team is visible to everyone.
@@ -59,17 +60,6 @@
Languages:
English
-The polls portlet is only shown if current polls exist.
-
- >>> print extract_text(find_tag_by_id(browser.contents, 'polls'))
- Polls
- A random poll that never closes...
- Show polls
-
- >>> browser.open('http://launchpad.dev/~launchpad')
- >>> print find_tag_by_id(browser.contents, 'polls')
- None
-
The subteam-of portlet is not shown if the team is not a subteam.
>>> browser.open('http://launchpad.dev/~ubuntu-team')
@@ -190,7 +180,8 @@
...
-== Team admins ==
+Team admins
+-----------
Team owners and admins can see a link to approve and decline applicants.
@@ -207,7 +198,8 @@
<Link text='Approve or decline members' url='.../+editproposedmembers'>
-== Non members ==
+Non members
+-----------
No Privileges Person is not a member of the Ubuntu team.
@@ -220,7 +212,8 @@
He can see the contact address, and the link explains the email
will actually go to the team's administrators.
- >>> print extract_text(find_tag_by_id(user_browser.contents, 'contact-email'))
+ >>> print extract_text(
+ ... find_tag_by_id(user_browser.contents, 'contact-email'))
Email:
support@xxxxxxxxxx
>>> content = find_tag_by_id(user_browser.contents, 'contact-user')
=== modified file 'lib/lp/registry/templates/team-index.pt'
--- lib/lp/registry/templates/team-index.pt 2010-10-10 21:54:16 +0000
+++ lib/lp/registry/templates/team-index.pt 2010-12-15 22:26:18 +0000
@@ -35,7 +35,6 @@
</metal:contact>
<tal:menu replace="structure view/@@+global-actions" />
- <tal:polls replace="structure context/@@+portlet-polls" />
</div>
=== modified file 'lib/lp/registry/templates/team-polls.pt'
--- lib/lp/registry/templates/team-polls.pt 2009-11-20 05:40:25 +0000
+++ lib/lp/registry/templates/team-polls.pt 2010-12-15 22:26:18 +0000
@@ -8,81 +8,17 @@
>
<body>
- <metal:heading fill-slot="heading">
- <h1>Polls for <span tal:replace="context/displayname" /></h1>
- </metal:heading>
-
<div metal:fill-slot="main">
- <h2>Current polls</h2>
+ <h1>Polls no longer supported</h1>
- <p tal:condition="not: view/has_current_polls">
- This team has no open polls nor polls that are not yet opened.
+ <p>
+ Conducting polls is no longer a feature in Launchpad. We have archived the
+ data from all previously conducted polls, which can be found as a comma-separated values file at
+ <a href="http://dev.launchpad.net/PollFeatureRemoved">http://dev.launchpad.net/PollFeatureRemoved</a>.
</p>
- <ul tal:condition="view/has_current_polls">
- <li tal:repeat="poll view/openpolls">
- <a tal:attributes="href poll/fmt:url">
- <span tal:replace="poll/title" />
- </a> - closes
- <span
- tal:attributes="title poll/datecloses/fmt:datetime"
- tal:content="poll/datecloses/fmt:displaydate" />.
-
- <tal:block define="user request/lp:person" condition="user">
- <tal:block condition="python: poll.personVoted(user)">
- You have
- <span tal:replace="poll/closesIn/fmt:approximateduration" />
- to change your vote if you wish.
- </tal:block>
-
- <tal:block condition="python: not poll.personVoted(user)">
- You have
- <span tal:replace="poll/closesIn/fmt:approximateduration" />
- left to vote in this poll.
- </tal:block>
- </tal:block>
-
- </li>
-
- <li tal:repeat="poll view/notyetopenedpolls">
- <a tal:attributes="href poll/fmt:url">
- <span tal:replace="poll/title" />
- </a> - opens
- <span
- tal:attributes="title poll/dateopens/fmt:datetime"
- tal:content="poll/dateopens/fmt:displaydate" />
- </li>
- </ul>
-
- <tal:block condition="view/closedpolls" >
- <h2>Closed polls</h2>
-
- <ul>
- <li tal:repeat="poll view/closedpolls">
- <a tal:attributes="href poll/fmt:url">
- <span tal:replace="poll/title" />
- </a> - closed
- <span
- tal:attributes="title poll/datecloses/fmt:datetime"
- tal:content="poll/datecloses/fmt:displaydate" />
- </li>
- </ul>
- </tal:block>
-
- <br />
- <tal:block tal:condition="request/lp:person">
- <ul tal:condition="context/required:launchpad.Edit">
- <li><a class="sprite add" href="+newpoll">Set up a new poll</a></li>
- </ul>
- </tal:block>
-
- <tal:block tal:condition="not: request/lp:person">
- <a href="+login">Log in as an admin to set up a new poll</a>
- </tal:block>
-
</div>
</body>
</html>
-
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-12-14 05:43:47 +0000
+++ lib/lp/testing/factory.py 2010-12-15 22:26:18 +0000
@@ -207,11 +207,6 @@
TeamSubscriptionPolicy,
)
from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.registry.interfaces.poll import (
- IPollSet,
- PollAlgorithm,
- PollSecrecy,
- )
from lp.registry.interfaces.product import (
IProductSet,
License,
@@ -729,16 +724,6 @@
naked_team.addMember(member, owner)
return team
- def makePoll(self, team, name, title, proposition,
- poll_type=PollAlgorithm.SIMPLE):
- """Create a new poll which starts tomorrow and lasts for a week."""
- dateopens = datetime.now(pytz.UTC) + timedelta(days=1)
- datecloses = dateopens + timedelta(days=7)
- return getUtility(IPollSet).new(
- team, name, title, proposition, dateopens, datecloses,
- PollSecrecy.SECRET, allowspoilt=True,
- poll_type=poll_type)
-
def makeTranslationGroup(self, owner=None, name=None, title=None,
summary=None, url=None):
"""Create a new, arbitrary `TranslationGroup`."""