Merge lp:~bac/launchpad/bug-689719 into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 12091
Proposed branch: lp:~bac/launchpad/bug-689719
Merge into: lp:launchpad
Diff against target: 4702 lines (+51/-4129)
43 files modified
lib/canonical/launchpad/browser/__init__.py (+0/-3)
lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt (+0/-1)
lib/canonical/launchpad/pagetitles.py (+0/-14)
lib/canonical/launchpad/security.py (+0/-25)
lib/canonical/launchpad/tests/test_poll.py (+0/-34)
lib/lp/registry/adapters.py (+2/-65)
lib/lp/registry/browser/configure.zcml (+0/-81)
lib/lp/registry/browser/person.py (+1/-50)
lib/lp/registry/browser/poll.py (+0/-469)
lib/lp/registry/browser/tests/poll-views.txt (+0/-134)
lib/lp/registry/browser/tests/poll-views_0.txt (+0/-92)
lib/lp/registry/browser/tests/test_breadcrumbs.py (+0/-20)
lib/lp/registry/browser/tests/test_poll.py (+0/-38)
lib/lp/registry/configure.zcml (+0/-103)
lib/lp/registry/doc/person-merge.txt (+26/-32)
lib/lp/registry/doc/poll-preconditions.txt (+0/-74)
lib/lp/registry/doc/poll.txt (+0/-140)
lib/lp/registry/doc/team-nav-menus.txt (+0/-3)
lib/lp/registry/interfaces/poll.py (+0/-500)
lib/lp/registry/model/person.py (+7/-3)
lib/lp/registry/model/poll.py (+0/-440)
lib/lp/registry/stories/team-polls/create-poll-options.txt (+0/-85)
lib/lp/registry/stories/team-polls/create-polls.txt (+0/-163)
lib/lp/registry/stories/team-polls/edit-options.txt (+0/-59)
lib/lp/registry/stories/team-polls/edit-poll.txt (+0/-97)
lib/lp/registry/stories/team-polls/vote-poll.txt (+0/-167)
lib/lp/registry/stories/team-polls/xx-poll-condorcet-voting.txt (+0/-229)
lib/lp/registry/stories/team-polls/xx-poll-confirm-vote.txt (+0/-89)
lib/lp/registry/stories/team-polls/xx-poll-results.txt (+0/-69)
lib/lp/registry/stories/team/xx-team-home.txt (+8/-15)
lib/lp/registry/templates/poll-edit.pt (+0/-34)
lib/lp/registry/templates/poll-index.pt (+0/-207)
lib/lp/registry/templates/poll-newoption.pt (+0/-36)
lib/lp/registry/templates/poll-portlet-details.pt (+0/-38)
lib/lp/registry/templates/poll-portlet-options.pt (+0/-46)
lib/lp/registry/templates/poll-vote-condorcet.pt (+0/-130)
lib/lp/registry/templates/poll-vote-simple.pt (+0/-141)
lib/lp/registry/templates/polloption-edit.pt (+0/-37)
lib/lp/registry/templates/team-index.pt (+0/-1)
lib/lp/registry/templates/team-newpoll.pt (+0/-25)
lib/lp/registry/templates/team-polls.pt (+7/-69)
lib/lp/registry/templates/team-portlet-polls.pt (+0/-56)
lib/lp/testing/factory.py (+0/-15)
To merge this branch: bzr merge lp:~bac/launchpad/bug-689719
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+43840@code.launchpad.net

Commit message

[r=sinzui][ui=none][bug=689719] Remove the Polls feature from Launchpad

Description of the change

= 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

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

