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