|
| 1 | +--- |
| 2 | +myst: |
| 3 | + html_meta: |
| 4 | + "description": "Reference for the Poll browser views in experimental.doodle" |
| 5 | + "property=og:description": "Reference for the Poll browser views" |
| 6 | + "property=og:title": "Poll views" |
| 7 | + "keywords": "Plone, experimental.doodle, poll, view, results, browser" |
| 8 | +--- |
| 9 | + |
| 10 | +# Poll views |
| 11 | + |
| 12 | +`experimental.doodle` ships two browser views for `Poll` objects, both |
| 13 | +registered against `IPoll` on `IBrowserLayer`. |
| 14 | + |
| 15 | +## Vote form (`@@poll_view`) |
| 16 | + |
| 17 | +**Class:** `experimental.doodle.browser.poll.PollView` |
| 18 | +**Permission:** `zope2.View` (no login required) |
| 19 | +**Default view:** yes (`default_view` on the FTI since profile version `1002`) |
| 20 | + |
| 21 | +The vote form renders the poll title, description, and a `<form>` with: |
| 22 | + |
| 23 | +- a text field for the participant's name; |
| 24 | +- one `yes`/`no` radio pair per proposed time slot; |
| 25 | +- a submit button. |
| 26 | + |
| 27 | +### Form submission |
| 28 | + |
| 29 | +On submit the view: |
| 30 | + |
| 31 | +1. Strips the `name` field; returns a form error if the result is empty. |
| 32 | +2. Reads `votes_0`, `votes_1`, … from the request; absent values default |
| 33 | + to `"false"`. |
| 34 | +3. Calls `IVoteStorage(context).cast_vote(name, votes)`. |
| 35 | +4. Redirects to `@@poll_view` (Post/Redirect/Get pattern). |
| 36 | + |
| 37 | +Any `ValueError` from `cast_vote` is caught and shown as an inline error |
| 38 | +without a redirect. |
| 39 | + |
| 40 | +### Template helpers |
| 41 | + |
| 42 | +| Property | Returns | |
| 43 | +|---|---| |
| 44 | +| `options` | `[(index, label), ...]` where `label` is a human-readable date/time string. | |
| 45 | +| `error` | The current error string, or `None`. | |
| 46 | +| `participant_count` | Number of participants who have already voted. | |
| 47 | + |
| 48 | +## Results (`@@results`) |
| 49 | + |
| 50 | +**Class:** `experimental.doodle.browser.poll.PollResultsView` |
| 51 | +**Permission:** `zope2.View` |
| 52 | + |
| 53 | +The results view shows the aggregated tally and a participant list. It |
| 54 | +is read-only; there is no form or POST handling. |
| 55 | + |
| 56 | +### Template helpers |
| 57 | + |
| 58 | +| Property | Returns | |
| 59 | +|---|---| |
| 60 | +| `rows` | `[(label, yes_count, max_count), ...]`, one row per slot. `max_count` is the highest yes-count across all slots, used to compute proportional bar widths. | |
| 61 | +| `participants` | `list[str]`, participant names sorted alphabetically. | |
| 62 | + |
| 63 | +A `@@results` link appears on the vote form. A "Back to poll" link |
| 64 | +returns to `@@poll_view` from the results. |
| 65 | + |
| 66 | +## Styles |
| 67 | + |
| 68 | +Both views include `++plone++experimental.doodle/poll.css` from |
| 69 | +`src/experimental/doodle/browser/static/`. The stylesheet covers only |
| 70 | +poll-specific elements and makes no global overrides. |
| 71 | + |
| 72 | +## Profile version |
| 73 | + |
| 74 | +The FTI change (`default_view` set to `poll_view`, `results` added to |
| 75 | +`view_methods`) ships at profile version `1002`. Sites on `1001` can |
| 76 | +upgrade through the {guilabel}`Add-ons` control panel or |
| 77 | +programmatically: |
| 78 | + |
| 79 | +```python |
| 80 | +from plone import api |
| 81 | + |
| 82 | +api.portal.get_tool("portal_setup").upgradeProfile( |
| 83 | + "experimental.doodle:default" |
| 84 | +) |
| 85 | +``` |
| 86 | + |
| 87 | +## Related |
| 88 | + |
| 89 | +- {doc}`vote-storage`: the adapter the views delegate to. |
| 90 | +- {doc}`poll-content-type`: the content type the views render. |
| 91 | +- {doc}`/how-to-guides/vote-on-a-poll`: step-by-step voting guide. |
0 commit comments