We talked on IRC and agreed that "Conducting polls is no longer a feature in Launchpad" is awkward. We will use "Launchpad no longer support team polls".

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/__init__.py'
2--- lib/canonical/launchpad/browser/__init__.py 2010-10-31 20:18:45 +0000
3+++ lib/canonical/launchpad/browser/__init__.py 2010-12-16 14:48:27 +0000
4@@ -48,7 +48,6 @@
5 from lp.registry.browser.mailinglists import *
6 from lp.registry.browser.objectreassignment import *
7 from lp.registry.browser.peoplemerge import *
8-from lp.registry.browser.poll import *
9 from lp.registry.browser.team import *
10 from lp.registry.browser.teammembership import *
11 # XXX flacoste 2009/03/18 We should use specific imports instead of
12@@ -63,5 +62,3 @@
13 from lp.soyuz.browser.publishing import *
14 from lp.soyuz.browser.queue import *
15 from lp.soyuz.browser.sourcepackagerelease import *
16-
17-
18
19=== modified file 'lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt'
20--- lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt 2010-12-13 18:04:24 +0000
21+++ lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt 2010-12-16 14:48:27 +0000
22@@ -369,7 +369,6 @@
23 >>> check("/~name18/+imports", host='translations.launchpad.dev')
24 >>> check("/~name18/+teamlist")
25 >>> check("/~name18/+polls")
26->>> check("/~name18/+newpoll", auth=True)
27 >>> check("/~name16/+rdf")
28 >>> check("/~name18/+rdf")
29
30
31=== modified file 'lib/canonical/launchpad/pagetitles.py'
32--- lib/canonical/launchpad/pagetitles.py 2010-11-08 12:52:43 +0000
33+++ lib/canonical/launchpad/pagetitles.py 2010-12-16 14:48:27 +0000
34@@ -285,20 +285,6 @@
35 person_translations_to_review = ContextDisplayName(
36 'Translations for review by %s')
37
38-poll_edit = ContextTitle(smartquote('Edit poll "%s"'))
39-
40-poll_index = ContextTitle(smartquote('Poll: "%s"'))
41-
42-poll_newoption = ContextTitle(smartquote('New option for poll "%s"'))
43-
44-def polloption_edit(context, view):
45- """Return the page title to edit a poll's option."""
46- return 'Edit option: %s' % context.title
47-
48-poll_vote_condorcet = ContextTitle(smartquote('Vote in poll "%s"'))
49-
50-poll_vote_simple = ContextTitle(smartquote('Vote in poll "%s"'))
51-
52 product_cvereport = ContextTitle('CVE reports for %s')
53
54 product_index = ContextTitle('%s in Launchpad')
55
56=== modified file 'lib/canonical/launchpad/security.py'
57--- lib/canonical/launchpad/security.py 2010-12-14 00:03:18 +0000
58+++ lib/canonical/launchpad/security.py 2010-12-16 14:48:27 +0000
59@@ -130,11 +130,6 @@
60 PersonVisibility,
61 )
62 from lp.registry.interfaces.pillar import IPillar
63-from lp.registry.interfaces.poll import (
64- IPoll,
65- IPollOption,
66- IPollSubset,
67- )
68 from lp.registry.interfaces.product import (
69 IProduct,
70 IProductSet,
71@@ -858,26 +853,6 @@
72 return False
73
74
75-class EditPollByTeamOwnerOrTeamAdminsOrAdmins(
76- EditTeamMembershipByTeamOwnerOrTeamAdminsOrAdmins):
77- permission = 'launchpad.Edit'
78- usedfor = IPoll
79-
80-
81-class EditPollSubsetByTeamOwnerOrTeamAdminsOrAdmins(
82- EditPollByTeamOwnerOrTeamAdminsOrAdmins):
83- permission = 'launchpad.Edit'
84- usedfor = IPollSubset
85-
86-
87-class EditPollOptionByTeamOwnerOrTeamAdminsOrAdmins(AuthorizationBase):
88- permission = 'launchpad.Edit'
89- usedfor = IPollOption
90-
91- def checkAuthenticated(self, user):
92- return can_edit_team(self.obj.poll.team, user)
93-
94-
95 class AdminDistribution(AdminByAdminsTeam):
96 """Soyuz involves huge chunks of data in the archive and librarian,
97 so for the moment we are locking down admin and edit on distributions
98
99=== removed file 'lib/canonical/launchpad/tests/test_poll.py'
100--- lib/canonical/launchpad/tests/test_poll.py 2010-10-04 19:50:45 +0000
101+++ lib/canonical/launchpad/tests/test_poll.py 1970-01-01 00:00:00 +0000
102@@ -1,34 +0,0 @@
103-# Copyright 2009 Canonical Ltd. This software is licensed under the
104-# GNU Affero General Public License version 3 (see the file LICENSE).
105-
106-from datetime import (
107- datetime,
108- timedelta,
109- )
110-import unittest
111-
112-import pytz
113-
114-from canonical.launchpad.ftests import login
115-from canonical.testing.layers import LaunchpadFunctionalLayer
116-from lp.testing import TestCaseWithFactory
117-
118-
119-class TestPoll(TestCaseWithFactory):
120- layer = LaunchpadFunctionalLayer
121-
122- def test_getWinners_handle_polls_with_only_spoilt_votes(self):
123- login('mark@example.com')
124- owner = self.factory.makePerson()
125- team = self.factory.makeTeam(owner)
126- poll = self.factory.makePoll(team, 'name', 'title', 'proposition')
127- # Force opening of poll so that we can vote.
128- poll.dateopens = datetime.now(pytz.UTC) - timedelta(minutes=2)
129- poll.storeSimpleVote(owner, None)
130- # Force closing of the poll so that we can call getWinners().
131- poll.datecloses = datetime.now(pytz.UTC)
132- self.failUnless(poll.getWinners() is None, poll.getWinners())
133-
134-
135-def test_suite():
136- return unittest.TestLoader().loadTestsFromName(__name__)
137
138=== modified file 'lib/lp/registry/adapters.py'
139--- lib/lp/registry/adapters.py 2010-09-21 13:39:17 +0000
140+++ lib/lp/registry/adapters.py 2010-12-16 14:48:27 +0000
141@@ -6,24 +6,15 @@
142 __metaclass__ = type
143
144 __all__ = [
145- 'distroseries_to_launchpadusage',
146- 'distroseries_to_serviceusage',
147- 'PollSubset',
148+ 'distroseries_to_distribution',
149+ 'person_from_principal',
150 'productseries_to_product',
151 ]
152
153
154-from zope.component import getUtility
155 from zope.component.interfaces import ComponentLookupError
156-from zope.interface import implements
157
158 from canonical.launchpad.webapp.interfaces import ILaunchpadPrincipal
159-from lp.registry.interfaces.poll import (
160- IPollSet,
161- IPollSubset,
162- PollAlgorithm,
163- PollStatus,
164- )
165
166
167 def distroseries_to_distribution(distroseries):
168@@ -51,60 +42,6 @@
169 raise ComponentLookupError
170
171
172-class PollSubset:
173- """Adapt an `IPoll` to an `IPollSubset`."""
174- implements(IPollSubset)
175-
176- title = 'Team polls'
177-
178- def __init__(self, team=None):
179- self.team = team
180-
181- def new(self, name, title, proposition, dateopens, datecloses,
182- secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
183- """See IPollSubset."""
184- assert self.team is not None, (
185- 'team cannot be None to call this method.')
186- return getUtility(IPollSet).new(
187- self.team, name, title, proposition, dateopens,
188- datecloses, secrecy, allowspoilt, poll_type)
189-
190- def getByName(self, name, default=None):
191- """See IPollSubset."""
192- assert self.team is not None, (
193- 'team cannot be None to call this method.')
194- pollset = getUtility(IPollSet)
195- return pollset.getByTeamAndName(self.team, name, default)
196-
197- def getAll(self):
198- """See IPollSubset."""
199- assert self.team is not None, (
200- 'team cannot be None to call this method.')
201- return getUtility(IPollSet).selectByTeam(self.team)
202-
203- def getOpenPolls(self, when=None):
204- """See IPollSubset."""
205- assert self.team is not None, (
206- 'team cannot be None to call this method.')
207- return getUtility(IPollSet).selectByTeam(
208- self.team, [PollStatus.OPEN], orderBy='datecloses', when=when)
209-
210- def getClosedPolls(self, when=None):
211- """See IPollSubset."""
212- assert self.team is not None, (
213- 'team cannot be None to call this method.')
214- return getUtility(IPollSet).selectByTeam(
215- self.team, [PollStatus.CLOSED], orderBy='datecloses', when=when)
216-
217- def getNotYetOpenedPolls(self, when=None):
218- """See IPollSubset."""
219- assert self.team is not None, (
220- 'team cannot be None to call this method.')
221- return getUtility(IPollSet).selectByTeam(
222- self.team, [PollStatus.NOT_YET_OPENED],
223- orderBy='dateopens', when=when)
224-
225-
226 def productseries_to_product(productseries):
227 """Adapts `IProductSeries` object to `IProduct`.
228
229
230=== modified file 'lib/lp/registry/browser/configure.zcml'
231--- lib/lp/registry/browser/configure.zcml 2010-12-10 23:36:06 +0000
232+++ lib/lp/registry/browser/configure.zcml 2010-12-16 14:48:27 +0000
233@@ -600,75 +600,6 @@
234 name="karmacontext-macros"
235 template="../templates/karmacontext-macros.pt"/>
236 </facet>
237- <facet
238- facet="overview">
239- <browser:menus
240- module="lp.registry.browser.poll"
241- classes="
242- PollOverviewMenu
243- PollActionNavigationMenu
244- PollEditNavigationMenu"/>
245- <browser:defaultView
246- for="lp.registry.interfaces.poll.IPoll"
247- name="+index"/>
248- <browser:navigation
249- module="lp.registry.browser.poll"
250- classes="
251- PollNavigation"/>
252- <browser:url
253- for="lp.registry.interfaces.poll.IPoll"
254- path_expression="string:+poll/${name}"
255- attribute_to_parent="team"/>
256- <browser:pages
257- for="lp.registry.interfaces.poll.IPoll"
258- permission="zope.Public"
259- class="lp.registry.browser.poll.PollView">
260- <browser:page
261- name="+index"
262- template="../templates/poll-index.pt"/>
263- </browser:pages>
264- <browser:pages
265- for="lp.registry.interfaces.poll.IPoll"
266- permission="zope.Public"
267- class="lp.registry.browser.poll.BasePollView">
268- <browser:page
269- name="+portlet-details"
270- template="../templates/poll-portlet-details.pt"/>
271- <browser:page
272- name="+portlet-options"
273- template="../templates/poll-portlet-options.pt"/>
274- </browser:pages>
275- <browser:page
276- name="+vote"
277- for="lp.registry.interfaces.poll.IPoll"
278- permission="launchpad.AnyPerson"
279- class="lp.registry.browser.poll.PollVoteView"/>
280- <browser:page
281- name="+edit"
282- for="lp.registry.interfaces.poll.IPoll"
283- class="lp.registry.browser.poll.PollEditView"
284- permission="launchpad.Edit"
285- template="../templates/poll-edit.pt"/>
286- <browser:page
287- name="+newoption"
288- for="lp.registry.interfaces.poll.IPoll"
289- class="lp.registry.browser.poll.PollOptionAddView"
290- permission="launchpad.Edit"
291- template="../templates/poll-newoption.pt"/>
292- <browser:defaultView
293- for="lp.registry.interfaces.poll.IPollOption"
294- name="+edit"/>
295- <browser:url
296- for="lp.registry.interfaces.poll.IPollOption"
297- path_expression="string:+option/${id}"
298- attribute_to_parent="poll"/>
299- <browser:page
300- name="+edit"
301- for="lp.registry.interfaces.poll.IPollOption"
302- class="lp.registry.browser.poll.PollOptionEditView"
303- permission="launchpad.Edit"
304- template="../templates/polloption-edit.pt"/>
305- </facet>
306 <browser:url
307 for="lp.registry.interfaces.announcement.IAnnouncement"
308 path_expression="string:+announcement/${id}"
309@@ -1080,12 +1011,6 @@
310 template="../templates/team-index.pt"/>
311 <browser:page
312 for="lp.registry.interfaces.person.ITeam"
313- class="lp.registry.browser.person.TeamIndexView"
314- permission="zope.Public"
315- name="+portlet-polls"
316- template="../templates/team-portlet-polls.pt"/>
317- <browser:page
318- for="lp.registry.interfaces.person.ITeam"
319 permission="zope.Public"
320 class="lp.registry.browser.team.TeamMapView"
321 name="+map"
322@@ -1198,12 +1123,6 @@
323 name="+polls"
324 template="../templates/team-polls.pt"/>
325 <browser:page
326- name="+newpoll"
327- for="lp.registry.interfaces.person.ITeam"
328- class="canonical.launchpad.browser.PollAddView"
329- permission="launchpad.Edit"
330- template="../templates/team-newpoll.pt"/>
331- <browser:page
332 name="+members"
333 for="lp.registry.interfaces.person.ITeam"
334 permission="zope.Public"
335
336=== modified file 'lib/lp/registry/browser/person.py'
337--- lib/lp/registry/browser/person.py 2010-12-10 23:10:09 +0000
338+++ lib/lp/registry/browser/person.py 2010-12-16 14:48:27 +0000
339@@ -284,10 +284,6 @@
340 )
341 from lp.registry.interfaces.personproduct import IPersonProductFactory
342 from lp.registry.interfaces.pillar import IPillarNameSet
343-from lp.registry.interfaces.poll import (
344- IPollSet,
345- IPollSubset,
346- )
347 from lp.registry.interfaces.product import IProduct
348 from lp.registry.interfaces.ssh import (
349 ISSHKeySet,
350@@ -562,10 +558,6 @@
351
352 usedfor = ITeam
353
354- @stepthrough('+poll')
355- def traverse_poll(self, name):
356- return getUtility(IPollSet).getByTeamAndName(self.context, name)
357-
358 @stepthrough('+invitation')
359 def traverse_invitation(self, name):
360 # Return the found membership regardless of its status as we know
361@@ -1334,17 +1326,6 @@
362 text = 'Show member photos'
363 return Link(target, text, icon='team')
364
365- def polls(self):
366- target = '+polls'
367- text = 'Show polls'
368- return Link(target, text, icon='info')
369-
370- @enabled_with_permission('launchpad.Edit')
371- def add_poll(self):
372- target = '+newpoll'
373- text = 'Create a poll'
374- return Link(target, text, icon='add')
375-
376 @enabled_with_permission('launchpad.Edit')
377 def editemail(self):
378 target = '+contactaddress'
379@@ -1429,8 +1410,6 @@
380 'moderate_mailing_list',
381 'editlanguages',
382 'map',
383- 'polls',
384- 'add_poll',
385 'join',
386 'leave',
387 'add_my_teams',
388@@ -1451,7 +1430,7 @@
389
390 usedfor = ITeam
391 facet = 'overview'
392- links = ['profile', 'polls', 'members', 'ppas']
393+ links = ['profile', 'members', 'ppas']
394
395
396 class TeamMembershipView(LaunchpadView):
397@@ -2941,21 +2920,6 @@
398 return ''
399
400 @cachedproperty
401- def openpolls(self):
402- assert self.context.isTeam()
403- return IPollSubset(self.context).getOpenPolls()
404-
405- @cachedproperty
406- def closedpolls(self):
407- assert self.context.isTeam()
408- return IPollSubset(self.context).getClosedPolls()
409-
410- @cachedproperty
411- def notyetopenedpolls(self):
412- assert self.context.isTeam()
413- return IPollSubset(self.context).getNotYetOpenedPolls()
414-
415- @cachedproperty
416 def contributions(self):
417 """Cache the results of getProjectsAndCategoriesContributedTo()."""
418 return self.context.getProjectsAndCategoriesContributedTo(
419@@ -3124,19 +3088,6 @@
420 else:
421 raise AssertionError('Unknown group to contact.')
422
423- @property
424- def should_show_polls_portlet(self):
425- menu = TeamOverviewMenu(self.context)
426- return (
427- self.has_current_polls or self.closedpolls
428- or menu.add_poll().enabled)
429-
430- @property
431- def has_current_polls(self):
432- """Return True if this team has any non-closed polls."""
433- assert self.context.isTeam()
434- return bool(self.openpolls) or bool(self.notyetopenedpolls)
435-
436 def userIsOwner(self):
437 """Return True if the user is the owner of this Team."""
438 if self.user is None:
439
440=== removed file 'lib/lp/registry/browser/poll.py'
441--- lib/lp/registry/browser/poll.py 2010-11-23 23:22:27 +0000
442+++ lib/lp/registry/browser/poll.py 1970-01-01 00:00:00 +0000
443@@ -1,469 +0,0 @@
444-# Copyright 2009 Canonical Ltd. This software is licensed under the
445-# GNU Affero General Public License version 3 (see the file LICENSE).
446-
447-__metaclass__ = type
448-
449-__all__ = [
450- 'BasePollView',
451- 'PollAddView',
452- 'PollEditNavigationMenu',
453- 'PollEditView',
454- 'PollNavigation',
455- 'PollOptionAddView',
456- 'PollOptionEditView',
457- 'PollOverviewMenu',
458- 'PollView',
459- 'PollVoteView',
460- 'PollBreadcrumb',
461- ]
462-
463-from z3c.ptcompat import ViewPageTemplateFile
464-from zope.app.form.browser import TextWidget
465-from zope.component import getUtility
466-from zope.event import notify
467-from zope.interface import (
468- implements,
469- Interface,
470- )
471-from zope.lifecycleevent import ObjectCreatedEvent
472-
473-from canonical.launchpad.helpers import shortlist
474-from canonical.launchpad.webapp import (
475- ApplicationMenu,
476- canonical_url,
477- enabled_with_permission,
478- LaunchpadView,
479- Link,
480- Navigation,
481- NavigationMenu,
482- stepthrough,
483- )
484-from canonical.launchpad.webapp.breadcrumb import TitleBreadcrumb
485-from lp.app.browser.launchpadform import (
486- action,
487- custom_widget,
488- LaunchpadEditFormView,
489- LaunchpadFormView,
490- )
491-from lp.registry.interfaces.poll import (
492- IPoll,
493- IPollOption,
494- IPollOptionSet,
495- IPollSubset,
496- IVoteSet,
497- PollAlgorithm,
498- PollSecrecy,
499- )
500-
501-
502-class PollEditLinksMixin:
503-
504- @enabled_with_permission('launchpad.Edit')
505- def addnew(self):
506- text = 'Add new option'
507- return Link('+newoption', text, icon='add')
508-
509- @enabled_with_permission('launchpad.Edit')
510- def edit(self):
511- text = 'Change details'
512- return Link('+edit', text, icon='edit')
513-
514-
515-class PollOverviewMenu(ApplicationMenu, PollEditLinksMixin):
516- usedfor = IPoll
517- facet = 'overview'
518- links = ['addnew']
519-
520-
521-class IPollEditMenu(Interface):
522- """A marker interface for the edit navigation menu."""
523-
524-
525-class PollEditNavigationMenu(NavigationMenu, PollEditLinksMixin):
526- usedfor = IPollEditMenu
527- facet = 'overview'
528- links = ['addnew', 'edit']
529-
530-
531-class IPollActionMenu(Interface):
532- """A marker interface for the action menu."""
533-
534-
535-class PollActionNavigationMenu(PollEditNavigationMenu):
536- usedfor = IPollActionMenu
537- links = ['edit']
538-
539-
540-class PollNavigation(Navigation):
541-
542- usedfor = IPoll
543-
544- @stepthrough('+option')
545- def traverse_option(self, name):
546- return getUtility(IPollOptionSet).getByPollAndId(
547- self.context, int(name))
548-
549-
550-class BasePollView(LaunchpadView):
551- """A base view class to be used in other poll views."""
552-
553- token = None
554- gotTokenAndVotes = False
555- feedback = ""
556-
557- def setUpTokenAndVotes(self):
558- """Set up the token and votes to be displayed."""
559- if not self.userVoted():
560- return
561-
562- # For secret polls we can only display the votes after the token
563- # is submitted.
564- if self.request.method == 'POST' and self.isSecret():
565- self.setUpTokenAndVotesForSecretPolls()
566- elif not self.isSecret():
567- self.setUpTokenAndVotesForNonSecretPolls()
568-
569- def setUpTokenAndVotesForNonSecretPolls(self):
570- """Get the votes of the logged in user in this poll.
571-
572- Set the votes in instance variables and also set self.gotTokenAndVotes
573- to True, so the templates know they can display the vote.
574-
575- This method should be used only on non-secret polls and if the logged
576- in user has voted on this poll.
577- """
578- assert not self.isSecret() and self.userVoted()
579- votes = self.context.getVotesByPerson(self.user)
580- assert votes, (
581- "User %r hasn't voted on poll %r" % (self.user, self.context))
582- if self.isSimple():
583- # Here we have only one vote.
584- self.currentVote = votes[0]
585- self.token = self.currentVote.token
586- elif self.isCondorcet():
587- # Here we have multiple votes, and the token is the same in
588- # all of them.
589- self.currentVotes = sorted(votes, key=lambda v: v.preference)
590- self.token = self.currentVotes[0].token
591- self.gotTokenAndVotes = True
592-
593- def setUpTokenAndVotesForSecretPolls(self):
594- """Get the votes with the token provided in the form.
595-
596- Set the votes, together with the token in instance variables. Also
597- set self.gotTokenAndVotes to True, so the templates know they can
598- display the vote.
599-
600- Return True if there's any vote with the given token and the votes
601- are on this poll.
602-
603- This method should be used only on secret polls and if the logged
604- in user has voted on this poll.
605- """
606- assert self.isSecret() and self.userVoted()
607- token = self.request.form.get('token')
608- # Only overwrite self.token if the request contains a 'token'
609- # variable.
610- if token is not None:
611- self.token = token
612- votes = getUtility(IVoteSet).getByToken(self.token)
613- if not votes:
614- self.feedback = ("There's no vote associated with the token %s"
615- % self.token)
616- return False
617-
618- # All votes with a given token must be on the same poll. That means
619- # checking the poll of the first vote is enough.
620- if votes[0].poll != self.context:
621- self.feedback = ("The vote associated with the token %s is not "
622- "a vote on this poll." % self.token)
623- return False
624-
625- if self.isSimple():
626- # A simple poll has only one vote, because you can choose only one
627- # option.
628- self.currentVote = votes[0]
629- elif self.isCondorcet():
630- self.currentVotes = sorted(votes, key=lambda v: v.preference)
631- self.gotTokenAndVotes = True
632- return True
633-
634- def userCanVote(self):
635- """Return True if the user is/was eligible to vote on this poll."""
636- return (self.user and self.user.inTeam(self.context.team))
637-
638- def userVoted(self):
639- """Return True if the user voted on this poll."""
640- return (self.user and self.context.personVoted(self.user))
641-
642- def isCondorcet(self):
643- """Return True if this poll's type is Condorcet."""
644- return self.context.type == PollAlgorithm.CONDORCET
645-
646- def isSimple(self):
647- """Return True if this poll's type is Simple."""
648- return self.context.type == PollAlgorithm.SIMPLE
649-
650- def isSecret(self):
651- """Return True if this is a secret poll."""
652- return self.context.secrecy == PollSecrecy.SECRET
653-
654-
655-class PollBreadcrumb(TitleBreadcrumb):
656- """Breadcrumb for polls."""
657-
658-
659-class PollView(BasePollView):
660- """A view class to display the results of a poll."""
661- implements(IPollActionMenu)
662-
663- def initialize(self):
664- super(PollView, self).initialize()
665- request = self.request
666- if (self.userCanVote() and self.context.isOpen() and
667- self.context.getActiveOptions()):
668- vote_url = canonical_url(self.context, view_name='+vote')
669- request.response.redirect(vote_url)
670-
671- def getVotesByOption(self, option):
672- """Return the number of votes the given option received."""
673- return getUtility(IVoteSet).getVotesByOption(option)
674-
675- def getPairwiseMatrixWithHeaders(self):
676- """Return the pairwise matrix, with headers being the option's
677- names.
678- """
679- # XXX: kiko 2006-03-13:
680- # The list() call here is necessary because, lo and behold,
681- # it gives us a non-security-proxied list object! Someone come
682- # in and fix this!
683- pairwise_matrix = list(self.context.getPairwiseMatrix())
684- headers = [None]
685- for idx, option in enumerate(self.context.getAllOptions()):
686- headers.append(option.title)
687- # Get a mutable row.
688- row = list(pairwise_matrix[idx])
689- row.insert(0, option.title)
690- pairwise_matrix[idx] = row
691- pairwise_matrix.insert(0, headers)
692- return pairwise_matrix
693-
694-
695-class PollVoteView(BasePollView):
696- """A view class to where the user can vote on a poll.
697-
698- If the user already voted, the current vote is displayed and the user can
699- change it. Otherwise he can register his vote.
700- """
701-
702- default_template = ViewPageTemplateFile(
703- '../templates/poll-vote-simple.pt')
704- condorcet_template = ViewPageTemplateFile(
705- '../templates/poll-vote-condorcet.pt')
706-
707- @property
708- def template(self):
709- if self.isCondorcet():
710- return self.condorcet_template
711- else:
712- return self.default_template
713-
714- def initialize(self):
715- """Process the form, if it was submitted."""
716- super(PollVoteView, self).initialize()
717- if not self.isSecret() and self.userVoted():
718- # For non-secret polls, the user's vote is always displayed
719- self.setUpTokenAndVotesForNonSecretPolls()
720-
721- if self.request.method != 'POST':
722- return
723-
724- if self.isSecret() and self.userVoted():
725- if not self.setUpTokenAndVotesForSecretPolls():
726- # Not possible to get the votes. Probably the token was wrong.
727- return
728-
729- if 'showvote' in self.request.form:
730- # The user only wants to see the vote.
731- return
732-
733- if not self.context.isOpen():
734- self.feedback = "This poll is not open."
735- return
736-
737- if self.isSimple():
738- self.processSimpleVotingForm()
739- else:
740- self.processCondorcetVotingForm()
741-
742- # User may have voted, so we need to setup the vote to display again.
743- self.setUpTokenAndVotes()
744-
745- def processSimpleVotingForm(self):
746- """Process the simple-voting form to change a user's vote or register
747- a new one.
748-
749- This method must not be called if the poll is not open.
750- """
751- assert self.context.isOpen()
752- context = self.context
753- newoption_id = self.request.form.get('newoption')
754- if newoption_id == 'donotchange':
755- self.feedback = "Your vote was not changed."
756- return
757- elif newoption_id == 'donotvote':
758- self.feedback = "You chose not to vote yet."
759- return
760- elif newoption_id == 'none':
761- newoption = None
762- else:
763- newoption = getUtility(IPollOptionSet).getByPollAndId(
764- context, int(newoption_id))
765-
766- if self.userVoted():
767- self.currentVote.option = newoption
768- self.feedback = "Your vote was changed successfully."
769- else:
770- self.currentVote = context.storeSimpleVote(self.user, newoption)
771- self.token = self.currentVote.token
772- self.currentVote = self.currentVote
773- if self.isSecret():
774- self.feedback = (
775- "Your vote has been recorded. If you want to view or "
776- "change it later you must write down this key: %s"
777- % self.token)
778- else:
779- self.feedback = (
780- "Your vote was stored successfully. You can come back to "
781- "this page at any time before this poll closes to view "
782- "or change your vote, if you want.")
783-
784- def processCondorcetVotingForm(self):
785- """Process the condorcet-voting form to change a user's vote or
786- register a new one.
787-
788- This method must not be called if the poll is not open.
789- """
790- assert self.context.isOpen()
791- form = self.request.form
792- activeoptions = shortlist(self.context.getActiveOptions())
793- newvotes = {}
794- for option in activeoptions:
795- try:
796- preference = int(form.get('option_%d' % option.id))
797- except ValueError:
798- # XXX: Guilherme Salgado 2005-09-14:
799- # User tried to specify a value which we can't convert to
800- # an integer. Better thing to do would be to notify the user
801- # and ask him to fix it.
802- preference = None
803- newvotes[option] = preference
804-
805- if self.userVoted():
806- # This is a vote change.
807- # For now it's not possible to have votes in an inactive option,
808- # but it'll be in the future as we'll allow people to make options
809- # inactive after a poll opens.
810- assert len(activeoptions) == len(self.currentVotes)
811- for vote in self.currentVotes:
812- vote.preference = newvotes.get(vote.option)
813- self.currentVotes.sort(key=lambda v: v.preference)
814- self.feedback = "Your vote was changed successfully."
815- else:
816- # This is a new vote.
817- votes = self.context.storeCondorcetVote(self.user, newvotes)
818- self.token = votes[0].token
819- self.currentVotes = sorted(votes, key=lambda v: v.preference)
820- if self.isSecret():
821- self.feedback = (
822- "Your vote has been recorded. If you want to view or "
823- "change it later you must write down this key: %s"
824- % self.token)
825- else:
826- self.feedback = (
827- "Your vote was stored successfully. You can come back to "
828- "this page at any time before this poll closes to view "
829- "or change your vote, if you want.")
830-
831-
832-class PollAddView(LaunchpadFormView):
833- """The view class to create a new poll in a given team."""
834-
835- schema = IPoll
836- field_names = ["name", "title", "proposition", "allowspoilt", "dateopens",
837- "datecloses"]
838-
839- @property
840- def cancel_url(self):
841- """See `LaunchpadFormView`."""
842- return canonical_url(self.context)
843-
844- @action("Continue", name="continue")
845- def continue_action(self, action, data):
846- # XXX: salgado, 2008-10-08: Only secret polls can be created until we
847- # fix https://launchpad.net/bugs/80596.
848- secrecy = PollSecrecy.SECRET
849- poll = IPollSubset(self.context).new(
850- data['name'], data['title'], data['proposition'],
851- data['dateopens'], data['datecloses'], secrecy,
852- data['allowspoilt'])
853- self.next_url = canonical_url(poll)
854- notify(ObjectCreatedEvent(poll))
855-
856-
857-class PollEditView(LaunchpadEditFormView):
858-
859- implements(IPollEditMenu)
860- schema = IPoll
861- label = "Edit poll details"
862- field_names = ["name", "title", "proposition", "allowspoilt", "dateopens",
863- "datecloses"]
864-
865- @property
866- def cancel_url(self):
867- """See `LaunchpadFormView`."""
868- return canonical_url(self.context)
869-
870- @action("Save", name="save")
871- def save_action(self, action, data):
872- self.updateContextFromData(data)
873- self.next_url = canonical_url(self.context)
874-
875-
876-class PollOptionEditView(LaunchpadEditFormView):
877- """Edit one of a poll's options."""
878-
879- schema = IPollOption
880- label = "Edit option details"
881- field_names = ["name", "title"]
882- custom_widget("title", TextWidget, width=30)
883-
884- @property
885- def cancel_url(self):
886- """See `LaunchpadFormView`."""
887- return canonical_url(self.context.poll)
888-
889- @action("Save", name="save")
890- def save_action(self, action, data):
891- self.updateContextFromData(data)
892- self.next_url = canonical_url(self.context.poll)
893-
894-
895-class PollOptionAddView(LaunchpadFormView):
896- """Create a new option in a given poll."""
897-
898- schema = IPollOption
899- label = "Create new poll option"
900- field_names = ["name", "title"]
901- custom_widget("title", TextWidget, width=30)
902-
903- @property
904- def cancel_url(self):
905- """See `LaunchpadFormView`."""
906- return canonical_url(self.context)
907-
908- @action("Create", name="create")
909- def create_action(self, action, data):
910- polloption = self.context.newOption(data['name'], data['title'])
911- self.next_url = canonical_url(self.context)
912- notify(ObjectCreatedEvent(polloption))
913
914=== removed file 'lib/lp/registry/browser/tests/poll-views.txt'
915--- lib/lp/registry/browser/tests/poll-views.txt 2009-12-15 20:33:49 +0000
916+++ lib/lp/registry/browser/tests/poll-views.txt 1970-01-01 00:00:00 +0000
917@@ -1,134 +0,0 @@
918-Poll views
919-----------
920-
921-The polls portlet shows the state of current polls, and links to past
922-polls.
923-
924- >>> from canonical.launchpad.testing.pages import extract_text
925-
926- >>> user = factory.makePerson()
927- >>> team = factory.makeTeam(name='team')
928- >>> owner = team.teamowner
929-
930- >>> def create_team_view(team, name=None, principal=None):
931- ... # XRDS inheritance requires a lot of setup.
932- ... path_info = '/~%s' % team.name
933- ... server_url = 'http://launchpad.dev'
934- ... view = create_view(
935- ... team, name=name, principal=principal,
936- ... server_url=server_url, path_info=path_info)
937- ... view.initialize()
938- ... return view
939-
940-The portlet does not render any markup when there are no polls...
941-
942- >>> login_person(user)
943- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
944- >>> view.has_current_polls
945- False
946-
947- >>> view.should_show_polls_portlet
948- False
949-
950- >>> print extract_text(view.render())
951- <BLANKLINE>
952-
953-Unless the user is a team owner.
954-
955- >>> login_person(owner)
956- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
957- >>> view.has_current_polls
958- False
959-
960- >>> view.should_show_polls_portlet
961- True
962-
963- >>> print extract_text(view.render())
964- Polls
965- No current polls.
966- Show polls
967- Create a poll
968-
969-The portlet shows a link to polls to all users when there is a poll, but it
970-has not opened.
971-
972- >>> import pytz
973- >>> from datetime import datetime, timedelta
974- >>> from lp.registry.interfaces.poll import IPollSubset, PollSecrecy
975-
976- >>> open_date = datetime.now().replace(tzinfo=pytz.timezone('UTC'))
977- >>> close_date = open_date + timedelta(weeks=1)
978- >>> poll_subset = IPollSubset(team)
979- >>> poll = poll_subset.new(
980- ... 'name', 'title', 'proposition', open_date, close_date,
981- ... PollSecrecy.OPEN, False)
982-
983- >>> login_person(user)
984- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
985- >>> view.has_current_polls
986- True
987-
988- >>> view.should_show_polls_portlet
989- True
990-
991- >>> print extract_text(view.render())
992- Polls
993- Show polls
994-
995-The portlet shows more details to the poll owner.
996-
997- >>> login_person(owner)
998- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
999- >>> view.has_current_polls
1000- True
1001-
1002- >>> view.should_show_polls_portlet
1003- True
1004-
1005- >>> print extract_text(view.render())
1006- Polls
1007- title - opens in 5 hours
1008- Show polls
1009- Create a poll
1010-
1011-Current polls are listed in the portlet, the only difference between a user
1012-and an owner is the owner has a link to create more polls.
1013-
1014- >>> poll.dateopens = open_date - timedelta(weeks=2)
1015-
1016- >>> login_person(user)
1017- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
1018- >>> print extract_text(view.render())
1019- Polls
1020- title - closes on ...
1021- You have seven days left to vote in this poll.
1022- Show polls
1023-
1024- >>> login_person(owner)
1025- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
1026- >>> print extract_text(view.render())
1027- Polls
1028- title - closes on ...
1029- You have seven days left to vote in this poll.
1030- Show polls
1031- Create a poll
1032-
1033-When all the polls are closed, the portlet states the case and has a link to
1034-see the polls.
1035-
1036- >>> poll.datecloses = close_date - timedelta(weeks=2)
1037-
1038- >>> login_person(user)
1039- >>> view = create_team_view(team, name='+portlet-polls', principal=user)
1040- >>> print extract_text(view.render())
1041- Polls
1042- No current polls.
1043- Show polls
1044-
1045- >>> login_person(owner)
1046- >>> view = create_team_view(team, name='+portlet-polls', principal=owner)
1047- >>> print extract_text(view.render())
1048- Polls
1049- No current polls.
1050- Show polls
1051- Create a poll
1052
1053=== removed file 'lib/lp/registry/browser/tests/poll-views_0.txt'
1054--- lib/lp/registry/browser/tests/poll-views_0.txt 2010-10-18 22:24:59 +0000
1055+++ lib/lp/registry/browser/tests/poll-views_0.txt 1970-01-01 00:00:00 +0000
1056@@ -1,92 +0,0 @@
1057-= Poll Pages =
1058-
1059-First import some stuff and setup some things we'll use in this test.
1060-
1061- >>> from zope.component import getUtility, getMultiAdapter
1062- >>> from zope.publisher.browser import TestRequest
1063- >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
1064- >>> from lp.registry.interfaces.person import IPersonSet
1065- >>> from lp.registry.interfaces.poll import IPollSet
1066- >>> from datetime import datetime, timedelta
1067- >>> login("test@canonical.com")
1068- >>> ubuntu_team = getUtility(IPersonSet).getByName('ubuntu-team')
1069-
1070-
1071-== Creating new polls ==
1072-
1073-When creating a new poll, its start date must be at least 12h after it is
1074-created.
1075-
1076-First we attempt to create a poll which starts 11h from now. That will fail
1077-with a proper explanation of why it failed.
1078-
1079- >>> eleven_hours_from_now = datetime.now() + timedelta(hours=11)
1080- >>> eleven_hours_from_now = eleven_hours_from_now.strftime(
1081- ... '%Y-%m-%d %H:%M:%S')
1082- >>> form = {
1083- ... 'field.name': 'test-poll',
1084- ... 'field.title': 'test-poll',
1085- ... 'field.proposition': 'test-poll',
1086- ... 'field.allowspoilt': '1',
1087- ... 'field.secrecy': 'SECRET',
1088- ... 'field.dateopens': eleven_hours_from_now,
1089- ... 'field.datecloses': '2025-06-04',
1090- ... 'field.actions.continue': 'Continue'}
1091- >>> request = LaunchpadTestRequest(method='POST', form=form)
1092- >>> new_poll = getMultiAdapter((ubuntu_team, request), name="+newpoll")
1093- >>> new_poll.initialize()
1094- >>> print "\n".join(new_poll.errors)
1095- A poll cannot open less than 12 hours after it's created.
1096-
1097-Now we successfully create a poll which starts 12h from now.
1098-
1099- >>> twelve_hours_from_now = datetime.now() + timedelta(hours=12)
1100- >>> twelve_hours_from_now = twelve_hours_from_now.strftime(
1101- ... '%Y-%m-%d %H:%M:%S')
1102- >>> form['field.dateopens'] = twelve_hours_from_now
1103- >>> request = LaunchpadTestRequest(method='POST', form=form)
1104- >>> new_poll = getMultiAdapter((ubuntu_team, request), name="+newpoll")
1105- >>> new_poll.initialize()
1106- >>> new_poll.errors
1107- []
1108-
1109-
1110-== Displaying results of condorcet polls ==
1111-
1112- >>> poll = getUtility(IPollSet).getByTeamAndName(ubuntu_team, 'director-2004')
1113- >>> poll.type.title
1114- 'Condorcet Voting'
1115-
1116-Although condorcet polls are disabled now, everything is implemented and we're
1117-using a pairwise matrix to display the results. It's very trick to create this
1118-matrix on page templates, so the view provides a method wich return this
1119-matrix as a python list, with the necessary headers (the option's names).
1120-
1121- >>> poll_results = getMultiAdapter((poll, TestRequest()), name="+index")
1122- >>> for row in poll_results.getPairwiseMatrixWithHeaders():
1123- ... print row
1124- [None, u'A', u'B', u'C', u'D']
1125- [u'A', None, 2L, 2L, 2L]
1126- [u'B', 2L, None, 2L, 2L]
1127- [u'C', 1L, 1L, None, 1L]
1128- [u'D', 2L, 1L, 2L, None]
1129-
1130-== Voting on closed polls ==
1131-
1132-This is not allowed, and apart from not linking to the +vote page and not
1133-even displaying its content for a closed poll, we also have some lower
1134-level checks.
1135-
1136- >>> request = TestRequest(form={'changevote': 'Change Vote'})
1137- >>> request.method = 'POST'
1138- >>> voting_page = getMultiAdapter((poll, request), name="+vote")
1139- >>> form_processed = False
1140- >>> def form_processing():
1141- ... form_processed = True
1142- >>> voting_page.processCondorcetVotingForm = form_processing
1143- >>> voting_page.initialize()
1144-
1145- >>> form_processed
1146- False
1147- >>> voting_page.feedback
1148- 'This poll is not open.'
1149
1150=== modified file 'lib/lp/registry/browser/tests/test_breadcrumbs.py'
1151--- lib/lp/registry/browser/tests/test_breadcrumbs.py 2010-11-30 13:41:48 +0000
1152+++ lib/lp/registry/browser/tests/test_breadcrumbs.py 2010-12-16 14:48:27 +0000
1153@@ -105,26 +105,6 @@
1154 self.assertEqual(self.milestone.name, last_crumb.text)
1155
1156
1157-class TestPollBreadcrumb(BaseBreadcrumbTestCase):
1158- """Test breadcrumbs for an `IPoll`."""
1159-
1160- def setUp(self):
1161- super(TestPollBreadcrumb, self).setUp()
1162- self.team = self.factory.makeTeam(displayname="Poll Team")
1163- name = "pollo-poll"
1164- title = "Marco Pollo"
1165- proposition = "Be mine"
1166- self.poll = self.factory.makePoll(
1167- team=self.team,
1168- name=name,
1169- title=title,
1170- proposition=proposition)
1171-
1172- def test_poll(self):
1173- crumbs = self.getBreadcrumbsForObject(self.poll)
1174- last_crumb = crumbs[-1]
1175- self.assertEqual(self.poll.title, last_crumb.text)
1176-
1177 from lp.registry.interfaces.nameblacklist import INameBlacklistSet
1178
1179
1180
1181=== removed file 'lib/lp/registry/browser/tests/test_poll.py'
1182--- lib/lp/registry/browser/tests/test_poll.py 2010-10-11 17:44:05 +0000
1183+++ lib/lp/registry/browser/tests/test_poll.py 1970-01-01 00:00:00 +0000
1184@@ -1,38 +0,0 @@
1185-# Copyright 2010 Canonical Ltd. This software is licensed under the
1186-# GNU Affero General Public License version 3 (see the file LICENSE).
1187-
1188-"""Tests for IPoll views."""
1189-
1190-__metaclass__ = type
1191-
1192-import os
1193-from canonical.testing.layers import DatabaseFunctionalLayer
1194-from lp.registry.interfaces.poll import PollAlgorithm
1195-from lp.testing import TestCaseWithFactory
1196-from lp.testing.views import create_view
1197-
1198-
1199-class TestPollVoteView(TestCaseWithFactory):
1200-
1201- layer = DatabaseFunctionalLayer
1202-
1203- def setUp(self):
1204- super(TestPollVoteView, self).setUp()
1205- self.team = self.factory.makeTeam()
1206-
1207- def test_simple_poll_template(self):
1208- poll = self.factory.makePoll(
1209- self.team, 'name', 'title', 'proposition',
1210- poll_type=PollAlgorithm.SIMPLE)
1211- view = create_view(poll, name='+vote')
1212- self.assertEqual(
1213- 'poll-vote-simple.pt', os.path.basename(view.template.filename))
1214-
1215- def test_condorcet_poll_template(self):
1216- poll = self.factory.makePoll(
1217- self.team, 'name', 'title', 'proposition',
1218- poll_type=PollAlgorithm.CONDORCET)
1219- view = create_view(poll, name='+vote')
1220- self.assertEqual(
1221- 'poll-vote-condorcet.pt',
1222- os.path.basename(view.template.filename))
1223
1224=== modified file 'lib/lp/registry/configure.zcml'
1225--- lib/lp/registry/configure.zcml 2010-12-06 17:33:19 +0000
1226+++ lib/lp/registry/configure.zcml 2010-12-16 14:48:27 +0000
1227@@ -689,109 +689,6 @@
1228 interface="lp.registry.interfaces.karma.IKarmaActionSet"/>
1229 </securedutility>
1230 </facet>
1231- <facet
1232- facet="overview">
1233-
1234- <!-- Poll -->
1235-
1236- <class
1237- class="lp.registry.model.poll.Poll">
1238- <allow
1239- interface="lp.registry.interfaces.poll.IPoll"/>
1240- <require
1241- permission="launchpad.Edit"
1242- set_schema="lp.registry.interfaces.poll.IPoll"/>
1243- </class>
1244-
1245- <adapter
1246- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
1247- for="lp.registry.interfaces.poll.IPoll"
1248- factory="lp.registry.browser.poll.PollBreadcrumb"
1249- permission="zope.Public"/>
1250-
1251- <!-- PollOption -->
1252-
1253- <class
1254- class="lp.registry.model.poll.PollOption">
1255- <allow
1256- interface="lp.registry.interfaces.poll.IPollOption"/>
1257- <require
1258- permission="launchpad.Edit"
1259- set_schema="lp.registry.interfaces.poll.IPollOption"/>
1260- </class>
1261-
1262- <!-- Vote -->
1263-
1264-
1265- <!-- Can't require launchpad.Edit to set_attributes because in most cases
1266- the vote won't be associated with a person, and thus we can't check it
1267- against the logged in user. -->
1268-
1269- <class
1270- class="lp.registry.model.poll.Vote">
1271- <allow
1272- interface="lp.registry.interfaces.poll.IVote"/>
1273- <require
1274- permission="zope.Public"
1275- set_attributes="option preference"/>
1276- </class>
1277-
1278- <!-- VoteCast -->
1279-
1280- <class
1281- class="lp.registry.model.poll.VoteCast">
1282- <allow
1283- interface="lp.registry.interfaces.poll.IVoteCast"/>
1284- </class>
1285-
1286- <!-- PollSet -->
1287-
1288- <class
1289- class="lp.registry.model.poll.PollSet">
1290- <allow
1291- interface="lp.registry.interfaces.poll.IPollSet"/>
1292- </class>
1293- <securedutility
1294- class="lp.registry.model.poll.PollSet"
1295- provides="lp.registry.interfaces.poll.IPollSet">
1296- <allow
1297- interface="lp.registry.interfaces.poll.IPollSet"/>
1298- </securedutility>
1299-
1300- <!-- PollSubset -->
1301-
1302- <adapter
1303- for="lp.registry.interfaces.person.ITeam"
1304- provides="lp.registry.interfaces.poll.IPollSubset"
1305- factory="lp.registry.adapters.PollSubset"
1306- permission="zope.Public"/>
1307-
1308- <!-- PollOptionSet -->
1309-
1310- <class
1311- class="lp.registry.model.poll.PollOptionSet">
1312- <allow
1313- interface="lp.registry.interfaces.poll.IPollOptionSet"/>
1314- </class>
1315- <securedutility
1316- class="lp.registry.model.poll.PollOptionSet"
1317- provides="lp.registry.interfaces.poll.IPollOptionSet">
1318- <allow
1319- interface="lp.registry.interfaces.poll.IPollOptionSet"/>
1320- </securedutility>
1321- <securedutility
1322- class="lp.registry.model.poll.VoteSet"
1323- provides="lp.registry.interfaces.poll.IVoteSet">
1324- <allow
1325- interface="lp.registry.interfaces.poll.IVoteSet"/>
1326- </securedutility>
1327- <securedutility
1328- class="lp.registry.model.poll.VoteCastSet"
1329- provides="lp.registry.interfaces.poll.IVoteCastSet">
1330- <allow
1331- interface="lp.registry.interfaces.poll.IVoteCastSet"/>
1332- </securedutility>
1333- </facet>
1334
1335 <!-- Announcement -->
1336
1337
1338=== modified file 'lib/lp/registry/doc/person-merge.txt'
1339--- lib/lp/registry/doc/person-merge.txt 2010-12-01 23:39:05 +0000
1340+++ lib/lp/registry/doc/person-merge.txt 2010-12-16 14:48:27 +0000
1341@@ -1,4 +1,5 @@
1342-= Merging =
1343+Merging
1344+=======
1345
1346 For many reasons (i.e. a gina run) we could have duplicated accounts in
1347 Launchpad. Once a duplicated account is identified, we need to allow the user
1348@@ -19,7 +20,8 @@
1349 >>> marilize = personset.getByName('marilize')
1350
1351
1352-== Sanity checks ==
1353+Sanity checks
1354+-------------
1355
1356 We can't merge an account that still has email addresses attached to it
1357
1358@@ -29,7 +31,8 @@
1359 AssertionError: ...
1360
1361
1362-== Preparing test person for the merge ==
1363+Preparing test person for the merge
1364+-----------------------------------
1365
1366 Merging people involves updating the merged person relationships. Let's
1367 put the person we will merge into some of those.
1368@@ -57,10 +60,11 @@
1369 marilize
1370 >>> sampleperson_old_karma = sample.karma
1371
1372-Branches whose owner is being merged are uniquified by appending '-N' where N
1373-is a unique integer. We create "peoplemerge" and "peoplemerge-1" branches owned
1374-by marilize, and a "peoplemerge" and "peoplemerge-1" branches owned by 'Sample
1375-Person' to test that branch name uniquifying works.
1376+Branches whose owner is being merged are uniquified by appending '-N'
1377+where N is a unique integer. We create "peoplemerge" and
1378+"peoplemerge-1" branches owned by marilize, and a "peoplemerge" and
1379+"peoplemerge-1" branches owned by 'Sample Person' to test that branch
1380+name uniquifying works.
1381
1382 Branches with smaller IDs will be processed first, so we create "peoplemerge"
1383 first, and it will be renamed "peoplemerge-2". The extant "peoplemerge-1"
1384@@ -81,9 +85,9 @@
1385 >>> peoplemerge11 = factory.makePersonalBranch(
1386 ... name='peoplemerge-1', owner=marilize)
1387
1388-'Sample Person' is a deactivated member of the 'Ubuntu Translators' team,
1389-while marilize is an active member. After the merge, 'Sample Person' will be an
1390-active member of that team.
1391+'Sample Person' is a deactivated member of the 'Ubuntu Translators'
1392+team, while marilize is an active member. After the merge, 'Sample
1393+Person' will be an active member of that team.
1394
1395 >>> sample in ubuntu_translators.inactivemembers
1396 True
1397@@ -102,7 +106,8 @@
1398 u'Marilize Coetzee'
1399
1400
1401-== Do the merge! ==
1402+Do the merge!
1403+-------------
1404
1405 # Now we remove the only email address marilize had, so that we can merge
1406 # it. First we need to change its status, though, because we can't delete
1407@@ -119,7 +124,8 @@
1408 >>> personset.merge(marilize, sample)
1409
1410
1411-== Merge results ==
1412+Merge results
1413+-------------
1414
1415 Check that 'Sample Person' has indeed become an active member of 'Ubuntu
1416 Translators'
1417@@ -336,37 +342,30 @@
1418 loser, winner,
1419
1420
1421-== Merging teams ==
1422+Merging teams
1423+-------------
1424
1425 Merging of teams is also possible and uses the same API used for merging
1426-people. Note, though, that when merging teams, its polls will not be
1427-carried over to the remaining team. Team memberships, on the other hand,
1428-are carried over just like when merging people.
1429+people. Team memberships are carried over just like when merging people.
1430
1431 >>> from datetime import datetime, timedelta
1432 >>> import pytz
1433- >>> from lp.registry.interfaces.poll import IPollSubset, PollSecrecy
1434 >>> test_team = personset.newTeam(sample, 'test-team', 'Test team')
1435 >>> launchpad_devs = personset.getByName('launchpad')
1436 >>> ignored = launchpad_devs.addMember(
1437 ... test_team, reviewer=launchpad_devs.teamowner, force_team_add=True)
1438- >>> today = datetime.now(pytz.timezone('UTC'))
1439+ >>> today = datetime.now(pytz.UTC)
1440 >>> tomorrow = today + timedelta(days=1)
1441- >>> poll = IPollSubset(test_team).new(
1442- ... 'test-poll', 'Title', 'Proposition', today, tomorrow,
1443- ... PollSecrecy.OPEN, allowspoilt=True)
1444
1445- # test_team has a superteam, one active member and a poll.
1446+ # test_team has a superteam and one active member.
1447 >>> [team.name for team in test_team.super_teams]
1448 [u'launchpad']
1449 >>> test_team.teamowner.name
1450 u'name12'
1451 >>> [member.name for member in test_team.allmembers]
1452 [u'name12']
1453- >>> list(IPollSubset(test_team).getAll())
1454- [<Poll at ...]
1455
1456- # Landscape-developers has no super teams, two members and no polls.
1457+ # Landscape-developers has no super teams and two members.
1458 >>> landscape = personset.getByName('landscape-developers')
1459 >>> [team.name for team in landscape.super_teams]
1460 []
1461@@ -374,8 +373,6 @@
1462 u'name12'
1463 >>> [member.name for member in landscape.allmembers]
1464 [u'salgado', u'name12']
1465- >>> list(IPollSubset(landscape).getAll())
1466- []
1467
1468 Now we try to merge them, but since test_team has active members it can't be
1469 merged.
1470@@ -427,14 +424,11 @@
1471 ... test_team.retractTeamMembership(team, test_team.teamowner)
1472 >>> personset.merge(test_team, landscape)
1473
1474- # The resulting Landscape-developers no new super teams, has
1475- # no polls and its members are still the same two from before the
1476- # merge.
1477+ # The resulting Landscape-developers no new super teams and its
1478+ # members are still the same two from before the merge.
1479 >>> landscape.teamowner.name
1480 u'name12'
1481 >>> [member.name for member in landscape.allmembers]
1482 [u'salgado', u'name12']
1483 >>> [team.name for team in landscape.super_teams]
1484 []
1485- >>> list(IPollSubset(landscape).getAll())
1486- []
1487
1488=== removed file 'lib/lp/registry/doc/poll-preconditions.txt'
1489--- lib/lp/registry/doc/poll-preconditions.txt 2010-10-18 22:24:59 +0000
1490+++ lib/lp/registry/doc/poll-preconditions.txt 1970-01-01 00:00:00 +0000
1491@@ -1,74 +0,0 @@
1492-Poll preconditions
1493-==================
1494-
1495-There's some preconditions that we need to meet to vote in polls and remove
1496-options from them, Not meeting these preconditions is a programming error and
1497-should be threated as so.
1498-
1499- >>> from zope.component import getUtility
1500- >>> from canonical.database.sqlbase import flush_database_updates
1501- >>> from canonical.launchpad.ftests import login
1502- >>> from datetime import timedelta
1503- >>> from lp.registry.interfaces.person import IPersonSet
1504- >>> from lp.registry.interfaces.poll import IPollSet
1505-
1506- >>> ubuntu_team = getUtility(IPersonSet).get(17)
1507- >>> ubuntu_team_member = getUtility(IPersonSet).get(1)
1508- >>> ubuntu_team_nonmember = getUtility(IPersonSet).get(12)
1509-
1510- >>> pollset = getUtility(IPollSet)
1511- >>> director_election = pollset.getByTeamAndName(ubuntu_team,
1512- ... 'director-2004')
1513- >>> director_options = director_election.getActiveOptions()
1514- >>> leader_election = pollset.getByTeamAndName(ubuntu_team, 'leader-2004')
1515- >>> leader_options = leader_election.getActiveOptions()
1516- >>> opendate = leader_election.dateopens
1517- >>> onesec = timedelta(seconds=1)
1518-
1519-
1520-If the poll is already opened, it's impossible to remove an option.
1521-
1522- >>> leader_election.removeOption(leader_options[0], when=opendate)
1523- Traceback (most recent call last):
1524- ...
1525- AssertionError
1526-
1527-
1528-Trying to vote two times is a programming error.
1529-
1530- >>> votes = leader_election.storeSimpleVote(
1531- ... ubuntu_team_member, leader_options[0], when=opendate)
1532-
1533- >>> votes = leader_election.storeSimpleVote(
1534- ... ubuntu_team_member, leader_options[0], when=opendate)
1535- Traceback (most recent call last):
1536- ...
1537- AssertionError: Can't vote twice in the same poll
1538-
1539-
1540-It's not possible for a non-member to vote, neither to vote when the poll is
1541-not open.
1542-
1543- >>> votes = leader_election.storeSimpleVote(
1544- ... ubuntu_team_nonmember, leader_options[0], when=opendate)
1545- Traceback (most recent call last):
1546- ...
1547- AssertionError: Person ... is not a member of this poll's team.
1548-
1549- >>> votes = leader_election.storeSimpleVote(
1550- ... ubuntu_team_member, leader_options[0], when=opendate - onesec)
1551- Traceback (most recent call last):
1552- ...
1553- AssertionError: This poll is not open
1554-
1555-
1556-It's not possible to vote on an option that doesn't belong to the poll you're
1557-voting in.
1558-
1559- >>> options = {leader_options[0]: 1}
1560- >>> votes = director_election.storeCondorcetVote(
1561- ... ubuntu_team_member, options, when=opendate)
1562- Traceback (most recent call last):
1563- ...
1564- AssertionError: The option ... doesn't belong to this poll
1565-
1566
1567=== removed file 'lib/lp/registry/doc/poll.txt'
1568--- lib/lp/registry/doc/poll.txt 2010-10-19 18:44:31 +0000
1569+++ lib/lp/registry/doc/poll.txt 1970-01-01 00:00:00 +0000
1570@@ -1,140 +0,0 @@
1571-Polls
1572-=====
1573-
1574-In Launchpad, we have teams as a way to group free software
1575-developers/contributors usually based on the free software
1576-product/project/distribution they're involved in. This is the case with teams
1577-like the 'Gnome Team' and the 'Ubuntu Team'. These teams often have leaders
1578-whose ellection depends on the vote of all members, and this is one of the
1579-reasons why we teams can have polls attached to them.
1580-
1581- >>> import pytz
1582- >>> from datetime import datetime, timedelta
1583- >>> from zope.component import getUtility
1584- >>> from canonical.database.sqlbase import flush_database_updates
1585- >>> from canonical.launchpad.ftests import login
1586- >>> from lp.registry.interfaces.person import IPersonSet
1587- >>> from lp.registry.interfaces.poll import (
1588- ... IPollSubset,
1589- ... PollAlgorithm,
1590- ... PollSecrecy,
1591- ... )
1592-
1593- >>> team = getUtility(IPersonSet).getByName('ubuntu-team')
1594- >>> member = getUtility(IPersonSet).getByName('stevea')
1595- >>> member2 = getUtility(IPersonSet).getByName('jdub')
1596- >>> member3 = getUtility(IPersonSet).getByName('kamion')
1597- >>> member4 = getUtility(IPersonSet).getByName('name16')
1598- >>> member5 = getUtility(IPersonSet).getByName('limi')
1599- >>> nonmember = getUtility(IPersonSet).getByName('justdave')
1600- >>> now = datetime.now(pytz.timezone('UTC'))
1601- >>> onesec = timedelta(seconds=1)
1602-
1603-We need to login with one of the administrators of the team named
1604-'ubuntu-team' to be able to create/edit polls.
1605- >>> login('colin.watson@ubuntulinux.com')
1606-
1607-First we get an object implementing IPollSubset, which is the set of polls for
1608-a given team (in our case, the 'Ubuntu Team')
1609- >>> pollsubset = IPollSubset(team)
1610-
1611-Now we create a new poll on this team.
1612- >>> opendate = datetime(2005, 01, 01, tzinfo=pytz.timezone('UTC'))
1613- >>> closedate = opendate + timedelta(weeks=2)
1614- >>> title = "2005 Leader's Elections"
1615- >>> proposition = "Who's going to be the next leader?"
1616- >>> type = PollAlgorithm.SIMPLE
1617- >>> secrecy = PollSecrecy.SECRET
1618- >>> allowspoilt = True
1619- >>> poll = pollsubset.new("leader-election", title, proposition, opendate,
1620- ... closedate, secrecy, allowspoilt, type)
1621-
1622-Now we test the if the poll is open or closed in some specific dates.
1623- >>> poll.isOpen(when=opendate)
1624- True
1625- >>> poll.isOpen(when=opendate - onesec)
1626- False
1627- >>> poll.isOpen(when=closedate)
1628- True
1629- >>> poll.isOpen(when=closedate + onesec)
1630- False
1631-
1632-To know what polls are open/closed/not-yet-opened in a team, you can use the
1633-methods of PollSubset.
1634-Here we'll query using three different dates:
1635-
1636-Query for open polls in the exact second the poll is opening.
1637- >>> [p.name for p in pollsubset.getOpenPolls(when=opendate)]
1638- [u'leader-election', u'never-closes', u'never-closes2', u'never-closes3',
1639- u'never-closes4']
1640-
1641-Query for closed polls, one second after the poll closes.
1642- >>> [p.name for p in pollsubset.getClosedPolls(when=closedate + onesec)]
1643- [u'director-2004', u'leader-2004', u'leader-election']
1644-
1645-Query for not-yet-opened polls, one second before the poll opens.
1646- >>> [p.name for p in pollsubset.getNotYetOpenedPolls(when=opendate - onesec)]
1647- [u'leader-election', u'not-yet-opened']
1648-
1649-All polls must have a set of options for people to choose, and they'll always
1650-start with zero options. We're responsible for adding new ones.
1651- >>> poll.getAllOptions().count()
1652- 0
1653-
1654-Let's add some options to this poll, so people can start voting. :)
1655- >>> will = poll.newOption('wgraham', 'Will Graham')
1656- >>> jack = poll.newOption('jcrawford', 'Jack Crawford')
1657- >>> francis = poll.newOption('fd', 'Francis Dolarhyde')
1658- >>> [o.title for o in poll.getActiveOptions()]
1659- [u'Francis Dolarhyde', u'Jack Crawford', u'Will Graham']
1660-
1661-Now, what happens if the poll is already open and, let's say, Francis Dolarhyde
1662-is convicted and thus becomes ineligible? We'll have to mark that option as
1663-inactive, so people can't vote on it.
1664- >>> francis.active = False
1665- >>> flush_database_updates()
1666- >>> [o.title for o in poll.getActiveOptions()]
1667- [u'Jack Crawford', u'Will Graham']
1668-
1669-If the poll is not yet opened, it's possible to simply remove a given option.
1670- >>> poll.removeOption(will, when=opendate - onesec)
1671- >>> [o.title for o in poll.getAllOptions()]
1672- [u'Francis Dolarhyde', u'Jack Crawford']
1673-
1674-Any member of the team this poll refers to is eligible to vote, if the poll is
1675-still open.
1676-
1677- >>> vote1 = poll.storeSimpleVote(member, jack, when=opendate)
1678- >>> vote2 = poll.storeSimpleVote(member2, None, when=opendate)
1679-
1680-
1681-Now we create a Condorcet poll on this team and add some options to it, so
1682-people can start voting.
1683-
1684- >>> title = "2005 Director's Elections"
1685- >>> proposition = "Who's going to be the next director?"
1686- >>> type = PollAlgorithm.CONDORCET
1687- >>> secrecy = PollSecrecy.SECRET
1688- >>> allowspoilt = True
1689- >>> poll2 = pollsubset.new("director-election", title, proposition, opendate,
1690- ... closedate, secrecy, allowspoilt, type)
1691- >>> a = poll2.newOption('A', 'Option A')
1692- >>> b = poll2.newOption('B', 'Option B')
1693- >>> c = poll2.newOption('C', 'Option C')
1694- >>> d = poll2.newOption('D', 'Option D')
1695-
1696- >>> options = {b: 1, d: 2, c: 3}
1697- >>> votes = poll2.storeCondorcetVote(member, options, when=opendate)
1698- >>> options = {d: 1, b: 2}
1699- >>> votes = poll2.storeCondorcetVote(member2, options, when=opendate)
1700- >>> options = {a: 1, c: 2, b: 3}
1701- >>> votes = poll2.storeCondorcetVote(member3, options, when=opendate)
1702- >>> options = {a: 1}
1703- >>> votes = poll2.storeCondorcetVote(member4, options, when=opendate)
1704- >>> for row in poll2.getPairwiseMatrix():
1705- ... print row
1706- [None, 2L, 2L, 2L]
1707- [2L, None, 2L, 2L]
1708- [1L, 1L, None, 1L]
1709- [2L, 1L, 2L, None]
1710-
1711
1712=== modified file 'lib/lp/registry/doc/team-nav-menus.txt'
1713--- lib/lp/registry/doc/team-nav-menus.txt 2010-10-18 22:24:59 +0000
1714+++ lib/lp/registry/doc/team-nav-menus.txt 2010-12-16 14:48:27 +0000
1715@@ -31,9 +31,6 @@
1716 link members
1717 url: .../~name18/+members
1718 ...
1719- link polls
1720- url: .../~name18/+polls
1721- ...
1722 link profile
1723 url: .../~name18
1724 ...
1725
1726=== removed file 'lib/lp/registry/interfaces/poll.py'
1727--- lib/lp/registry/interfaces/poll.py 2010-08-20 20:31:18 +0000
1728+++ lib/lp/registry/interfaces/poll.py 1970-01-01 00:00:00 +0000
1729@@ -1,500 +0,0 @@
1730-# Copyright 2009 Canonical Ltd. This software is licensed under the
1731-# GNU Affero General Public License version 3 (see the file LICENSE).
1732-
1733-# pylint: disable-msg=E0211,E0213
1734-
1735-__all__ = [
1736- 'IPoll',
1737- 'IPollSet',
1738- 'IPollSubset',
1739- 'IPollOption',
1740- 'IPollOptionSet',
1741- 'IVote',
1742- 'IVoteCast',
1743- 'PollAlgorithm',
1744- 'PollSecrecy',
1745- 'PollStatus',
1746- 'IVoteSet',
1747- 'IVoteCastSet',
1748- 'OptionIsNotFromSimplePoll'
1749- ]
1750-
1751-from datetime import (
1752- datetime,
1753- timedelta,
1754- )
1755-
1756-from lazr.enum import (
1757- DBEnumeratedType,
1758- DBItem,
1759- )
1760-import pytz
1761-from zope.component import getUtility
1762-from zope.interface import (
1763- Attribute,
1764- Interface,
1765- )
1766-from zope.interface.exceptions import Invalid
1767-from zope.interface.interface import invariant
1768-from zope.schema import (
1769- Bool,
1770- Choice,
1771- Datetime,
1772- Int,
1773- Text,
1774- TextLine,
1775- )
1776-
1777-from canonical.launchpad import _
1778-from canonical.launchpad.validators.name import name_validator
1779-from lp.registry.interfaces.person import ITeam
1780-from lp.services.fields import ContentNameField
1781-
1782-
1783-class PollNameField(ContentNameField):
1784-
1785- errormessage = _("%s is already in use by another poll in this team.")
1786-
1787- @property
1788- def _content_iface(self):
1789- return IPoll
1790-
1791- def _getByName(self, name):
1792- team = ITeam(self.context, None)
1793- if team is None:
1794- team = self.context.team
1795- return getUtility(IPollSet).getByTeamAndName(team, name)
1796-
1797-
1798-class PollAlgorithm(DBEnumeratedType):
1799- """The algorithm used to accept and calculate the results."""
1800-
1801- SIMPLE = DBItem(1, """
1802- Simple Voting
1803-
1804- The most simple method for voting; you just choose a single option.
1805- """)
1806-
1807- CONDORCET = DBItem(2, """
1808- Condorcet Voting
1809-
1810- One of various methods used for calculating preferential votes. See
1811- http://www.electionmethods.org/CondorcetEx.htm for more information.
1812- """)
1813-
1814-
1815-class PollSecrecy(DBEnumeratedType):
1816- """The secrecy of a given Poll."""
1817-
1818- OPEN = DBItem(1, """
1819- Public Votes (Anyone can see a person's vote)
1820-
1821- Everyone who wants will be able to see a person's vote.
1822- """)
1823-
1824- ADMIN = DBItem(2, """
1825- Semi-secret Votes (Only team administrators can see a person's vote)
1826-
1827- All team owners and administrators will be able to see a person's vote.
1828- """)
1829-
1830- SECRET = DBItem(3, """
1831- Secret Votes (It's impossible to track a person's vote)
1832-
1833- We don't store the option a person voted in our database,
1834- """)
1835-
1836-
1837-class PollStatus:
1838- """This class stores the constants used when searching for polls."""
1839-
1840- OPEN = 'open'
1841- CLOSED = 'closed'
1842- NOT_YET_OPENED = 'not-yet-opened'
1843- ALL = frozenset([OPEN, CLOSED, NOT_YET_OPENED])
1844-
1845-
1846-class IPoll(Interface):
1847- """A poll for a given proposition in a team."""
1848-
1849- id = Int(title=_('The unique ID'), required=True, readonly=True)
1850-
1851- team = Int(
1852- title=_('The team that this poll refers to.'), required=True,
1853- readonly=True)
1854-
1855- name = PollNameField(
1856- title=_('The unique name of this poll'),
1857- description=_('A short unique name, beginning with a lower-case '
1858- 'letter or number, and containing only letters, '
1859- 'numbers, dots, hyphens, or plus signs.'),
1860- required=True, readonly=False, constraint=name_validator)
1861-
1862- title = TextLine(
1863- title=_('The title of this poll'), required=True, readonly=False)
1864-
1865- dateopens = Datetime(
1866- title=_('The date and time when this poll opens'), required=True,
1867- readonly=False)
1868-
1869- datecloses = Datetime(
1870- title=_('The date and time when this poll closes'), required=True,
1871- readonly=False)
1872-
1873- proposition = Text(
1874- title=_('The proposition that is going to be voted'), required=True,
1875- readonly=False)
1876-
1877- type = Choice(
1878- title=_('The type of this poll'), required=True,
1879- readonly=False, vocabulary=PollAlgorithm,
1880- default=PollAlgorithm.CONDORCET)
1881-
1882- allowspoilt = Bool(
1883- title=_('Users can spoil their votes?'),
1884- description=_(
1885- 'Allow users to leave the ballot blank (i.e. cast a vote for '
1886- '"None of the above")'),
1887- required=True, readonly=False, default=True)
1888-
1889- secrecy = Choice(
1890- title=_('The secrecy of the Poll'), required=True,
1891- readonly=False, vocabulary=PollSecrecy,
1892- default=PollSecrecy.SECRET)
1893-
1894- @invariant
1895- def saneDates(poll):
1896- """Ensure the poll's dates are sane.
1897-
1898- A poll's end date must be after its start date and its start date must
1899- be at least 12h from now.
1900- """
1901- if poll.dateopens >= poll.datecloses:
1902- raise Invalid(
1903- "A poll cannot close at the time (or before) it opens.")
1904- now = datetime.now(pytz.UTC)
1905- twelve_hours_ahead = now + timedelta(hours=12)
1906- start_date = poll.dateopens.astimezone(pytz.UTC)
1907- if start_date < twelve_hours_ahead:
1908- raise Invalid(
1909- "A poll cannot open less than 12 hours after it's created.")
1910-
1911- def isOpen(when=None):
1912- """Return True if this Poll is still open.
1913-
1914- The optional :when argument is used only by our tests, to test if the
1915- poll is/was/will be open at a specific date.
1916- """
1917-
1918- def isClosed(when=None):
1919- """Return True if this Poll is already closed.
1920-
1921- The optional :when argument is used only by our tests, to test if the
1922- poll is/was/will be closed at a specific date.
1923- """
1924-
1925- def isNotYetOpened(when=None):
1926- """Return True if this Poll is not yet opened.
1927-
1928- The optional :when argument is used only by our tests, to test if the
1929- poll is/was/will be not-yet-opened at a specific date.
1930- """
1931-
1932- def closesIn():
1933- """Return a timedelta object of the interval between now and the date
1934- when this poll closes."""
1935-
1936- def opensIn():
1937- """Return a timedelta object of the interval between now and the date
1938- when this poll opens."""
1939-
1940- def newOption(name, title=None, active=True):
1941- """Create a new PollOption for this poll.
1942-
1943- If title is None it'll be the same as name.
1944- """
1945-
1946- def getActiveOptions():
1947- """Return all PollOptions of this poll that are active."""
1948-
1949- def getAllOptions():
1950- """Return all Options of this poll."""
1951-
1952- def personVoted(person):
1953- """Return True if :person has already voted in this poll."""
1954-
1955- def getVotesByPerson(person):
1956- """Return the votes of the given person in this poll.
1957-
1958- The return value will always be a list of Vote objects. That's for
1959- consistency because on simple polls there'll be always a single vote,
1960- but for condorcet poll, there'll always be a list.
1961- """
1962-
1963- def getTotalVotes():
1964- """Return the total number of votes this poll had.
1965-
1966- This must be used only on closed polls.
1967- """
1968-
1969- def getWinners():
1970- """Return the options which won this poll.
1971-
1972- This should be used only on closed polls.
1973- """
1974-
1975- def removeOption(option, when=None):
1976- """Remove the given option from this poll.
1977-
1978- A ValueError is raised if the given option doesn't belong to this poll.
1979- This method can be used only on polls that are not yet opened.
1980- The optional :when argument is used only by our tests, to test if the
1981- poll is/was/will be not-yet-opened at a specific date.
1982- """
1983-
1984- def getOptionByName(name):
1985- """Return the PollOption by the given name."""
1986-
1987- def storeSimpleVote(person, option, when=None):
1988- """Store and return the vote of a given person in a this poll.
1989-
1990- This method can be used only if this poll is still open and if this is
1991- a Simple-style poll.
1992-
1993- :option: The choosen option.
1994-
1995- :when: Optional argument used only by our tests, to test if the poll
1996- is/was/will be open at a specific date.
1997- """
1998-
1999- def storeCondorcetVote(person, options, when=None):
2000- """Store and return the votes of a given person in this poll.
2001-
2002- This method can be used only if this poll is still open and if this is
2003- a Condorcet-style poll.
2004-
2005- :options: A dictionary, where the options are the keys and the
2006- preferences of each option are the values.
2007-
2008- :when: Optional argument used only by our tests, to test if the poll
2009- is/was/will be open at a specific date.
2010- """
2011-
2012- def getPairwiseMatrix():
2013- """Return the pairwise matrix for this poll.
2014-
2015- This method is only available for condorcet-style polls.
2016- See http://www.electionmethods.org/CondorcetEx.htm for an example of a
2017- pairwise matrix.
2018- """
2019-
2020-
2021-class IPollSet(Interface):
2022- """The set of Poll objects."""
2023-
2024- def new(team, name, title, proposition, dateopens, datecloses,
2025- secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
2026- """Create a new Poll for the given team."""
2027-
2028- def selectByTeam(team, status=PollStatus.ALL, orderBy=None, when=None):
2029- """Return all Polls for the given team, filtered by status.
2030-
2031- :status: is a sequence containing as many values as you want from
2032- PollStatus.
2033-
2034- :orderBy: can be either a string with the column name you want to sort
2035- or a list of column names as strings.
2036- If no orderBy is specified the results will be ordered using the
2037- default ordering specified in Poll._defaultOrder.
2038-
2039- The optional :when argument is used only by our tests, to test if the
2040- poll is/was/will-be open at a specific date.
2041- """
2042-
2043- def getByTeamAndName(team, name, default=None):
2044- """Return the Poll for the given team with the given name.
2045-
2046- Return :default if there's no Poll with this name for that team.
2047- """
2048-
2049-
2050-class IPollSubset(Interface):
2051- """The set of Poll objects for a given team."""
2052-
2053- team = Attribute(_("The team of these polls."))
2054-
2055- title = Attribute('Polls Page Title')
2056-
2057- def new(name, title, proposition, dateopens, datecloses, secrecy,
2058- allowspoilt, poll_type=PollAlgorithm.SIMPLE):
2059- """Create a new Poll for this team."""
2060-
2061- def getAll():
2062- """Return all Polls of this team."""
2063-
2064- def getOpenPolls(when=None):
2065- """Return all Open Polls for this team ordered by the date they'll
2066- close.
2067-
2068- The optional :when argument is used only by our tests, to test if the
2069- poll is/was/will be open at a specific date.
2070- """
2071-
2072- def getNotYetOpenedPolls(when=None):
2073- """Return all Not-Yet-Opened Polls for this team ordered by the date
2074- they'll open.
2075-
2076- The optional :when argument is used only by our tests, to test if the
2077- poll is/was/will be open at a specific date.
2078- """
2079-
2080- def getClosedPolls(when=None):
2081- """Return all Closed Polls for this team ordered by the date they
2082- closed.
2083-
2084- The optional :when argument is used only by our tests, to test if the
2085- poll is/was/will be open at a specific date.
2086- """
2087-
2088-
2089-class PollOptionNameField(ContentNameField):
2090-
2091- errormessage = _("%s is already in use by another option in this poll.")
2092-
2093- @property
2094- def _content_iface(self):
2095- return IPollOption
2096-
2097- def _getByName(self, name):
2098- if IPollOption.providedBy(self.context):
2099- poll = self.context.poll
2100- else:
2101- poll = self.context
2102- return poll.getOptionByName(name)
2103-
2104-
2105-class IPollOption(Interface):
2106- """An option to be voted in a given Poll."""
2107-
2108- id = Int(title=_('The unique ID'), required=True, readonly=True)
2109-
2110- poll = Int(
2111- title=_('The Poll to which this option refers to.'), required=True,
2112- readonly=True)
2113-
2114- name = PollOptionNameField(
2115- title=_('Name'), required=True, readonly=False)
2116-
2117- title = TextLine(
2118- title=_('Title'),
2119- description=_(
2120- 'The title of this option. A single brief sentence that '
2121- 'summarises the outcome for which people are voting if '
2122- 'they select this option.'),
2123- required=True, readonly=False)
2124-
2125- active = Bool(
2126- title=_('Is this option active?'), required=True, readonly=False,
2127- default=True)
2128-
2129- def destroySelf():
2130- """Remove this option from the database."""
2131-
2132-
2133-class IPollOptionSet(Interface):
2134- """The set of PollOption objects."""
2135-
2136- def new(poll, name, title, active=True):
2137- """Create a new PollOption."""
2138-
2139- def selectByPoll(poll, only_active=False):
2140- """Return all PollOptions of the given poll.
2141-
2142- If :only_active is True, then return only the active polls.
2143- """
2144-
2145- def getByPollAndId(poll, id, default=None):
2146- """Return the PollOption with the given id.
2147-
2148- Return :default if there's no PollOption with the given id or if that
2149- PollOption is not in the given poll.
2150- """
2151-
2152-
2153-class IVoteCast(Interface):
2154- """Here we store who voted in a Poll, but not their votes."""
2155-
2156- id = Int(title=_('The unique ID'), required=True, readonly=True)
2157-
2158- person = Int(
2159- title=_('The Person that voted.'), required=False, readonly=True)
2160-
2161- poll = Int(
2162- title=_('The Poll in which the person voted.'), required=True,
2163- readonly=True)
2164-
2165-
2166-class IVoteCastSet(Interface):
2167- """The set of all VoteCast objects."""
2168-
2169- def new(poll, person):
2170- """Create a new VoteCast."""
2171-
2172-
2173-class IVote(Interface):
2174- """Here we store the vote itself, linked to a special token.
2175-
2176- This token is given to the user when he votes, so he can change his vote
2177- later.
2178- """
2179-
2180- id = Int(
2181- title=_('The unique ID'), required=True, readonly=True)
2182-
2183- person = Int(
2184- title=_('The Person that voted.'), required=False, readonly=True)
2185-
2186- poll = Int(
2187- title=_('The Poll in which the person voted.'), required=True,
2188- readonly=True)
2189-
2190- option = Int(
2191- title=_('The PollOption choosen.'), required=True, readonly=False)
2192-
2193- preference = Int(
2194- title=_('The preference of the choosen PollOption'), required=True,
2195- readonly=False)
2196-
2197- token = Text(
2198- title=_('The token we give to the user.'),
2199- required=True, readonly=True)
2200-
2201-
2202-class OptionIsNotFromSimplePoll(Exception):
2203- """Someone tried use an option from a non-SIMPLE poll as if it was from a
2204- SIMPLE one."""
2205-
2206-
2207-class IVoteSet(Interface):
2208- """The set of all Vote objects."""
2209-
2210- def newToken():
2211- """Return a token that was never used in the Vote table."""
2212-
2213- def new(poll, option, preference, token, person):
2214- """Create a new Vote."""
2215-
2216- def getByToken(token):
2217- """Return the list of votes with the given token.
2218-
2219- For polls whose type is SIMPLE, this list will contain a single vote,
2220- because in SIMPLE poll only one option can be choosen.
2221- """
2222-
2223- def getVotesByOption(option):
2224- """Return the number of votes the given option received.
2225-
2226- Raises a TypeError if the given option doesn't belong to a
2227- simple-style poll.
2228- """
2229-
2230
2231=== modified file 'lib/lp/registry/model/person.py'
2232--- lib/lp/registry/model/person.py 2010-12-10 14:58:31 +0000
2233+++ lib/lp/registry/model/person.py 2010-12-16 14:48:27 +0000
2234@@ -3871,12 +3871,17 @@
2235 ('personlanguage', 'person'),
2236 ('person', 'merged'),
2237 ('emailaddress', 'person'),
2238- # Polls are not carried over when merging teams.
2239- ('poll', 'team'),
2240 # We can safely ignore the mailinglist table as there's a sanity
2241 # check above which prevents teams with associated mailing lists
2242 # from being merged.
2243 ('mailinglist', 'team'),
2244+ ('translationrelicensingagreement', 'person'),
2245+ # Polls are not carried over when merging teams.
2246+ # XXX: BradCrittenden 2010-12-16 bug=691105:
2247+ # Even though polls have been removed as a feature and from the
2248+ # data model, they still exist in the database and must be skipped
2249+ # here to avoid violating uniqueness constraints.
2250+ ('poll', 'team'),
2251 # I don't think we need to worry about the votecast and vote
2252 # tables, because a real human should never have two profiles
2253 # in Launchpad that are active members of a given team and voted
2254@@ -3885,7 +3890,6 @@
2255 # closed -- StuartBishop 20060602
2256 ('votecast', 'person'),
2257 ('vote', 'person'),
2258- ('translationrelicensingagreement', 'person'),
2259 ]
2260
2261 references = list(postgresql.listReferences(cur, 'person', 'id'))
2262
2263=== removed file 'lib/lp/registry/model/poll.py'
2264--- lib/lp/registry/model/poll.py 2010-08-20 20:31:18 +0000
2265+++ lib/lp/registry/model/poll.py 1970-01-01 00:00:00 +0000
2266@@ -1,440 +0,0 @@
2267-# Copyright 2009 Canonical Ltd. This software is licensed under the
2268-# GNU Affero General Public License version 3 (see the file LICENSE).
2269-
2270-# pylint: disable-msg=E0611,W0212
2271-
2272-__metaclass__ = type
2273-__all__ = [
2274- 'Poll',
2275- 'PollOption',
2276- 'PollOptionSet',
2277- 'PollSet',
2278- 'VoteCast',
2279- 'Vote',
2280- 'VoteSet',
2281- 'VoteCastSet',
2282- ]
2283-
2284-from datetime import datetime
2285-import random
2286-
2287-import pytz
2288-from sqlobject import (
2289- AND,
2290- BoolCol,
2291- ForeignKey,
2292- IntCol,
2293- OR,
2294- SQLObjectNotFound,
2295- StringCol,
2296- )
2297-from storm.store import Store
2298-from zope.component import getUtility
2299-from zope.interface import implements
2300-
2301-from canonical.database.datetimecol import UtcDateTimeCol
2302-from canonical.database.enumcol import EnumCol
2303-from canonical.database.sqlbase import (
2304- SQLBase,
2305- sqlvalues,
2306- )
2307-from lp.registry.interfaces.person import validate_public_person
2308-from lp.registry.interfaces.poll import (
2309- IPoll,
2310- IPollOption,
2311- IPollOptionSet,
2312- IPollSet,
2313- IVote,
2314- IVoteCast,
2315- IVoteCastSet,
2316- IVoteSet,
2317- OptionIsNotFromSimplePoll,
2318- PollAlgorithm,
2319- PollSecrecy,
2320- PollStatus,
2321- )
2322-
2323-
2324-class Poll(SQLBase):
2325- """See IPoll."""
2326-
2327- implements(IPoll)
2328- _table = 'Poll'
2329- sortingColumns = ['title', 'id']
2330- _defaultOrder = sortingColumns
2331-
2332- team = ForeignKey(
2333- dbName='team', foreignKey='Person',
2334- storm_validator=validate_public_person, notNull=True)
2335-
2336- name = StringCol(dbName='name', notNull=True)
2337-
2338- title = StringCol(dbName='title', notNull=True, unique=True)
2339-
2340- dateopens = UtcDateTimeCol(dbName='dateopens', notNull=True)
2341-
2342- datecloses = UtcDateTimeCol(dbName='datecloses', notNull=True)
2343-
2344- proposition = StringCol(dbName='proposition', notNull=True)
2345-
2346- type = EnumCol(dbName='type', enum=PollAlgorithm,
2347- default=PollAlgorithm.SIMPLE)
2348-
2349- allowspoilt = BoolCol(dbName='allowspoilt', default=True, notNull=True)
2350-
2351- secrecy = EnumCol(dbName='secrecy', enum=PollSecrecy,
2352- default=PollSecrecy.SECRET)
2353-
2354- def newOption(self, name, title, active=True):
2355- """See IPoll."""
2356- return getUtility(IPollOptionSet).new(self, name, title, active)
2357-
2358- def isOpen(self, when=None):
2359- """See IPoll."""
2360- if when is None:
2361- when = datetime.now(pytz.timezone('UTC'))
2362- return (self.datecloses >= when and self.dateopens <= when)
2363-
2364- @property
2365- def closesIn(self):
2366- """See IPoll."""
2367- return self.datecloses - datetime.now(pytz.timezone('UTC'))
2368-
2369- @property
2370- def opensIn(self):
2371- """See IPoll."""
2372- return self.dateopens - datetime.now(pytz.timezone('UTC'))
2373-
2374- def isClosed(self, when=None):
2375- """See IPoll."""
2376- if when is None:
2377- when = datetime.now(pytz.timezone('UTC'))
2378- return self.datecloses <= when
2379-
2380- def isNotYetOpened(self, when=None):
2381- """See IPoll."""
2382- if when is None:
2383- when = datetime.now(pytz.timezone('UTC'))
2384- return self.dateopens > when
2385-
2386- def getAllOptions(self):
2387- """See IPoll."""
2388- return getUtility(IPollOptionSet).selectByPoll(self)
2389-
2390- def getActiveOptions(self):
2391- """See IPoll."""
2392- return getUtility(IPollOptionSet).selectByPoll(self, only_active=True)
2393-
2394- def getVotesByPerson(self, person):
2395- """See IPoll."""
2396- return Vote.selectBy(person=person, poll=self)
2397-
2398- def personVoted(self, person):
2399- """See IPoll."""
2400- results = VoteCast.selectBy(person=person, poll=self)
2401- return bool(results.count())
2402-
2403- def removeOption(self, option, when=None):
2404- """See IPoll."""
2405- assert self.isNotYetOpened(when=when)
2406- if option.poll != self:
2407- raise ValueError(
2408- "Can't remove an option that doesn't belong to this poll")
2409- option.destroySelf()
2410-
2411- def getOptionByName(self, name):
2412- """See IPoll."""
2413- return PollOption.selectOneBy(poll=self, name=name)
2414-
2415- def _assertEverythingOkAndGetVoter(self, person, when=None):
2416- """Use assertions to Make sure all pre-conditions for a person to vote
2417- are met.
2418-
2419- Return the person if this is not a secret poll or None if it's a
2420- secret one.
2421- """
2422- assert self.isOpen(when=when), "This poll is not open"
2423- assert not self.personVoted(person), "Can't vote twice in the same poll"
2424- assert person.inTeam(self.team), (
2425- "Person %r is not a member of this poll's team." % person)
2426-
2427- # We only associate the option with the person if the poll is not a
2428- # SECRET one.
2429- if self.secrecy == PollSecrecy.SECRET:
2430- voter = None
2431- else:
2432- voter = person
2433- return voter
2434-
2435- def storeCondorcetVote(self, person, options, when=None):
2436- """See IPoll."""
2437- voter = self._assertEverythingOkAndGetVoter(person, when=when)
2438- assert self.type == PollAlgorithm.CONDORCET
2439- voteset = getUtility(IVoteSet)
2440-
2441- token = voteset.newToken()
2442- votes = []
2443- activeoptions = self.getActiveOptions()
2444- for option, preference in options.items():
2445- assert option.poll == self, (
2446- "The option %r doesn't belong to this poll" % option)
2447- assert option.active, "Option %r is not active" % option
2448- votes.append(voteset.new(self, option, preference, token, voter))
2449-
2450- # Store a vote with preference = None for each active option of this
2451- # poll that wasn't in the options argument.
2452- for option in activeoptions:
2453- if option not in options:
2454- votes.append(voteset.new(self, option, None, token, voter))
2455-
2456- getUtility(IVoteCastSet).new(self, person)
2457- return votes
2458-
2459- def storeSimpleVote(self, person, option, when=None):
2460- """See IPoll."""
2461- voter = self._assertEverythingOkAndGetVoter(person, when=when)
2462- assert self.type == PollAlgorithm.SIMPLE
2463- voteset = getUtility(IVoteSet)
2464-
2465- if option is None and not self.allowspoilt:
2466- raise ValueError("This poll doesn't allow spoilt votes.")
2467- elif option is not None:
2468- assert option.poll == self, (
2469- "The option %r doesn't belong to this poll" % option)
2470- assert option.active, "Option %r is not active" % option
2471- token = voteset.newToken()
2472- # This is a simple-style poll, so you can vote only on a single option
2473- # and this option's preference must be 1
2474- preference = 1
2475- vote = voteset.new(self, option, preference, token, voter)
2476- getUtility(IVoteCastSet).new(self, person)
2477- return vote
2478-
2479- def getTotalVotes(self):
2480- """See IPoll."""
2481- assert self.isClosed()
2482- return Vote.selectBy(poll=self).count()
2483-
2484- def getWinners(self):
2485- """See IPoll."""
2486- assert self.isClosed()
2487- # XXX: GuilhermeSalgado 2005-08-24:
2488- # For now, this method works only for SIMPLE-style polls. This is
2489- # not a problem as CONDORCET-style polls are disabled.
2490- assert self.type == PollAlgorithm.SIMPLE
2491- query = """
2492- SELECT option
2493- FROM Vote
2494- WHERE poll = %d AND option IS NOT NULL
2495- GROUP BY option
2496- HAVING COUNT(*) = (
2497- SELECT COUNT(*)
2498- FROM Vote
2499- WHERE poll = %d
2500- GROUP BY option
2501- ORDER BY COUNT(*) DESC LIMIT 1
2502- )
2503- """ % (self.id, self.id)
2504- results = Store.of(self).execute(query).get_all()
2505- if not results:
2506- return None
2507- return [PollOption.get(id) for (id,) in results]
2508-
2509- def getPairwiseMatrix(self):
2510- """See IPoll."""
2511- assert self.type == PollAlgorithm.CONDORCET
2512- options = list(self.getAllOptions())
2513- pairwise_matrix = []
2514- for option1 in options:
2515- pairwise_row = []
2516- for option2 in options:
2517- points_query = """
2518- SELECT COUNT(*) FROM Vote as v1, Vote as v2 WHERE
2519- v1.token = v2.token AND
2520- v1.option = %s AND v2.option = %s AND
2521- (
2522- (
2523- v1.preference IS NOT NULL AND
2524- v2.preference IS NOT NULL AND
2525- v1.preference < v2.preference
2526- )
2527- OR
2528- (
2529- v1.preference IS NOT NULL AND
2530- v2.preference IS NULL
2531- )
2532- )
2533- """ % sqlvalues(option1.id, option2.id)
2534- if option1 == option2:
2535- pairwise_row.append(None)
2536- else:
2537- points = Store.of(self).execute(points_query).get_one()[0]
2538- pairwise_row.append(points)
2539- pairwise_matrix.append(pairwise_row)
2540- return pairwise_matrix
2541-
2542-
2543-class PollSet:
2544- """See IPollSet."""
2545-
2546- implements(IPollSet)
2547-
2548- def new(self, team, name, title, proposition, dateopens, datecloses,
2549- secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
2550- """See IPollSet."""
2551- return Poll(team=team, name=name, title=title,
2552- proposition=proposition, dateopens=dateopens,
2553- datecloses=datecloses, secrecy=secrecy,
2554- allowspoilt=allowspoilt, type=poll_type)
2555-
2556- def selectByTeam(self, team, status=PollStatus.ALL, orderBy=None, when=None):
2557- """See IPollSet."""
2558- if when is None:
2559- when = datetime.now(pytz.timezone('UTC'))
2560-
2561- if orderBy is None:
2562- orderBy = Poll.sortingColumns
2563-
2564-
2565- status = set(status)
2566- status_clauses = []
2567- if PollStatus.OPEN in status:
2568- status_clauses.append(AND(Poll.q.dateopens <= when,
2569- Poll.q.datecloses > when))
2570- if PollStatus.CLOSED in status:
2571- status_clauses.append(Poll.q.datecloses <= when)
2572- if PollStatus.NOT_YET_OPENED in status:
2573- status_clauses.append(Poll.q.dateopens > when)
2574-
2575- assert len(status_clauses) > 0, "No poll statuses were selected"
2576-
2577- results = Poll.select(AND(Poll.q.teamID == team.id,
2578- OR(*status_clauses)))
2579-
2580- return results.orderBy(orderBy)
2581-
2582- def getByTeamAndName(self, team, name, default=None):
2583- """See IPollSet."""
2584- query = AND(Poll.q.teamID == team.id, Poll.q.name == name)
2585- try:
2586- return Poll.selectOne(query)
2587- except SQLObjectNotFound:
2588- return default
2589-
2590-
2591-class PollOption(SQLBase):
2592- """See IPollOption."""
2593-
2594- implements(IPollOption)
2595- _table = 'PollOption'
2596- _defaultOrder = ['title', 'id']
2597-
2598- poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
2599-
2600- name = StringCol(notNull=True)
2601-
2602- title = StringCol(notNull=True)
2603-
2604- active = BoolCol(notNull=True, default=False)
2605-
2606-
2607-class PollOptionSet:
2608- """See IPollOptionSet."""
2609-
2610- implements(IPollOptionSet)
2611-
2612- def new(self, poll, name, title, active=True):
2613- """See IPollOptionSet."""
2614- return PollOption(poll=poll, name=name, title=title, active=active)
2615-
2616- def selectByPoll(self, poll, only_active=False):
2617- """See IPollOptionSet."""
2618- query = PollOption.q.pollID == poll.id
2619- if only_active:
2620- query = AND(query, PollOption.q.active == True)
2621- return PollOption.select(query)
2622-
2623- def getByPollAndId(self, poll, option_id, default=None):
2624- """See IPollOptionSet."""
2625- query = AND(PollOption.q.pollID == poll.id,
2626- PollOption.q.id == option_id)
2627- try:
2628- return PollOption.selectOne(query)
2629- except SQLObjectNotFound:
2630- return default
2631-
2632-
2633-class VoteCast(SQLBase):
2634- """See IVoteCast."""
2635-
2636- implements(IVoteCast)
2637- _table = 'VoteCast'
2638- _defaultOrder = 'id'
2639-
2640- person = ForeignKey(
2641- dbName='person', foreignKey='Person',
2642- storm_validator=validate_public_person, notNull=True)
2643-
2644- poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
2645-
2646-
2647-class VoteCastSet:
2648- """See IVoteCastSet."""
2649-
2650- implements(IVoteCastSet)
2651-
2652- def new(self, poll, person):
2653- """See IVoteCastSet."""
2654- return VoteCast(poll=poll, person=person)
2655-
2656-
2657-class Vote(SQLBase):
2658- """See IVote."""
2659-
2660- implements(IVote)
2661- _table = 'Vote'
2662- _defaultOrder = ['preference', 'id']
2663-
2664- person = ForeignKey(
2665- dbName='person', foreignKey='Person',
2666- storm_validator=validate_public_person)
2667-
2668- poll = ForeignKey(dbName='poll', foreignKey='Poll', notNull=True)
2669-
2670- option = ForeignKey(dbName='option', foreignKey='PollOption')
2671-
2672- preference = IntCol(dbName='preference')
2673-
2674- token = StringCol(dbName='token', notNull=True, unique=True)
2675-
2676-
2677-class VoteSet:
2678- """See IVoteSet."""
2679-
2680- implements(IVoteSet)
2681-
2682- def newToken(self):
2683- """See IVoteSet."""
2684- chars = '23456789bcdfghjkmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ'
2685- length = 10
2686- token = ''.join([random.choice(chars) for c in range(length)])
2687- while self.getByToken(token):
2688- token = ''.join([random.choice(chars) for c in range(length)])
2689- return token
2690-
2691- def new(self, poll, option, preference, token, person):
2692- """See IVoteSet."""
2693- return Vote(poll=poll, option=option, preference=preference,
2694- token=token, person=person)
2695-
2696- def getByToken(self, token):
2697- """See IVoteSet."""
2698- return Vote.selectBy(token=token)
2699-
2700- def getVotesByOption(self, option):
2701- """See IVoteSet."""
2702- if option.poll.type != PollAlgorithm.SIMPLE:
2703- raise OptionIsNotFromSimplePoll(
2704- '%r is not an option of a simple-style poll.' % option)
2705- return Vote.selectBy(option=option).count()
2706-
2707
2708=== removed directory 'lib/lp/registry/stories/team-polls'
2709=== removed file 'lib/lp/registry/stories/team-polls/create-poll-options.txt'
2710--- lib/lp/registry/stories/team-polls/create-poll-options.txt 2009-08-19 19:48:09 +0000
2711+++ lib/lp/registry/stories/team-polls/create-poll-options.txt 1970-01-01 00:00:00 +0000
2712@@ -1,85 +0,0 @@
2713-= Poll options =
2714-
2715-A poll can have any number of options, but these must be created
2716-before the poll has opened.
2717-
2718-First we create a new poll to use throughout this test.
2719-
2720- >>> login('jeff.waugh@ubuntulinux.com')
2721- >>> from zope.component import getUtility
2722- >>> from lp.registry.interfaces.person import IPersonSet
2723- >>> factory.makePoll(getUtility(IPersonSet).getByName('ubuntu-team'),
2724- ... 'dpl-2080', 'dpl-2080', 'dpl-2080')
2725- <Poll...
2726- >>> logout()
2727-
2728-Our poll is not yet open, so new options can be added to it.
2729-
2730- >>> team_admin_browser = setupBrowser(
2731- ... auth='Basic jeff.waugh@ubuntulinux.com:jdub')
2732- >>> team_admin_browser.open(
2733- ... 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080')
2734- >>> team_admin_browser.getLink('Add new option').click()
2735- >>> team_admin_browser.url
2736- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080/+newoption'
2737-
2738- >>> bill_name = (
2739- ... 'bill-amazingly-huge-middle-name-almost-impossible-to-read-graham')
2740- >>> team_admin_browser.getControl('Name').value = bill_name
2741- >>> team_admin_browser.getControl('Title').value = 'Bill Graham'
2742- >>> team_admin_browser.getControl('Create').click()
2743-
2744-After adding an options we're taken back to the poll's home page.
2745-
2746- >>> team_admin_browser.url
2747- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080'
2748-
2749-And here we see the option listed as one of the active options for this
2750-poll.
2751-
2752- >>> print extract_text(
2753- ... find_tag_by_id(team_admin_browser.contents, 'options'))
2754- Name Title Active
2755- bill... Bill Graham Yes
2756-
2757-If we try to add a new option without providing a title, we'll get an error
2758-message because the title is required.
2759-
2760- >>> team_admin_browser.getLink('Add new option').click()
2761- >>> team_admin_browser.url
2762- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080/+newoption'
2763-
2764- >>> will_name = (
2765- ... 'will-amazingly-huge-middle-name-almost-impossible-to-read-graham')
2766- >>> team_admin_browser.getControl('Name').value = will_name
2767- >>> team_admin_browser.getControl('Title').value = ''
2768- >>> team_admin_browser.getControl('Create').click()
2769-
2770- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
2771- There is 1 error.
2772- Required input is missing.
2773-
2774-If we try to add a new option with the same name of a existing option, we
2775-should get a nice error message
2776-
2777- >>> team_admin_browser.getControl('Name').value = bill_name
2778- >>> team_admin_browser.getControl('Title').value = 'Bill Again'
2779- >>> team_admin_browser.getControl('Create').click()
2780-
2781- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
2782- There is 1 error.
2783- ...is already in use by another option in this poll.
2784-
2785-It's not possible to add/edit a poll option after a poll is open or closed.
2786-That's only possible when the poll is not yet open.
2787-
2788- >>> team_admin_browser.open(
2789- ... 'http://launchpad.dev/~ubuntu-team/+poll/director-2004/+newoption')
2790-
2791- >>> "\n".join(get_feedback_messages(team_admin_browser.contents))
2792- u'You can&#8217;t add new options because the poll is already closed.'
2793-
2794- >>> team_admin_browser.open(
2795- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+newoption')
2796- >>> "\n".join(get_feedback_messages(team_admin_browser.contents))
2797- u'You can&#8217;t add new options because the poll is already open.'
2798
2799=== removed file 'lib/lp/registry/stories/team-polls/create-polls.txt'
2800--- lib/lp/registry/stories/team-polls/create-polls.txt 2010-05-19 05:47:50 +0000
2801+++ lib/lp/registry/stories/team-polls/create-polls.txt 1970-01-01 00:00:00 +0000
2802@@ -1,163 +0,0 @@
2803-Let's first setup some objects that we'll need.
2804-
2805- >>> no_priv_browser = setupBrowser(
2806- ... auth='Basic no-priv@canonical.com:test')
2807- >>> team_admin_browser = setupBrowser(
2808- ... 'Basic jeff.waugh@ubuntulinux.com:jdub')
2809-
2810-If you're not logged in and go to the +polls page of the "Ubuntu Team"
2811-you'll see a link to login as a team administrator.
2812-
2813- >>> anon_browser.open('http://launchpad.dev/~ubuntu-team')
2814- >>> anon_browser.getLink('Show polls').click()
2815- >>> anon_browser.url
2816- 'http://launchpad.dev/~ubuntu-team/+polls'
2817- >>> anon_browser.getLink('Log in as an admin to set up a new poll').url
2818- 'http://launchpad.dev/~ubuntu-team/+login'
2819-
2820-Try to create a new poll logged in as 'no-priv', which is not a team
2821-administrator. There's no link leading to the +newpoll page, but the user can
2822-easily guess it.
2823-
2824- >>> no_priv_browser.open('http://launchpad.dev/~ubuntu-team/+newpoll')
2825- Traceback (most recent call last):
2826- ...
2827- Unauthorized:...
2828-
2829-Now we're logged in as Jeff Waugh which is a team administrator and thus can
2830-create a new poll.
2831-
2832- >>> team_admin_browser.open('http://launchpad.dev/~ubuntu-team')
2833- >>> team_admin_browser.getLink('Show polls').click()
2834- >>> team_admin_browser.getLink('Set up a new poll').click()
2835- >>> team_admin_browser.url
2836- 'http://launchpad.dev/~ubuntu-team/+newpoll'
2837-
2838- >>> team_admin_browser.title
2839- 'New poll for team Ubuntu Team...
2840-
2841-First we try to create a poll with a invalid name to
2842-test the name field validator.
2843-
2844- >>> team_admin_browser.getControl(
2845- ... 'The unique name of this poll').value = 'election_2100'
2846- >>> team_admin_browser.getControl(
2847- ... 'The title of this poll').value = 'Presidential Election 2100'
2848- >>> proposition = 'Who is going to be the next president?'
2849- >>> team_admin_browser.getControl(
2850- ... 'The proposition that is going to be voted').value = proposition
2851- >>> team_admin_browser.getControl(
2852- ... 'Users can spoil their votes?').selected = True
2853- >>> team_admin_browser.getControl(
2854- ... name='field.dateopens').value = '2100-06-04 02:00:00+00:00'
2855- >>> team_admin_browser.getControl(
2856- ... name='field.datecloses').value = '2100-07-04 02:00:00+00:00'
2857- >>> team_admin_browser.getControl('Continue').click()
2858-
2859- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
2860- There is 1 error.
2861- Invalid name 'election_2100'. Names must be at least two characters ...
2862-
2863-We fix the name, but swap the dates. Again a nice error message.
2864-
2865- >>> team_admin_browser.getControl(
2866- ... 'The unique name of this poll').value = 'election-2100'
2867- >>> team_admin_browser.getControl(
2868- ... name='field.dateopens').value = '2100-07-04 02:00:00+00:00'
2869- >>> team_admin_browser.getControl(
2870- ... name='field.datecloses').value = '2100-06-04 02:00:00+00:00'
2871- >>> team_admin_browser.getControl('Continue').click()
2872-
2873- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
2874- There is 1 error.
2875- A poll cannot close at the time (or before) it opens.
2876-
2877-Now we get it right.
2878-
2879- >>> team_admin_browser.getControl(
2880- ... name='field.dateopens').value = '2100-06-04 02:00:00+00:00'
2881- >>> team_admin_browser.getControl(
2882- ... name='field.datecloses').value = '2100-07-04 02:00:00+00:00'
2883- >>> team_admin_browser.getControl('Continue').click()
2884-
2885-We're redirected to the newly created poll page.
2886-
2887- >>> team_admin_browser.url
2888- 'http://launchpad.dev/~ubuntu-team/+poll/election-2100'
2889-
2890-Create a new poll that starts in 2025-06-04 and will last until 2035.
2891-
2892- >>> team_admin_browser.open(
2893- ... 'http://launchpad.dev/~ubuntu-team/+newpoll')
2894- >>> team_admin_browser.getControl(
2895- ... 'The unique name of this poll').value = 'dpl-2080'
2896- >>> team_admin_browser.getControl(
2897- ... 'The title of this poll').value = 'Debian Project Leader Election 2080'
2898- >>> proposition = 'The next debian project leader'
2899- >>> team_admin_browser.getControl(
2900- ... 'The proposition that is going to be voted').value = proposition
2901- >>> team_admin_browser.getControl(
2902- ... 'Users can spoil their votes?').selected = True
2903- >>> team_admin_browser.getControl(
2904- ... name='field.dateopens').value = '2025-06-04 02:00:00+00:00'
2905- >>> team_admin_browser.getControl(
2906- ... name='field.datecloses').value = '2035-06-04 02:00:00+00:00'
2907- >>> team_admin_browser.getControl('Continue').click()
2908-
2909-We're redirected to the newly created poll
2910-
2911- >>> team_admin_browser.url
2912- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080'
2913- >>> team_admin_browser.title
2914- 'Debian Project Leader Election 2080 : \xe2\x80\x9cUbuntu Team\xe2\x80\x9d team'
2915- >>> print_location(team_admin_browser.contents)
2916- Hierarchy: ?Ubuntu Team? team > Debian Project Leader Election 2080
2917- Tabs:
2918- * Overview (selected) - http://launchpad.dev/~ubuntu-team
2919- * Code - http://code.launchpad.dev/~ubuntu-team
2920- * Bugs - http://bugs.launchpad.dev/~ubuntu-team
2921- * Blueprints - http://blueprints.launchpad.dev/~ubuntu-team
2922- * Translations - http://translations.launchpad.dev/~ubuntu-team
2923- * Answers - http://answers.launchpad.dev/~ubuntu-team
2924- Main heading: Debian Project Leader Election 2080
2925- >>> team_admin_browser.getLink('add an option').url
2926- 'http://launchpad.dev/%7Eubuntu-team/+poll/dpl-2080/+newoption'
2927-
2928-Now lets try to insert a poll with the name of a existing one.
2929-
2930-# XXX matsubara 2006-07-17 bug=53302:
2931-# There's no link to get back to +polls.
2932-
2933- >>> team_admin_browser.open(
2934- ... 'http://launchpad.dev/~ubuntu-team/+newpoll')
2935- >>> team_admin_browser.getControl(
2936- ... 'The unique name of this poll').value = 'dpl-2080'
2937- >>> team_admin_browser.getControl(
2938- ... 'The title of this poll').value = 'Debian Project Leader Election 2080'
2939- >>> proposition = 'The next debian project leader'
2940- >>> team_admin_browser.getControl(
2941- ... 'The proposition that is going to be voted').value = proposition
2942- >>> team_admin_browser.getControl(
2943- ... 'Users can spoil their votes?').selected = True
2944- >>> team_admin_browser.getControl(
2945- ... name='field.dateopens').value = '2025-06-04 02:00:00+00:00'
2946- >>> team_admin_browser.getControl(
2947- ... name='field.datecloses').value = '2035-06-04 02:00:00+00:00'
2948- >>> team_admin_browser.getControl('Continue').click()
2949-
2950- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
2951- There is 1 error.
2952- dpl-2080 is already in use by another poll in this team.
2953-
2954-When creating a new poll, its start date must be at least 12 hours from
2955-now, so that the user creating it has a chance to add some options before
2956-the poll opens -- at that point new options cannot be added.
2957-
2958- >>> team_admin_browser.getControl('The unique name').value = 'today'
2959- >>> from datetime import datetime
2960- >>> today = datetime.today().strftime('%Y-%m-%d')
2961- >>> team_admin_browser.getControl(name='field.dateopens').value = today
2962- >>> team_admin_browser.getControl('Continue').click()
2963- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
2964- There is 1 error.
2965- A poll cannot open less than 12 hours after it's created.
2966
2967=== removed file 'lib/lp/registry/stories/team-polls/edit-options.txt'
2968--- lib/lp/registry/stories/team-polls/edit-options.txt 2009-08-19 19:48:09 +0000
2969+++ lib/lp/registry/stories/team-polls/edit-options.txt 1970-01-01 00:00:00 +0000
2970@@ -1,59 +0,0 @@
2971-= Editing poll options =
2972-
2973-Changing the poll options detail is not possible if you are not one of the
2974-team's administrators:
2975-
2976- >>> user_browser.open('http://launchpad.dev/~ubuntu-team/+polls')
2977- >>> user_browser.getLink('A public poll that never closes').click()
2978- >>> user_browser.url
2979- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes4'
2980- >>> print extract_text(find_tag_by_id(user_browser.contents, 'options'))
2981- Name Title Active
2982- OptionA OptionA Yes
2983- ...
2984- >>> user_browser.getLink('[Edit]')
2985- Traceback (most recent call last):
2986- ...
2987- LinkNotFoundError
2988-
2989-And when the poll already started, administrators cannot change the options
2990-either:
2991-
2992- # Need to craft the URL manually because there's no link to it -- the
2993- # option can't be changed, after all.
2994- >>> browser = setupBrowser(
2995- ... auth='Basic jeff.waugh@ubuntulinux.com:jdub')
2996- >>> browser.open('http://launchpad.dev/~ubuntu-team/+poll/never-closes4/'
2997- ... '+option/20')
2998- >>> print "\n".join(get_feedback_messages(browser.contents))
2999- You can&#8217;t edit any options because the poll is already open.
3000-
3001-Since Jeff is an administrator of ubuntu-team and we have a poll that hasn't
3002-been opened yet, he should be able to edit its options.
3003-
3004- >>> browser.open('http://launchpad.dev/~ubuntu-team/+polls')
3005- >>> browser.getLink('A public poll that has not opened yet').click()
3006- >>> browser.url
3007- 'http://launchpad.dev/~ubuntu-team/+poll/not-yet-opened'
3008-
3009- >>> browser.getLink('[Edit]').click()
3010- >>> browser.url
3011- 'http://launchpad.dev/~ubuntu-team/+poll/not-yet-opened/+option/...'
3012-
3013- >>> browser.getControl('Name').value
3014- 'OptionX'
3015- >>> browser.getControl('Title').value
3016- 'OptionX'
3017- >>> browser.getControl('Name').value = 'option-z'
3018- >>> browser.getControl('Title').value = 'Option Z'
3019- >>> browser.getControl('Save').click()
3020-
3021- >>> browser.url
3022- 'http://launchpad.dev/~ubuntu-team/+poll/not-yet-opened'
3023- >>> print find_portlet(browser.contents, 'Voting options').renderContents()
3024- <BLANKLINE>
3025- <h2>Voting options</h2>
3026- ...
3027- ...option-z...
3028- ...
3029-
3030
3031=== removed file 'lib/lp/registry/stories/team-polls/edit-poll.txt'
3032--- lib/lp/registry/stories/team-polls/edit-poll.txt 2010-10-11 17:36:14 +0000
3033+++ lib/lp/registry/stories/team-polls/edit-poll.txt 1970-01-01 00:00:00 +0000
3034@@ -1,97 +0,0 @@
3035-= Editing a poll =
3036-
3037-All attributes of a poll can be changed as long as the poll has not opened
3038-yet.
3039-
3040-First we create a new poll to use throughout this test.
3041-
3042- >>> login('jeff.waugh@ubuntulinux.com')
3043- >>> from zope.component import getUtility
3044- >>> from lp.registry.interfaces.person import IPersonSet
3045- >>> factory.makePoll(getUtility(IPersonSet).getByName('ubuntu-team'),
3046- ... 'dpl-2080', 'dpl-2080', 'dpl-2080')
3047- <Poll...
3048- >>> logout()
3049-
3050-Now we'll try to change its name to something that is already in use.
3051-
3052- >>> team_admin_browser = setupBrowser(
3053- ... auth='Basic jeff.waugh@ubuntulinux.com:jdub')
3054- >>> team_admin_browser.open(
3055- ... 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080')
3056- >>> team_admin_browser.getLink('Change details').click()
3057-
3058- >>> team_admin_browser.url
3059- 'http://launchpad.dev/~ubuntu-team/+poll/dpl-2080/+edit'
3060-
3061- >>> team_admin_browser.getControl(
3062- ... 'The unique name of this poll').value = 'never-closes'
3063- >>> team_admin_browser.getControl('Save').click()
3064-
3065- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
3066- There is 1 error.
3067- ...never-closes is already in use by another poll in this team.
3068-
3069-Entering an end date that precedes the start date returns a nice error
3070-message.
3071-
3072- >>> team_admin_browser.getControl(
3073- ... 'The unique name of this poll').value = 'dpl-2080'
3074- >>> team_admin_browser.getControl(
3075- ... name='field.dateopens').value = '3000-11-01 00:00:00+00:00'
3076- >>> team_admin_browser.getControl(
3077- ... name='field.datecloses').value = '3000-01-01 00:00:00+00:00'
3078- >>> team_admin_browser.getControl('Save').click()
3079-
3080- >>> print "\n".join(get_feedback_messages(team_admin_browser.contents))
3081- There is 1 error.
3082- A poll cannot close at the time (or before) it opens.
3083-
3084-We successfully change the polls name
3085-
3086- >>> team_admin_browser.getControl(
3087- ... 'The unique name of this poll').value = 'election-3000'
3088- >>> team_admin_browser.getControl(
3089- ... name='field.dateopens').value = '3000-01-01 00:00:00+00:00'
3090- >>> team_admin_browser.getControl(
3091- ... name='field.datecloses').value = '3000-11-01 00:00:00+00:00'
3092- >>> team_admin_browser.getControl('Save').click()
3093-
3094- >>> team_admin_browser.url
3095- 'http://launchpad.dev/~ubuntu-team/+poll/election-3000'
3096-
3097-Trying to edit a poll that's already open isn't possible.
3098-
3099- >>> team_admin_browser.open('http://launchpad.dev/~ubuntu-team/')
3100- >>> team_admin_browser.getLink('Show polls').click()
3101- >>> team_admin_browser.url
3102- 'http://launchpad.dev/~ubuntu-team/+polls'
3103-
3104- >>> team_admin_browser.getLink('A random poll that never closes').click()
3105- >>> team_admin_browser.url
3106- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote'
3107-
3108- >>> team_admin_browser.open(
3109- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+edit')
3110- >>> print extract_text(
3111- ... find_tag_by_id(team_admin_browser.contents, 'not-editable'))
3112- This poll can't be edited...
3113-
3114-It's also not possible to edit a poll that's already closed.
3115-
3116- >>> team_admin_browser.open('http://launchpad.dev/~ubuntu-team/')
3117- >>> team_admin_browser.getLink('Show polls').click()
3118- >>> team_admin_browser.url
3119- 'http://launchpad.dev/~ubuntu-team/+polls'
3120-
3121- >>> team_admin_browser.getLink("2004 Director's Elections").click()
3122- >>> team_admin_browser.url
3123- 'http://launchpad.dev/~ubuntu-team/+poll/director-2004'
3124-
3125- >>> 'Voting has closed' in team_admin_browser.contents
3126- True
3127-
3128- >>> team_admin_browser.getLink('Change details').click()
3129- >>> print extract_text(
3130- ... find_tag_by_id(team_admin_browser.contents, 'not-editable'))
3131- This poll can't be edited...
3132
3133=== removed file 'lib/lp/registry/stories/team-polls/vote-poll.txt'
3134--- lib/lp/registry/stories/team-polls/vote-poll.txt 2010-10-11 17:36:14 +0000
3135+++ lib/lp/registry/stories/team-polls/vote-poll.txt 1970-01-01 00:00:00 +0000
3136@@ -1,167 +0,0 @@
3137-= Voting on polls =
3138-
3139-Foo Bar (a member of the ubuntu-team) wants to vote on the 'never-closes'
3140-poll, which is a poll with secret votes, which means he'll get a token that he
3141-must use to see/change his vote afterwards.
3142-
3143- >>> browser = setupBrowser(auth='Basic foo.bar@canonical.com:test')
3144- >>> browser.open('http://launchpad.dev/~ubuntu-team/+polls')
3145- >>> browser.getLink('A random poll that never closes').click()
3146- >>> browser.url
3147- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote'
3148-
3149- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
3150- <BLANKLINE>
3151- ...
3152- <h2>Your current vote</h2>
3153- ...You have not yet voted in this poll...
3154- <h2>Vote now</h2>
3155- ...
3156-
3157- >>> browser.getControl('None of these options').selected = True
3158- >>> browser.getControl('Continue').click()
3159-
3160- >>> browser.url
3161- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote'
3162-
3163- >>> tags = find_tags_by_class(browser.contents, "informational message")
3164- >>> for tag in tags:
3165- ... print tag.renderContents()
3166- Your vote has been recorded. If you want to view or change it later you
3167- must write down this key: ...
3168-
3169- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
3170- <BLANKLINE>
3171- ...
3172- <h2>Your current vote</h2>
3173- ...Your current vote is for <b> none of the options. </b>...
3174- ...
3175-
3176-Foo Bar will now vote on a poll with public votes.
3177-
3178- >>> browser.open('http://launchpad.dev/~ubuntu-team/+polls')
3179- >>> browser.getLink('A public poll that never closes').click()
3180- >>> browser.url
3181- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes4/+vote'
3182-
3183- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
3184- <BLANKLINE>
3185- ...
3186- <h2>Your current vote</h2>
3187- ...You have not yet voted in this poll...
3188- <h2>Vote now</h2>
3189- ...
3190-
3191- >>> browser.getControl('OptionB').selected = True
3192- >>> browser.getControl('Continue').click()
3193-
3194- >>> browser.url
3195- 'http://launchpad.dev/~ubuntu-team/+poll/never-closes4/+vote'
3196-
3197- >>> tags = find_tags_by_class(browser.contents, "informational message")
3198- >>> for tag in tags:
3199- ... print tag.renderContents()
3200- Your vote was stored successfully. You can come back to this page at any
3201- time before this poll closes to view or change your vote, if you want.
3202-
3203- >>> print find_tag_by_id(browser.contents, 'your-vote').renderContents()
3204- <BLANKLINE>
3205- ...
3206- <h2>Your current vote</h2>
3207- ...Your current vote is for <b>OptionB</b>...
3208- ...
3209-
3210-
3211-For convenience we provide an option for when the user doesn't want to vote
3212-yet.
3213-
3214- >>> team_admin_browser = setupBrowser(
3215- ... auth='Basic jeff.waugh@ubuntulinux.com:jdub')
3216- >>> team_admin_browser.open(
3217- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote')
3218- >>> not_yet_voted_message = 'You have not yet voted in this poll.'
3219- >>> not_yet_voted_message in team_admin_browser.contents
3220- True
3221-
3222- >>> team_admin_browser.getControl(name='newoption').value = ["donotvote"]
3223- >>> team_admin_browser.getControl(name='continue').click()
3224-
3225- >>> contents = team_admin_browser.contents
3226- >>> for tag in find_tags_by_class(contents, "informational message"):
3227- ... print tag.renderContents()
3228- You chose not to vote yet.
3229-
3230- >>> print find_tag_by_id(contents, 'your-vote').renderContents()
3231- <BLANKLINE>
3232- ...
3233- <h2>Your current vote</h2>
3234- ...You have not yet voted in this poll...
3235- ...
3236-
3237-
3238-== No permission to vote ==
3239-
3240-Only members of a given team can vote on that team's polls. Other users can't,
3241-even if they guess the URL for the voting page.
3242-
3243- >>> non_member_browser = setupBrowser(
3244- ... auth='Basic test@canonical.com:test')
3245- >>> non_member_browser.open(
3246- ... 'http://launchpad.dev/~ubuntu-team/+poll/never-closes/+vote')
3247- >>> for tag in find_tags_by_class(
3248- ... non_member_browser.contents, "informational message"):
3249- ... print tag.renderContents()
3250- You can&#8217;t vote in this poll because you&#8217;re not a member
3251- of Ubuntu Team.
3252-
3253-
3254-== Closed polls ==
3255-
3256-It's not possible to vote on closed polls, even if we manually craft the URL.
3257-
3258- >>> team_admin_browser.open(
3259- ... 'http://launchpad.dev/~ubuntu-team/+poll/leader-2004')
3260- >>> print find_tag_by_id(
3261- ... team_admin_browser.contents, 'maincontent').renderContents()
3262- <BLANKLINE>
3263- ...
3264- <h2>Voting has closed</h2>
3265- ...
3266-
3267- >>> team_admin_browser.open(
3268- ... 'http://launchpad.dev/~ubuntu-team/+poll/leader-2004/+vote')
3269- >>> print find_tag_by_id(
3270- ... team_admin_browser.contents, 'maincontent').renderContents()
3271- <BLANKLINE>
3272- ...
3273- <p class="informational message">
3274- This poll is already closed.
3275- </p>
3276- ...
3277-
3278- >>> team_admin_browser.getControl(name='continue')
3279- Traceback (most recent call last):
3280- ...
3281- LookupError: name 'continue'
3282-
3283-The same is true for condorcet polls too.
3284-
3285- >>> team_admin_browser.open(
3286- ... 'http://launchpad.dev/~ubuntu-team/+poll/director-2004')
3287- >>> print find_tag_by_id(
3288- ... team_admin_browser.contents, 'maincontent').renderContents()
3289- <BLANKLINE>
3290- ...
3291- <h2>Voting has closed</h2>
3292- ...
3293-
3294- >>> team_admin_browser.getControl(name='continue')
3295- Traceback (most recent call last):
3296- ...
3297- LookupError: name 'continue'
3298-
3299- >>> team_admin_browser.open(
3300- ... 'http://launchpad.dev/~ubuntu-team/+poll/director-2004/+vote')
3301- >>> for message in get_feedback_messages(team_admin_browser.contents):
3302- ... print message
3303- This poll is already closed.
3304
3305=== removed file 'lib/lp/registry/stories/team-polls/xx-poll-condorcet-voting.txt'
3306--- lib/lp/registry/stories/team-polls/xx-poll-condorcet-voting.txt 2010-10-11 17:36:14 +0000
3307+++ lib/lp/registry/stories/team-polls/xx-poll-condorcet-voting.txt 1970-01-01 00:00:00 +0000
3308@@ -1,229 +0,0 @@
3309-# XXX Guilherme Salgado, 2006-01-19:
3310-# Merge this test with team-polls/xx-votepoll.txt
3311-
3312- Go to a condorcet-style poll (which is still open) and check that apart
3313- from seeing our vote we can also change it.
3314-
3315- >>> print http(r"""
3316- ... GET /~ubuntu-team/+poll/never-closes2 HTTP/1.1
3317- ... Accept-Language: en-us,en;q=0.5
3318- ... Authorization: Basic foo.bar@canonical.com:test
3319- ... """)
3320- HTTP/1.1 303 See Other
3321- ...
3322- Location: http://localhost/~ubuntu-team/+poll/never-closes2/+vote
3323- ...
3324-
3325- >>> print http(r"""
3326- ... GET /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
3327- ... Authorization: Basic foo.bar@canonical.com:test
3328- ... """)
3329- HTTP/1.1 200 Ok
3330- ...
3331- ...You must enter your vote key...
3332- ...This is a secret poll...
3333- ...your vote is identified only by the key you...
3334- ...were given when you voted. To view or change your vote you must enter...
3335- ...your key:...
3336- ...
3337-
3338-
3339- If a non-member (Sample Person) guesses the voting URL and tries to vote,
3340- he won't be allowed.
3341-
3342- >>> print http(r"""
3343- ... GET /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
3344- ... Authorization: Basic dGVzdEBjYW5vbmljYWwuY29tOnRlc3Q=
3345- ... """)
3346- HTTP/1.1 200 Ok
3347- ...You can&#8217;t vote in this poll because you&#8217;re not...
3348- ...a member of Ubuntu Team...
3349-
3350-
3351- By providing the token we will be able to see our current vote.
3352-
3353- >>> print http(r"""
3354- ... POST /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
3355- ... Authorization: Basic foo.bar@canonical.com:test
3356- ... Content-Type: application/x-www-form-urlencoded
3357- ...
3358- ... token=xn9FDCTp4m&showvote=Show+My+Vote&option_12=&option_13=&option_14=&option_15=""")
3359- HTTP/1.1 200 Ok
3360- ...
3361- <p>Your current vote is as follows:</p>
3362- <p>
3363- <BLANKLINE>
3364- </p>
3365- <p>
3366- <BLANKLINE>
3367- <b>1</b>.
3368- Option 1
3369- <BLANKLINE>
3370- </p>
3371- <p>
3372- <BLANKLINE>
3373- <b>2</b>.
3374- Option 2
3375- <BLANKLINE>
3376- </p>
3377- <p>
3378- <BLANKLINE>
3379- <b>3</b>.
3380- Option 4
3381- <BLANKLINE>
3382- </p>
3383- ...
3384-
3385-
3386- It's also possible to change the vote, if wanted.
3387-
3388- >>> print http(r"""
3389- ... POST /~ubuntu-team/+poll/never-closes2/+vote HTTP/1.1
3390- ... Authorization: Basic foo.bar@canonical.com:test
3391- ... Content-Type: application/x-www-form-urlencoded
3392- ...
3393- ... token=xn9FDCTp4m&option_12=2&option_13=3&option_14=4&option_15=1&changevote=Change+Vote""")
3394- HTTP/1.1 200 Ok
3395- ...
3396- ...Your vote was changed successfully.</p>
3397- ...
3398- <p>Your current vote is as follows:</p>
3399- <p>
3400- <BLANKLINE>
3401- <b>1</b>.
3402- Option 4
3403- <BLANKLINE>
3404- </p>
3405- <p>
3406- <BLANKLINE>
3407- <b>2</b>.
3408- Option 1
3409- <BLANKLINE>
3410- </p>
3411- <p>
3412- <BLANKLINE>
3413- <b>3</b>.
3414- Option 2
3415- <BLANKLINE>
3416- </p>
3417- <p>
3418- <BLANKLINE>
3419- <b>4</b>.
3420- Option 3
3421- <BLANKLINE>
3422- </p>
3423- ...
3424-
3425-
3426- Now we go to another poll in which name16 voted. But this time it's a public
3427- one, so there's no need to provide the token to see the current vote.
3428-
3429- >>> print http(r"""
3430- ... GET /~ubuntu-team/+poll/never-closes3 HTTP/1.1
3431- ... Authorization: Basic foo.bar@canonical.com:test
3432- ... """)
3433- HTTP/1.1 303 See Other
3434- ...
3435- Location: http://localhost/~ubuntu-team/+poll/never-closes3/+vote
3436- ...
3437-
3438- >>> print http(r"""
3439- ... GET /~ubuntu-team/+poll/never-closes3/+vote HTTP/1.1
3440- ... Authorization: Basic foo.bar@canonical.com:test
3441- ... """)
3442- HTTP/1.1 200 Ok
3443- ...
3444- <p>Your current vote is as follows:</p>
3445- <p>
3446- <BLANKLINE>
3447- <b>1</b>.
3448- Option 1
3449- <BLANKLINE>
3450- </p>
3451- <p>
3452- <BLANKLINE>
3453- <b>2</b>.
3454- Option 2
3455- <BLANKLINE>
3456- </p>
3457- <p>
3458- <BLANKLINE>
3459- <b>3</b>.
3460- Option 3
3461- <BLANKLINE>
3462- </p>
3463- <p>
3464- <BLANKLINE>
3465- <b>4</b>.
3466- Option 4
3467- <BLANKLINE>
3468- </p>
3469- ...
3470-
3471-
3472- Now we change the vote and we see the new vote displayed as our current
3473- vote.
3474-
3475- >>> print http(r"""
3476- ... POST /~ubuntu-team/+poll/never-closes3/+vote HTTP/1.1
3477- ... Authorization: Basic foo.bar@canonical.com:test
3478- ... Content-Type: application/x-www-form-urlencoded
3479- ...
3480- ... option_16=4&option_17=2&option_18=1&option_19=3&changevote=Change+Vote""")
3481- HTTP/1.1 200 Ok
3482- ...
3483- <p>Your current vote is as follows:</p>
3484- <p>
3485- <BLANKLINE>
3486- <b>1</b>.
3487- Option 3
3488- <BLANKLINE>
3489- </p>
3490- <p>
3491- <BLANKLINE>
3492- <b>2</b>.
3493- Option 2
3494- <BLANKLINE>
3495- </p>
3496- <p>
3497- <BLANKLINE>
3498- <b>3</b>.
3499- Option 4
3500- <BLANKLINE>
3501- </p>
3502- <p>
3503- <BLANKLINE>
3504- <b>4</b>.
3505- Option 1
3506- <BLANKLINE>
3507- </p>
3508- ...
3509-
3510-
3511- Logged in as mark@example.com (which is a member of ubuntu-team), go to a public
3512- condorcet-style poll that's still open and get redirected to a page where
3513- it's possible to vote (and see the current vote).
3514-
3515- >>> print http(r"""
3516- ... GET /~ubuntu-team/+poll/never-closes3 HTTP/1.1
3517- ... Authorization: Basic mark@example.com:test
3518- ... """)
3519- HTTP/1.1 303 See Other
3520- ...
3521- Location: http://localhost/~ubuntu-team/+poll/never-closes3/+vote
3522- ...
3523-
3524-
3525- And here we'll see the form which says you haven't voted yet and allows you
3526- to vote.
3527-
3528- >>> print http(r"""
3529- ... GET /~ubuntu-team/+poll/never-closes3/+vote HTTP/1.1
3530- ... Authorization: Basic mark@example.com:test
3531- ... """)
3532- HTTP/1.1 200 Ok
3533- ...
3534- ...Your current vote...
3535- ...You have not yet voted in this poll...
3536- ...Rank options in order...
3537- ...
3538
3539=== removed file 'lib/lp/registry/stories/team-polls/xx-poll-confirm-vote.txt'
3540--- lib/lp/registry/stories/team-polls/xx-poll-confirm-vote.txt 2009-08-21 18:46:34 +0000
3541+++ lib/lp/registry/stories/team-polls/xx-poll-confirm-vote.txt 1970-01-01 00:00:00 +0000
3542@@ -1,89 +0,0 @@
3543- Logged in as 'jdub' (which voted in the director-2004 poll), let's see the
3544- results of the director-2004 poll.
3545-
3546- >>> print http(r"""
3547- ... GET /~ubuntu-team/+poll/director-2004 HTTP/1.1
3548- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
3549- ... """)
3550- HTTP/1.1 200 Ok
3551- ...
3552- ...2004 Director's Elections...
3553- ...
3554- ...This was a secret poll: your vote is identified only by the key...
3555- ...you were given when you voted. To view your vote you must enter...
3556- ...your key:...
3557- ...Results...
3558- ...This is the pairwise matrix for this poll...
3559- ...
3560-
3561-
3562- Now let's see if jdub's vote was stored correctly, by entering the token he
3563- got when voting.
3564-
3565- >>> print http(r"""
3566- ... POST /~ubuntu-team/+poll/director-2004 HTTP/1.1
3567- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
3568- ... Content-Type: application/x-www-form-urlencoded
3569- ...
3570- ... token=9WjxQq2V9p&showvote=Show+My+Vote""")
3571- HTTP/1.1 200 Ok
3572- ...
3573- <p>Your vote was as follows:</p>
3574- <p>
3575- <BLANKLINE>
3576- <b>1</b>.
3577- D
3578- <BLANKLINE>
3579- </p>
3580- <p>
3581- <BLANKLINE>
3582- <b>2</b>.
3583- B
3584- <BLANKLINE>
3585- </p>
3586- <p>
3587- <BLANKLINE>
3588- <b>3</b>.
3589- A
3590- <BLANKLINE>
3591- </p>
3592- <p>
3593- <BLANKLINE>
3594- <b>3</b>.
3595- C
3596- <BLANKLINE>
3597- </p>
3598- ...
3599-
3600-
3601- Now we'll see the results of the leader-2004 poll, in which jdub also
3602- voted.
3603-
3604- >>> print http(r"""
3605- ... GET /~ubuntu-team/+poll/leader-2004 HTTP/1.1
3606- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
3607- ... """)
3608- HTTP/1.1 200 Ok
3609- ...
3610- ...2004 Leader's Elections...
3611- ...
3612- ...This was a secret poll: your vote is identified only by the key...
3613- ...you were given when you voted. To view your vote you must enter...
3614- ...your key:...
3615- ...
3616-
3617-
3618- And now we confirm his vote on this poll too.
3619-
3620- >>> print http(r"""
3621- ... POST /~ubuntu-team/+poll/leader-2004 HTTP/1.1
3622- ... Authorization: Basic amVmZi53YXVnaEB1YnVudHVsaW51eC5jb206amR1Yg==
3623- ... Content-Type: application/x-www-form-urlencoded
3624- ...
3625- ... token=W7gR5mjNrX&showvote=Show+My+Vote""")
3626- HTTP/1.1 200 Ok
3627- ...
3628- <p>Your vote was for
3629- <BLANKLINE>
3630- <b>Jack Crawford</b></p>
3631- ...
3632
3633=== removed file 'lib/lp/registry/stories/team-polls/xx-poll-results.txt'
3634--- lib/lp/registry/stories/team-polls/xx-poll-results.txt 2009-11-15 18:21:10 +0000
3635+++ lib/lp/registry/stories/team-polls/xx-poll-results.txt 1970-01-01 00:00:00 +0000
3636@@ -1,69 +0,0 @@
3637-First we check all polls of 'ubuntu-team'.
3638-
3639- >>> anon_browser.open("http://launchpad.dev/~ubuntu-team")
3640- >>> anon_browser.getLink('Show polls').click()
3641- >>> print find_main_content(anon_browser.contents)
3642- <...
3643- ...Current polls...
3644- ...A random poll that never closes...
3645- ...A second random poll that never closes...
3646- ...A third random poll that never closes...
3647- ...Closed polls...
3648- ...2004 Director's Elections...
3649- ...2004 Leader's Elections...
3650-
3651-
3652- Check the results of a closed simple-style poll.
3653-
3654- >>> anon_browser.open("http://launchpad.dev/~ubuntu-team/+poll/leader-2004")
3655- >>> print find_main_content(anon_browser.contents)
3656- <...
3657- ...Who's going to be the next leader?...
3658- ...Results...
3659- ...
3660- <td>
3661- Francis Dolarhyde
3662- <BLANKLINE>
3663- </td>
3664- <td>1</td>
3665- ...
3666- <td>
3667- Jack Crawford
3668- <BLANKLINE>
3669- </td>
3670- <td>1</td>
3671- ...
3672- <td>
3673- Will Graham
3674- <BLANKLINE>
3675- </td>
3676- <td>2</td>
3677- ...
3678-
3679-
3680- Check the results of a closed condorcet-style poll.
3681-
3682- >>> anon_browser.open("http://launchpad.dev/~ubuntu-team/+poll/director-2004")
3683- >>> print find_main_content(anon_browser.contents)
3684- <...
3685- ...Who's going to be the next director?...
3686- ...Results...
3687- ...
3688- ...A...
3689- ...2...
3690- ...2...
3691- ...2...
3692- ...B...
3693- ...2...
3694- ...2...
3695- ...2...
3696- ...C...
3697- ...1...
3698- ...1...
3699- ...1...
3700- ...D...
3701- ...2...
3702- ...1...
3703- ...2...
3704- ...
3705-
3706
3707=== modified file 'lib/lp/registry/stories/team/xx-team-home.txt'
3708--- lib/lp/registry/stories/team/xx-team-home.txt 2010-04-22 17:18:29 +0000
3709+++ lib/lp/registry/stories/team/xx-team-home.txt 2010-12-16 14:48:27 +0000
3710@@ -1,4 +1,5 @@
3711-= A team's home page =
3712+A team's home page
3713+==================
3714
3715 The home page of a public team is visible to everyone.
3716
3717@@ -59,17 +60,6 @@
3718 Languages:
3719 English
3720
3721-The polls portlet is only shown if current polls exist.
3722-
3723- >>> print extract_text(find_tag_by_id(browser.contents, 'polls'))
3724- Polls
3725- A random poll that never closes...
3726- Show polls
3727-
3728- >>> browser.open('http://launchpad.dev/~launchpad')
3729- >>> print find_tag_by_id(browser.contents, 'polls')
3730- None
3731-
3732 The subteam-of portlet is not shown if the team is not a subteam.
3733
3734 >>> browser.open('http://launchpad.dev/~ubuntu-team')
3735@@ -190,7 +180,8 @@
3736 ...
3737
3738
3739-== Team admins ==
3740+Team admins
3741+-----------
3742
3743 Team owners and admins can see a link to approve and decline applicants.
3744
3745@@ -207,7 +198,8 @@
3746 <Link text='Approve or decline members' url='.../+editproposedmembers'>
3747
3748
3749-== Non members ==
3750+Non members
3751+-----------
3752
3753 No Privileges Person is not a member of the Ubuntu team.
3754
3755@@ -220,7 +212,8 @@
3756 He can see the contact address, and the link explains the email
3757 will actually go to the team's administrators.
3758
3759- >>> print extract_text(find_tag_by_id(user_browser.contents, 'contact-email'))
3760+ >>> print extract_text(
3761+ ... find_tag_by_id(user_browser.contents, 'contact-email'))
3762 Email:
3763 support@ubuntu.com
3764 >>> content = find_tag_by_id(user_browser.contents, 'contact-user')
3765
3766=== removed file 'lib/lp/registry/templates/poll-edit.pt'
3767--- lib/lp/registry/templates/poll-edit.pt 2009-08-18 20:24:56 +0000
3768+++ lib/lp/registry/templates/poll-edit.pt 1970-01-01 00:00:00 +0000
3769@@ -1,34 +0,0 @@
3770-<html
3771- xmlns="http://www.w3.org/1999/xhtml"
3772- xmlns:tal="http://xml.zope.org/namespaces/tal"
3773- xmlns:metal="http://xml.zope.org/namespaces/metal"
3774- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
3775- metal:use-macro="view/macro:page/main_only"
3776- i18n:domain="launchpad"
3777->
3778-
3779-<body>
3780-
3781- <div metal:fill-slot="main">
3782-
3783- <div tal:condition="context/isNotYetOpened">
3784- <div metal:use-macro="context/@@launchpad_form/form">
3785- <h1 metal:fill-slot="heading">
3786- Edit poll &#8220;<span tal:replace="context/title" />&#8221;
3787- </h1>
3788- </div>
3789- </div>
3790-
3791- <div tal:condition="not: context/isNotYetOpened" id="not-editable">
3792- <h1>This poll can't be edited</h1>
3793-
3794- <p>Only polls that are not yet opened can be edited. As soon as a poll
3795- opens it can't be edited anymore.</p>
3796- </div>
3797-
3798- <tal:menu replace="structure view/@@+related-pages" />
3799-
3800- </div>
3801-
3802-</body>
3803-</html>
3804
3805=== removed file 'lib/lp/registry/templates/poll-index.pt'
3806--- lib/lp/registry/templates/poll-index.pt 2009-08-19 19:48:09 +0000
3807+++ lib/lp/registry/templates/poll-index.pt 1970-01-01 00:00:00 +0000
3808@@ -1,207 +0,0 @@
3809-<html
3810- xmlns="http://www.w3.org/1999/xhtml"
3811- xmlns:tal="http://xml.zope.org/namespaces/tal"
3812- xmlns:metal="http://xml.zope.org/namespaces/metal"
3813- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
3814- metal:use-macro="view/macro:page/main_side"
3815- i18n:domain="launchpad"
3816->
3817-<body>
3818-
3819-<tal:heading metal:fill-slot="heading">
3820- <h1 tal:content="context/title">Mozilla</h1>
3821-</tal:heading>
3822-
3823-<div metal:fill-slot="main">
3824-
3825- <tal:do-this-first replace="view/setUpTokenAndVotes" />
3826-
3827- <div
3828- class="highlighted"
3829- tal:content="structure context/proposition/fmt:text-to-html"
3830- />
3831- <br />
3832-
3833- <p tal:condition="not: context/getActiveOptions">
3834- This poll does not yet have any voting options. Please <a
3835- href="+newoption">add an option</a>. Note, you need more than one option
3836- for a real poll, of course :-)
3837- </p>
3838-
3839- <div class="two-column-list">
3840- <dl>
3841- <dt>Opens:</dt>
3842- <dd
3843- tal:attributes="title context/dateopens/fmt:datetime"
3844- tal:content="context/dateopens/fmt:approximatedate" />
3845- </dl>
3846-
3847- <dl>
3848- <dt>Type:</dt>
3849- <dd tal:content="context/type/title" />
3850- </dl>
3851-
3852- <dl>
3853- <dt>Closes:</dt>
3854- <dd
3855- tal:attributes="title context/datecloses/fmt:datetime"
3856- tal:content="context/datecloses/fmt:approximatedate" />
3857- </dl>
3858-
3859- <dl>
3860- <dt>Secrecy:</dt>
3861- <dd tal:content="context/secrecy/title" />
3862- </dl>
3863- </div>
3864- <br />
3865-
3866- <tal:details replace="structure context/@@+portlet-options" />
3867- <br />
3868-
3869- <tal:is_open condition="context/isOpen">
3870- <p tal:condition="not: request/lp:person">
3871- You need to <a href="+login">login to vote</a>.
3872- </p>
3873- </tal:is_open>
3874-
3875- <tal:block condition="context/isClosed">
3876-
3877- <h2>Voting has closed</h2>
3878-
3879- <p>Voting closed
3880- <span
3881- tal:attributes="title context/datecloses/fmt:datetime"
3882- tal:content="context/datecloses/fmt:displaydate" />.
3883- </p>
3884-
3885- <tal:block condition="view/userVoted">
3886- <tal:block condition="view/isSecret">
3887- <tal:block condition="not: view/gotTokenAndVotes">
3888- <p>
3889- This was a secret poll: your vote is identified only by the key
3890- you were given when you voted. To view your vote you must enter
3891- your key:
3892- </p>
3893- <form action="" method="POST">
3894- <input type="text" name="token" />
3895- <input type="submit" value="Show My Vote" name="showvote" />
3896- </form>
3897- </tal:block>
3898- </tal:block>
3899-
3900- <tal:block condition="view/gotTokenAndVotes">
3901- <tal:block condition="view/isSimple">
3902- <p>Your vote was for
3903- <b tal:condition="not: view/currentVote/option">
3904- none of the options.
3905- </b>
3906- <b tal:condition="view/currentVote/option"
3907- tal:content="view/currentVote/option/name" /></p>
3908- </tal:block>
3909-
3910- <tal:block condition="view/isCondorcet">
3911- <tal:block condition="view/currentVotes">
3912- <p>Your vote was as follows:</p>
3913- <p tal:repeat="vote view/currentVotes">
3914- <tal:block tal:condition="vote/preference">
3915- <b tal:content="vote/preference" />.
3916- <span tal:replace="vote/option/name" />
3917- </tal:block>
3918- </p>
3919- </tal:block>
3920-
3921- <tal:block condition="not: view/currentVotes">
3922- <p>You haven't voted for any of the existing options.</p>
3923- </tal:block>
3924- </tal:block>
3925-
3926- </tal:block>
3927- </tal:block>
3928-
3929- <h2>Results</h2>
3930-
3931- <tal:block condition="view/isSimple">
3932- <tal:block define="winners context/getWinners">
3933- <p tal:condition="winners">The winner(s) of this poll is(are)
3934- <tal:block repeat="winner winners">
3935- <b tal:content="winner/title"
3936- /><span tal:condition="not: repeat/winner/end">,</span>
3937- </tal:block>
3938- </p>
3939-
3940- <p tal:condition="not: winners">This poll has no winner(s).</p>
3941- </tal:block>
3942-
3943- <p>Here are the number of votes each option received.</p>
3944- <table class="listing">
3945- <thead>
3946- <tr>
3947- <th>Option</th>
3948- <th>Votes</th>
3949- </tr>
3950- </thead>
3951-
3952- <tr tal:repeat="option context/getAllOptions">
3953- <tal:block define="votes python: view.getVotesByOption(option)">
3954- <td>
3955- <span tal:replace="option/title" />
3956- <tal:block tal:condition="not: option/active">
3957- (Inactive)
3958- </tal:block>
3959- </td>
3960- <td tal:content="votes">
3961- </td>
3962- </tal:block>
3963- </tr>
3964- </table>
3965- </tal:block>
3966-
3967- <tal:block condition="view/isCondorcet">
3968- <p>This is the pairwise matrix for this poll.</p>
3969-
3970- <table border="2"
3971- tal:define="pairwise_matrix view/getPairwiseMatrixWithHeaders">
3972- <tr tal:repeat="row pairwise_matrix">
3973- <tal:block repeat="column pairwise_matrix">
3974- <tal:block tal:define="x repeat/row/index; y repeat/column/index">
3975- <td tal:condition="python: x == y"
3976- style="background-color: black" />
3977-
3978- <tal:block condition="python: x != y">
3979- <td tal:condition="python: x != 0 and y != 0"
3980- style="text-align: right">
3981- <span tal:replace="python: pairwise_matrix[x][y]" />
3982- </td>
3983- <td tal:condition="python: x == 0 or y == 0">
3984- <span tal:replace="python: pairwise_matrix[x][y]" />
3985- </td>
3986- </tal:block>
3987- </tal:block>
3988- </tal:block>
3989- </tr>
3990- </table>
3991- </tal:block>
3992-
3993- </tal:block>
3994-
3995- <tal:block condition="context/isNotYetOpened">
3996- <h2>Voting hasn't opened yet</h2>
3997-
3998- <p>
3999- The vote will commence
4000- <span
4001- tal:attributes="title context/dateopens/fmt:datetime"
4002- tal:content="context/dateopens/fmt:displaydate" />.
4003- </p>
4004- </tal:block>
4005-
4006-</div>
4007-
4008-<div metal:fill-slot="side">
4009- <div id="object-actions" class="top-portlet">
4010- <tal:menu replace="structure view/@@+global-actions" />
4011- </div>
4012-</div>
4013-
4014-</body>
4015-</html>
4016
4017=== removed file 'lib/lp/registry/templates/poll-newoption.pt'
4018--- lib/lp/registry/templates/poll-newoption.pt 2009-08-19 19:48:09 +0000
4019+++ lib/lp/registry/templates/poll-newoption.pt 1970-01-01 00:00:00 +0000
4020@@ -1,36 +0,0 @@
4021-<html
4022- xmlns="http://www.w3.org/1999/xhtml"
4023- xmlns:tal="http://xml.zope.org/namespaces/tal"
4024- xmlns:metal="http://xml.zope.org/namespaces/metal"
4025- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4026- metal:use-macro="view/macro:page/main_only"
4027- i18n:domain="launchpad"
4028->
4029-
4030-<body>
4031-
4032- <div metal:fill-slot="main">
4033-
4034- <tal:block condition="context/isNotYetOpened">
4035- <div metal:use-macro="context/@@launchpad_form/form">
4036-
4037- <h1 metal:fill-slot="heading">
4038- Add a poll option
4039- </h1>
4040-
4041- </div>
4042- </tal:block>
4043-
4044- <tal:block condition="not: context/isNotYetOpened">
4045- <p class="error message" tal:condition="context/isClosed">
4046- You can&#8217;t add new options because the poll is already closed.
4047- </p>
4048- <p class="error message" tal:condition="context/isOpen">
4049- You can&#8217;t add new options because the poll is already open.
4050- </p>
4051- </tal:block>
4052-
4053- </div>
4054-
4055-</body>
4056-</html>
4057
4058=== removed file 'lib/lp/registry/templates/poll-portlet-details.pt'
4059--- lib/lp/registry/templates/poll-portlet-details.pt 2009-07-17 17:59:07 +0000
4060+++ lib/lp/registry/templates/poll-portlet-details.pt 1970-01-01 00:00:00 +0000
4061@@ -1,38 +0,0 @@
4062-<tal:root
4063- xmlns:tal="http://xml.zope.org/namespaces/tal"
4064- xmlns:metal="http://xml.zope.org/namespaces/metal"
4065- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4066- omit-tag="">
4067-
4068-<div class="portlet" id="portlet-details">
4069- <h2><span tal:replace="context/name" /></h2>
4070-
4071- <div class="portletBody portletContent">
4072-
4073- <b>Title:</b>
4074- <span tal:replace="context/title" /><br />
4075-
4076- <b>Voting team:</b>
4077- <a tal:attributes="href context/team/fmt:url"
4078- tal:content="context/team/displayname" /><br />
4079-
4080- <b>Opens:</b>
4081- <span
4082- tal:attributes="title context/dateopens/fmt:datetime"
4083- tal:content="context/dateopens/fmt:approximatedate" /><br />
4084-
4085- <b>Closes:</b>
4086- <span
4087- tal:attributes="title context/datecloses/fmt:datetime"
4088- tal:content="context/datecloses/fmt:approximatedate" /><br />
4089-
4090- <b>Type:</b>
4091- <span tal:replace="context/type/title" /><br />
4092-
4093- <b>Secrecy:</b>
4094- <span tal:replace="context/secrecy/title" /><br />
4095-
4096- </div>
4097-
4098-</div>
4099-</tal:root>
4100
4101=== removed file 'lib/lp/registry/templates/poll-portlet-options.pt'
4102--- lib/lp/registry/templates/poll-portlet-options.pt 2009-08-20 18:25:13 +0000
4103+++ lib/lp/registry/templates/poll-portlet-options.pt 1970-01-01 00:00:00 +0000
4104@@ -1,46 +0,0 @@
4105-<tal:root
4106- xmlns:tal="http://xml.zope.org/namespaces/tal"
4107- xmlns:metal="http://xml.zope.org/namespaces/metal"
4108- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4109- omit-tag="">
4110-
4111-<div class="portlet" id="portlet-options">
4112-
4113- <h2>Voting options</h2>
4114- <tal:block condition="context/getAllOptions">
4115- <table class="listing" id="options">
4116- <thead>
4117- <tr>
4118- <th>Name</th>
4119- <th>Title</th>
4120- <th>Active</th>
4121- <th tal:condition="context/required:launchpad.Edit"></th>
4122- </tr>
4123- </thead>
4124- <tr tal:repeat="polloption context/getAllOptions">
4125- <td tal:content="polloption/name">mjg59</td>
4126- <td tal:content="polloption/title/fmt:break-long-words">
4127- This guy rocks!
4128- </td>
4129- <td>
4130- <tal:is_active condition="polloption/active">Yes</tal:is_active>
4131- <tal:inactive condition="not: polloption/active">No</tal:inactive>
4132- </td>
4133- <td tal:condition="context/required:launchpad.Edit">
4134- <a tal:attributes="href polloption/fmt:url"
4135- ><img src="/@@/edit" alt="[Edit]"
4136- title="Change this option details" /></a>
4137- </td>
4138- </tr>
4139- </table>
4140- </tal:block>
4141-
4142- <p class="warning message" tal:condition="not: context/getAllOptions">
4143- This poll doesn't have any options for people to vote on yet.
4144- Make sure you add some options before the poll opens!
4145- </p>
4146-
4147- <tal:new-option replace="structure context/menu:overview/addnew/render" />
4148-
4149-</div>
4150-</tal:root>
4151
4152=== removed file 'lib/lp/registry/templates/poll-vote-condorcet.pt'
4153--- lib/lp/registry/templates/poll-vote-condorcet.pt 2009-08-19 19:48:09 +0000
4154+++ lib/lp/registry/templates/poll-vote-condorcet.pt 1970-01-01 00:00:00 +0000
4155@@ -1,130 +0,0 @@
4156-<html
4157- xmlns="http://www.w3.org/1999/xhtml"
4158- xmlns:tal="http://xml.zope.org/namespaces/tal"
4159- xmlns:metal="http://xml.zope.org/namespaces/metal"
4160- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4161- metal:use-macro="view/macro:page/main_only"
4162- i18n:domain="launchpad"
4163->
4164-<body>
4165-
4166- <tal:heading metal:fill-slot="heading">
4167- <h1 tal:content="context/title">Mozilla</h1>
4168- </tal:heading>
4169-
4170- <div metal:fill-slot="main">
4171-
4172- <tal:open-poll condition="context/isOpen">
4173- <tal:can-vote condition="view/userCanVote">
4174- <p
4175- tal:condition="view/feedback"
4176- tal:content="view/feedback"
4177- class="informational message"
4178- />
4179-
4180- <div class="highlighted" style="font-size: 80%;">
4181- <tal:proposition replace="structure context/proposition/fmt:text-to-html">
4182- The proposition goes here.
4183- </tal:proposition>
4184- </div>
4185-
4186- <form action="" method="POST">
4187-
4188- <tal:block condition="view/userVoted">
4189- <tal:block condition="view/isSecret">
4190- <h2>You must enter your vote key</h2>
4191-
4192- <p>This is a secret poll &mdash;
4193- your vote is identified only by the key you
4194- were given when you voted. To view or change your vote you must enter
4195- your key:</p>
4196-
4197- <input type="text" name="token"
4198- tal:attributes="value view/token|nothing" />
4199- <br />
4200- <br />
4201- </tal:block>
4202- </tal:block>
4203-
4204- <table cols="2" id="your-vote">
4205- <tr>
4206- <td>
4207- <h2>Your current vote</h2>
4208- <tal:block condition="not: view/userVoted">
4209- <p>You have not yet voted in this poll.</p>
4210- </tal:block>
4211-
4212- <tal:block condition="view/userVoted">
4213- <tal:block condition="view/gotTokenAndVotes">
4214- <tal:block condition="view/currentVotes">
4215- <p>Your current vote is as follows:</p>
4216- <p tal:repeat="vote view/currentVotes">
4217- <tal:block tal:condition="vote/preference">
4218- <b tal:content="vote/preference" />.
4219- <span tal:replace="vote/option/name" />
4220- </tal:block>
4221- </p>
4222- </tal:block>
4223-
4224- <tal:block condition="not: view/currentVotes">
4225- <p>You haven't manifested preference for any of the existing
4226- options.</p>
4227- </tal:block>
4228- </tal:block>
4229-
4230- <tal:block condition="not: view/gotTokenAndVotes">
4231- <p>You have voted in this poll. Launchpad can display your vote
4232- once you have entered your vote key.</p>
4233-
4234- <input type="submit" value="Show My Vote" name="showvote" />
4235- </tal:block>
4236- </tal:block>
4237- </td>
4238- <td>
4239- <tal:block condition="not: view/userVoted">
4240- <h2>Rank options in order of preference</h2>
4241- </tal:block>
4242-
4243- <tal:block condition="view/userVoted">
4244- <h2>Change your vote</h2>
4245- </tal:block>
4246-
4247- <p>Enter 1 next to your most preferred option, 2 next to your second
4248- preference, and so on. You may mark two or more options equally, or
4249- leave some options unmarked, if desired.</p>
4250-
4251- <tal:block repeat="option context/getActiveOptions">
4252- <input type="text" size="2"
4253- tal:attributes="name string:option_${option/id}" />
4254- <span tal:replace="option/name" />
4255- <br />
4256- </tal:block>
4257- <br />
4258-
4259- <tal:block condition="view/userVoted">
4260- <input type="submit" value="Change Vote" name="changevote" />
4261- </tal:block>
4262-
4263- <tal:block condition="not: view/userVoted">
4264- <input type="submit" value="Vote" name="vote" />
4265- </tal:block>
4266- or <a tal:attributes="href context/team/fmt:url/+polls">Cancel</a>
4267- </td>
4268- </tr>
4269- </table>
4270- </form>
4271- </tal:can-vote>
4272-
4273- <p tal:condition="not: view/userCanVote" class="informational message">
4274- You can&#8217;t vote in this poll because you&#8217;re not
4275- a member of <span tal:replace="context/team/displayname" />.
4276- </p>
4277- </tal:open-poll>
4278-
4279- <p tal:condition="not: context/isOpen" class="informational message">
4280- This poll is already closed.
4281- </p>
4282-
4283- </div>
4284-</body>
4285-</html>
4286
4287=== removed file 'lib/lp/registry/templates/poll-vote-simple.pt'
4288--- lib/lp/registry/templates/poll-vote-simple.pt 2009-08-19 19:48:09 +0000
4289+++ lib/lp/registry/templates/poll-vote-simple.pt 1970-01-01 00:00:00 +0000
4290@@ -1,141 +0,0 @@
4291-<html
4292- xmlns="http://www.w3.org/1999/xhtml"
4293- xmlns:tal="http://xml.zope.org/namespaces/tal"
4294- xmlns:metal="http://xml.zope.org/namespaces/metal"
4295- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4296- metal:use-macro="view/macro:page/main_only"
4297- i18n:domain="launchpad"
4298->
4299-<body>
4300-
4301- <tal:heading metal:fill-slot="heading">
4302- <h1 tal:content="context/title">Mozilla</h1>
4303- </tal:heading>
4304-
4305- <div metal:fill-slot="main">
4306-
4307- <tal:open-poll condition="context/isOpen">
4308-
4309- <tal:can-vote condition="view/userCanVote">
4310- <p
4311- tal:condition="view/feedback"
4312- tal:content="view/feedback"
4313- class="informational message"
4314- />
4315-
4316- <div
4317- class="highlighted"
4318- tal:content="structure context/proposition/fmt:text-to-html"
4319- />
4320-
4321- <form action="" method="POST">
4322-
4323- <tal:block condition="view/userVoted">
4324- <tal:block condition="view/isSecret">
4325- <h2>You must enter your vote key</h2>
4326-
4327- <p>This is a secret poll &mdash;
4328- your vote is identified only by the key you
4329- were given when you voted. To view or change your vote you must enter
4330- your key:</p>
4331-
4332- <input type="text" name="token"
4333- tal:attributes="value view/token|nothing" />
4334- <br />
4335- <br />
4336- </tal:block>
4337- </tal:block>
4338-
4339- <table cols="2" id="your-vote">
4340- <tr>
4341- <td>
4342- <h2>Your current vote</h2>
4343- <tal:block condition="not: view/userVoted">
4344- <p>You have not yet voted in this poll.</p>
4345- </tal:block>
4346-
4347- <tal:block condition="view/userVoted">
4348- <tal:block condition="view/gotTokenAndVotes">
4349- <p>Your current vote is for
4350- <b tal:condition="not: view/currentVote/option">
4351- none of the options.
4352- </b>
4353- <b tal:condition="view/currentVote/option"
4354- tal:content="view/currentVote/option/name" />
4355- </p>
4356- </tal:block>
4357-
4358- <tal:block condition="not: view/gotTokenAndVotes">
4359- <p>You have voted in this poll. Launchpad can display your vote
4360- once you have entered your vote key.</p>
4361-
4362- <input type="submit" value="Show My Vote" name="showvote" />
4363- </tal:block>
4364- </tal:block>
4365- </td>
4366- <td>
4367- <tal:block condition="not: view/userVoted">
4368- <h2>Vote now</h2>
4369- <p>Choose one option</p>
4370-
4371- <label>
4372- <input type="radio" name="newoption" value="donotvote"
4373- checked="checked" />
4374- I'm not voting yet
4375- </label>
4376- <br />
4377- </tal:block>
4378-
4379- <tal:block condition="view/userVoted">
4380- <h2>Change your vote</h2>
4381- <p>Choose one option</p>
4382-
4383- <label>
4384- <input type="radio" name="newoption" value="donotchange"
4385- checked="checked" />
4386- Don't change my vote
4387- </label>
4388- <br />
4389- </tal:block>
4390-
4391- <tal:block repeat="option context/getActiveOptions">
4392- <label>
4393- <input type="radio" name="newoption"
4394- tal:attributes="value option/id" />
4395- <span tal:replace="option/name" />
4396- </label>
4397- <br />
4398- </tal:block>
4399-
4400- <tal:block condition="context/allowspoilt">
4401- <label>
4402- <input type="radio" name="newoption" value="none" />
4403- None of these options
4404- </label>
4405- <br />
4406- </tal:block>
4407- <br />
4408-
4409- <input type="submit" value="Continue" name="continue" />
4410- or <a tal:attributes="href context/team/fmt:url/+polls">Cancel</a>
4411- </td>
4412- </tr>
4413- </table>
4414- </form>
4415- </tal:can-vote>
4416-
4417- <p tal:condition="not: view/userCanVote" class="informational message">
4418- You can&#8217;t vote in this poll because you&#8217;re not
4419- a member of <span tal:replace="context/team/displayname" />.
4420- </p>
4421-
4422- </tal:open-poll>
4423-
4424- <p tal:condition="not: context/isOpen" class="informational message">
4425- This poll is already closed.
4426- </p>
4427-
4428- </div>
4429-
4430-</body>
4431-</html>
4432
4433=== removed file 'lib/lp/registry/templates/polloption-edit.pt'
4434--- lib/lp/registry/templates/polloption-edit.pt 2009-08-18 20:24:56 +0000
4435+++ lib/lp/registry/templates/polloption-edit.pt 1970-01-01 00:00:00 +0000
4436@@ -1,37 +0,0 @@
4437-<html
4438- xmlns="http://www.w3.org/1999/xhtml"
4439- xmlns:tal="http://xml.zope.org/namespaces/tal"
4440- xmlns:metal="http://xml.zope.org/namespaces/metal"
4441- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4442- metal:use-macro="view/macro:page/main_only"
4443- i18n:domain="launchpad"
4444->
4445-
4446-<body>
4447-
4448- <div metal:fill-slot="main">
4449-
4450- <tal:block condition="context/poll/isNotYetOpened">
4451- <div metal:use-macro="context/@@launchpad_form/form">
4452-
4453- <h1 metal:fill-slot="heading">
4454- Edit option
4455- &#8220;<span tal:replace="context/name" />&#8221;
4456- </h1>
4457-
4458- </div>
4459- </tal:block>
4460-
4461- <tal:block condition="not: context/poll/isNotYetOpened">
4462- <p class="error message" tal:condition="context/poll/isClosed">
4463- You can&#8217;t edit any options because the poll is already closed.
4464- </p>
4465- <p class="error message" tal:condition="context/poll/isOpen">
4466- You can&#8217;t edit any options because the poll is already open.
4467- </p>
4468- </tal:block>
4469-
4470- </div>
4471-
4472-</body>
4473-</html>
4474
4475=== modified file 'lib/lp/registry/templates/team-index.pt'
4476--- lib/lp/registry/templates/team-index.pt 2010-10-10 21:54:16 +0000
4477+++ lib/lp/registry/templates/team-index.pt 2010-12-16 14:48:27 +0000
4478@@ -35,7 +35,6 @@
4479 </metal:contact>
4480
4481 <tal:menu replace="structure view/@@+global-actions" />
4482- <tal:polls replace="structure context/@@+portlet-polls" />
4483
4484 </div>
4485
4486
4487=== removed file 'lib/lp/registry/templates/team-newpoll.pt'
4488--- lib/lp/registry/templates/team-newpoll.pt 2009-08-24 14:18:10 +0000
4489+++ lib/lp/registry/templates/team-newpoll.pt 1970-01-01 00:00:00 +0000
4490@@ -1,25 +0,0 @@
4491-<html
4492- xmlns="http://www.w3.org/1999/xhtml"
4493- xmlns:tal="http://xml.zope.org/namespaces/tal"
4494- xmlns:metal="http://xml.zope.org/namespaces/metal"
4495- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4496- metal:use-macro="view/macro:page/main_only"
4497- i18n:domain="launchpad"
4498->
4499-
4500-<body>
4501-
4502- <h1 metal:fill-slot="heading">
4503- Create a new poll
4504- </h1>
4505-
4506- <div metal:fill-slot="main">
4507-
4508- <div metal:use-macro="context/@@launchpad_form/form">
4509-
4510- </div>
4511-
4512- </div>
4513-
4514-</body>
4515-</html>
4516
4517=== modified file 'lib/lp/registry/templates/team-polls.pt'
4518--- lib/lp/registry/templates/team-polls.pt 2009-11-20 05:40:25 +0000
4519+++ lib/lp/registry/templates/team-polls.pt 2010-12-16 14:48:27 +0000
4520@@ -8,81 +8,19 @@
4521 >
4522 <body>
4523
4524- <metal:heading fill-slot="heading">
4525- <h1>Polls for <span tal:replace="context/displayname" /></h1>
4526- </metal:heading>
4527-
4528 <div metal:fill-slot="main">
4529
4530- <h2>Current polls</h2>
4531+ <h1>Polls no longer supported</h1>
4532
4533- <p tal:condition="not: view/has_current_polls">
4534- This team has no open polls nor polls that are not yet opened.
4535+ <p>
4536+ Launchpad no longer supports team polls. We have archived the data from
4537+ all previously conducted polls, which can be found as a comma-separated
4538+ values file at
4539+ <a href="http://dev.launchpad.net/PollFeatureRemoved">
4540+ http://dev.launchpad.net/PollFeatureRemoved</a>.
4541 </p>
4542
4543- <ul tal:condition="view/has_current_polls">
4544- <li tal:repeat="poll view/openpolls">
4545- <a tal:attributes="href poll/fmt:url">
4546- <span tal:replace="poll/title" />
4547- </a> - closes
4548- <span
4549- tal:attributes="title poll/datecloses/fmt:datetime"
4550- tal:content="poll/datecloses/fmt:displaydate" />.
4551-
4552- <tal:block define="user request/lp:person" condition="user">
4553- <tal:block condition="python: poll.personVoted(user)">
4554- You have
4555- <span tal:replace="poll/closesIn/fmt:approximateduration" />
4556- to change your vote if you wish.
4557- </tal:block>
4558-
4559- <tal:block condition="python: not poll.personVoted(user)">
4560- You have
4561- <span tal:replace="poll/closesIn/fmt:approximateduration" />
4562- left to vote in this poll.
4563- </tal:block>
4564- </tal:block>
4565-
4566- </li>
4567-
4568- <li tal:repeat="poll view/notyetopenedpolls">
4569- <a tal:attributes="href poll/fmt:url">
4570- <span tal:replace="poll/title" />
4571- </a> - opens
4572- <span
4573- tal:attributes="title poll/dateopens/fmt:datetime"
4574- tal:content="poll/dateopens/fmt:displaydate" />
4575- </li>
4576- </ul>
4577-
4578- <tal:block condition="view/closedpolls" >
4579- <h2>Closed polls</h2>
4580-
4581- <ul>
4582- <li tal:repeat="poll view/closedpolls">
4583- <a tal:attributes="href poll/fmt:url">
4584- <span tal:replace="poll/title" />
4585- </a> - closed
4586- <span
4587- tal:attributes="title poll/datecloses/fmt:datetime"
4588- tal:content="poll/datecloses/fmt:displaydate" />
4589- </li>
4590- </ul>
4591- </tal:block>
4592-
4593- <br />
4594- <tal:block tal:condition="request/lp:person">
4595- <ul tal:condition="context/required:launchpad.Edit">
4596- <li><a class="sprite add" href="+newpoll">Set up a new poll</a></li>
4597- </ul>
4598- </tal:block>
4599-
4600- <tal:block tal:condition="not: request/lp:person">
4601- <a href="+login">Log in as an admin to set up a new poll</a>
4602- </tal:block>
4603-
4604 </div>
4605
4606 </body>
4607 </html>
4608-
4609
4610=== removed file 'lib/lp/registry/templates/team-portlet-polls.pt'
4611--- lib/lp/registry/templates/team-portlet-polls.pt 2009-11-20 05:40:25 +0000
4612+++ lib/lp/registry/templates/team-portlet-polls.pt 1970-01-01 00:00:00 +0000
4613@@ -1,56 +0,0 @@
4614-<tal:root
4615- xmlns:tal="http://xml.zope.org/namespaces/tal"
4616- xmlns:metal="http://xml.zope.org/namespaces/metal"
4617- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
4618- omit-tag="">
4619-
4620- <div id="polls" class="portlet"
4621- tal:define="overview_menu context/menu:overview"
4622- tal:condition="view/should_show_polls_portlet">
4623- <h2>Polls</h2>
4624- <p tal:condition="not: view/has_current_polls">
4625- No current polls.
4626- </p>
4627-
4628- <ul tal:condition="view/has_current_polls">
4629- <li tal:repeat="poll view/openpolls">
4630- <a tal:attributes="href poll/fmt:url">
4631- <span tal:replace="poll/title" />
4632- </a> - closes
4633- <span
4634- tal:attributes="title poll/datecloses/fmt:datetime"
4635- tal:content="poll/datecloses/fmt:displaydate" />.
4636-
4637- <tal:block define="user request/lp:person" condition="user">
4638- <tal:block condition="python: poll.personVoted(user)">
4639- You have
4640- <span tal:replace="poll/closesIn/fmt:approximateduration" />
4641- to change your vote if you wish.
4642- </tal:block>
4643-
4644- <tal:block condition="python: not poll.personVoted(user)">
4645- You have
4646- <span tal:replace="poll/closesIn/fmt:approximateduration" />
4647- left to vote in this poll.
4648- </tal:block>
4649- </tal:block>
4650-
4651- </li>
4652-
4653- <li tal:condition="view/userIsOwner"
4654- tal:repeat="poll view/notyetopenedpolls">
4655- <a tal:attributes="href poll/fmt:url">
4656- <span tal:replace="poll/title" />
4657- </a> - opens
4658- <span
4659- tal:attributes="title poll/dateopens/fmt:datetime"
4660- tal:content="poll/dateopens/fmt:displaydate" />
4661- </li>
4662- </ul>
4663-
4664- <a tal:condition="view/should_show_polls_portlet"
4665- tal:replace="structure overview_menu/polls/fmt:link" />
4666- <a tal:replace="structure overview_menu/add_poll/fmt:link" />
4667-
4668- </div>
4669-</tal:root>
4670
4671=== modified file 'lib/lp/testing/factory.py'
4672--- lib/lp/testing/factory.py 2010-12-14 05:43:47 +0000
4673+++ lib/lp/testing/factory.py 2010-12-16 14:48:27 +0000
4674@@ -207,11 +207,6 @@
4675 TeamSubscriptionPolicy,
4676 )
4677 from lp.registry.interfaces.pocket import PackagePublishingPocket
4678-from lp.registry.interfaces.poll import (
4679- IPollSet,
4680- PollAlgorithm,
4681- PollSecrecy,
4682- )
4683 from lp.registry.interfaces.product import (
4684 IProductSet,
4685 License,
4686@@ -729,16 +724,6 @@
4687 naked_team.addMember(member, owner)
4688 return team
4689
4690- def makePoll(self, team, name, title, proposition,
4691- poll_type=PollAlgorithm.SIMPLE):
4692- """Create a new poll which starts tomorrow and lasts for a week."""
4693- dateopens = datetime.now(pytz.UTC) + timedelta(days=1)
4694- datecloses = dateopens + timedelta(days=7)
4695- return getUtility(IPollSet).new(
4696- team, name, title, proposition, dateopens, datecloses,
4697- PollSecrecy.SECRET, allowspoilt=True,
4698- poll_type=poll_type)
4699-
4700 def makeTranslationGroup(self, owner=None, name=None, title=None,
4701 summary=None, url=None):
4702 """Create a new, arbitrary `TranslationGroup`."""