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