flaskbb-plugin-vote 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flaskbb_plugin_vote-1.0.0/.gitignore +43 -0
- flaskbb_plugin_vote-1.0.0/LICENSE +31 -0
- flaskbb_plugin_vote-1.0.0/PKG-INFO +43 -0
- flaskbb_plugin_vote-1.0.0/README.md +15 -0
- flaskbb_plugin_vote-1.0.0/pyproject.toml +90 -0
- flaskbb_plugin_vote-1.0.0/tests/conftest.py +36 -0
- flaskbb_plugin_vote-1.0.0/tests/test_vote_integration.py +205 -0
- flaskbb_plugin_vote-1.0.0/tests/test_vote_models.py +61 -0
- flaskbb_plugin_vote-1.0.0/tests/test_vote_utils.py +196 -0
- flaskbb_plugin_vote-1.0.0/tests/test_vote_views.py +165 -0
- flaskbb_plugin_vote-1.0.0/vote/__init__.py +257 -0
- flaskbb_plugin_vote-1.0.0/vote/forms.py +29 -0
- flaskbb_plugin_vote-1.0.0/vote/migrations/202607301200_a1f3c9d7e2b4_add_vote_polls.py +91 -0
- flaskbb_plugin_vote-1.0.0/vote/migrations/__init__.py +0 -0
- flaskbb_plugin_vote-1.0.0/vote/models.py +115 -0
- flaskbb_plugin_vote-1.0.0/vote/static/vote.js +161 -0
- flaskbb_plugin_vote-1.0.0/vote/templates/vote/_cheatsheet.html +4 -0
- flaskbb_plugin_vote-1.0.0/vote/templates/vote/_form_extra.html +41 -0
- flaskbb_plugin_vote-1.0.0/vote/templates/vote/_poll_widget.html +56 -0
- flaskbb_plugin_vote-1.0.0/vote/templates/vote/_scripts.html +1 -0
- flaskbb_plugin_vote-1.0.0/vote/templates/vote/_toolbar_button.html +5 -0
- flaskbb_plugin_vote-1.0.0/vote/utils.py +159 -0
- flaskbb_plugin_vote-1.0.0/vote/views.py +86 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
.Python
|
|
8
|
+
build/
|
|
9
|
+
develop-eggs/
|
|
10
|
+
dist/
|
|
11
|
+
downloads/
|
|
12
|
+
eggs/
|
|
13
|
+
.eggs/
|
|
14
|
+
lib/
|
|
15
|
+
lib64/
|
|
16
|
+
parts/
|
|
17
|
+
sdist/
|
|
18
|
+
var/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Unit test / coverage reports
|
|
24
|
+
htmlcov/
|
|
25
|
+
.tox/
|
|
26
|
+
.coverage
|
|
27
|
+
.coverage.*
|
|
28
|
+
.cache
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.ruff_cache/
|
|
31
|
+
nosetests.xml
|
|
32
|
+
coverage.xml
|
|
33
|
+
*,cover
|
|
34
|
+
.hypothesis/
|
|
35
|
+
|
|
36
|
+
# Translations
|
|
37
|
+
*.mo
|
|
38
|
+
*.pot
|
|
39
|
+
|
|
40
|
+
*.log
|
|
41
|
+
|
|
42
|
+
.vscode
|
|
43
|
+
.venv/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Copyright (c) 2026 by Peter Justin and contributors. See AUTHORS for more
|
|
2
|
+
details.
|
|
3
|
+
|
|
4
|
+
Some rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are
|
|
8
|
+
met:
|
|
9
|
+
|
|
10
|
+
* Redistributions of source code must retain the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
14
|
+
notice, this list of conditions and the following disclaimer in the
|
|
15
|
+
documentation and/or other materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
* The names of the contributors may not be used to endorse or
|
|
18
|
+
promote products derived from this software without specific
|
|
19
|
+
prior written permission.
|
|
20
|
+
|
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
22
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
23
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
24
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
25
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
26
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
27
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
28
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
29
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
30
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
31
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flaskbb-plugin-vote
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A polls plugin for FlaskBB - attach single or multiple choice polls to topics and posts
|
|
5
|
+
Project-URL: Homepage, https://github.com/flaskbb/flaskbb-plugin-vote
|
|
6
|
+
Project-URL: Repository, https://github.com/flaskbb/flaskbb-plugin-vote
|
|
7
|
+
Project-URL: Issues, https://github.com/flaskbb/flaskbb-plugin-vote/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/flaskbb/flaskbb-plugin-vote/blob/master/CHANGELOG.md
|
|
9
|
+
Author-email: Peter Justin <peter.justin@outlook.com>
|
|
10
|
+
Maintainer-email: Peter Justin <peter.justin@outlook.com>
|
|
11
|
+
License-Expression: BSD-3-Clause
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: flaskbb,plugin,poll,polls,vote
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Web Environment
|
|
16
|
+
Classifier: Framework :: Flask
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: flaskbb>=3.0.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
Vote
|
|
30
|
+
====
|
|
31
|
+
|
|
32
|
+
A polls plugin for FlaskBB. Lets a user create polls in a topic or post.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
Installation
|
|
36
|
+
------------
|
|
37
|
+
|
|
38
|
+
Install *flaskbb-plugin-vote* with
|
|
39
|
+
``pip install flaskbb-plugin-vote``
|
|
40
|
+
|
|
41
|
+
License
|
|
42
|
+
-------
|
|
43
|
+
This project is licensed under the terms of the [BSD License](/LICENSE).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Vote
|
|
2
|
+
====
|
|
3
|
+
|
|
4
|
+
A polls plugin for FlaskBB. Lets a user create polls in a topic or post.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Installation
|
|
8
|
+
------------
|
|
9
|
+
|
|
10
|
+
Install *flaskbb-plugin-vote* with
|
|
11
|
+
``pip install flaskbb-plugin-vote``
|
|
12
|
+
|
|
13
|
+
License
|
|
14
|
+
-------
|
|
15
|
+
This project is licensed under the terms of the [BSD License](/LICENSE).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "flaskbb-plugin-vote"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
dependencies = [
|
|
5
|
+
"FlaskBB>=3.0.0",
|
|
6
|
+
]
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Peter Justin", email = "peter.justin@outlook.com"},
|
|
10
|
+
]
|
|
11
|
+
maintainers = [
|
|
12
|
+
{name = "Peter Justin", email = "peter.justin@outlook.com"}
|
|
13
|
+
]
|
|
14
|
+
description = "A polls plugin for FlaskBB - attach single or multiple choice polls to topics and posts"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
license = "BSD-3-Clause"
|
|
17
|
+
license-files = ["LICEN[CS]E"]
|
|
18
|
+
keywords = ["flaskbb", "plugin", "vote", "poll", "polls"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Framework :: Flask",
|
|
22
|
+
"Environment :: Web Environment",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Intended Audience :: End Users/Desktop",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
31
|
+
]
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/flaskbb/flaskbb-plugin-vote"
|
|
34
|
+
Repository = "https://github.com/flaskbb/flaskbb-plugin-vote"
|
|
35
|
+
Issues = "https://github.com/flaskbb/flaskbb-plugin-vote/issues"
|
|
36
|
+
Changelog = "https://github.com/flaskbb/flaskbb-plugin-vote/blob/master/CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
[project.entry-points.'flaskbb_plugins']
|
|
39
|
+
vote = 'vote'
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = [
|
|
47
|
+
"hatch",
|
|
48
|
+
"Babel",
|
|
49
|
+
"pytest",
|
|
50
|
+
"ruff",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.sdist]
|
|
54
|
+
only-include = ["vote", "tests"]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.wheel]
|
|
57
|
+
packages = ["vote"]
|
|
58
|
+
|
|
59
|
+
[tool.uv]
|
|
60
|
+
package = true
|
|
61
|
+
|
|
62
|
+
[tool.hatch.metadata]
|
|
63
|
+
allow-direct-references = true
|
|
64
|
+
|
|
65
|
+
[tool.basedpyright]
|
|
66
|
+
include = ["vote"]
|
|
67
|
+
exclude = ["**/node_modules", "**/resources", "**/__pycache__", "**/docs"]
|
|
68
|
+
defineConstant = { DEBUG = true }
|
|
69
|
+
reportIgnoreCommentWithoutRule = false
|
|
70
|
+
reportMissingImports = "error"
|
|
71
|
+
reportMissingTypeStubs = false
|
|
72
|
+
reportUnusedCallResult = false
|
|
73
|
+
reportUnannotatedClassAttribute = false
|
|
74
|
+
reportUnusedParameter = false
|
|
75
|
+
reportAny = false
|
|
76
|
+
reportExplicitAny = false
|
|
77
|
+
|
|
78
|
+
[tool.pytest]
|
|
79
|
+
minversion = "9.0"
|
|
80
|
+
addopts = [
|
|
81
|
+
"-vvl",
|
|
82
|
+
"--strict-markers",
|
|
83
|
+
"--capture", "fd",
|
|
84
|
+
"--tb", "short",
|
|
85
|
+
"--pythonwarnings", "error::flaskbb.deprecation.FlaskBBDeprecation",
|
|
86
|
+
"--dist", "load"
|
|
87
|
+
]
|
|
88
|
+
testpaths = [
|
|
89
|
+
"tests",
|
|
90
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from flaskbb.forum.models import Post
|
|
3
|
+
from tests.fixtures.app import *
|
|
4
|
+
from tests.fixtures.forum import *
|
|
5
|
+
from tests.fixtures.user import *
|
|
6
|
+
|
|
7
|
+
from vote.models import Poll, PollOption
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _reply(topic, user):
|
|
11
|
+
"""A reply post in ``topic`` - polls attach to this, not the first
|
|
12
|
+
post, so cascade-delete tests don't get entangled with Post.delete()
|
|
13
|
+
redirecting to Topic.delete() for a topic's first post."""
|
|
14
|
+
return Post(content="Test reply").save(user=user, topic=topic)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def poll(topic, user):
|
|
19
|
+
post = _reply(topic, user)
|
|
20
|
+
poll = Poll(post_id=post.id, question="Favorite color?", poll_type="single")
|
|
21
|
+
poll.save()
|
|
22
|
+
PollOption(poll_id=poll.id, text="Red", position=0).save()
|
|
23
|
+
PollOption(poll_id=poll.id, text="Green", position=1).save()
|
|
24
|
+
PollOption(poll_id=poll.id, text="Blue", position=2).save()
|
|
25
|
+
return Poll.get(Poll.id == poll.id)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def multi_poll(topic, user):
|
|
30
|
+
post = _reply(topic, user)
|
|
31
|
+
poll = Poll(post_id=post.id, question="Favorite toppings?", poll_type="multiple")
|
|
32
|
+
poll.save()
|
|
33
|
+
PollOption(poll_id=poll.id, text="Cheese", position=0).save()
|
|
34
|
+
PollOption(poll_id=poll.id, text="Pepperoni", position=1).save()
|
|
35
|
+
PollOption(poll_id=poll.id, text="Mushroom", position=2).save()
|
|
36
|
+
return Poll.get(Poll.id == poll.id)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
End-to-end tests for the flaskbb_form_topic/flaskbb_form_post +
|
|
3
|
+
flaskbb_event_post_save_after hook chain that turns a submitted poll_data
|
|
4
|
+
payload into a real Poll row. This only works if the `vote` package is
|
|
5
|
+
installed into flaskbb's environment (so its entry point is discovered by
|
|
6
|
+
create_app() and its hookimpls actually run) - see README.md.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
|
|
12
|
+
from flaskbb.core.settings import flaskbb_config
|
|
13
|
+
from flaskbb.extensions import pluggy
|
|
14
|
+
from flaskbb.forum.forms import EditTopicForm, NewTopicForm, ReplyForm
|
|
15
|
+
|
|
16
|
+
import vote as vote_plugin
|
|
17
|
+
from vote.models import Poll, PollOption
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def poll_json(**overrides):
|
|
21
|
+
data = {
|
|
22
|
+
"question": "Favorite color?",
|
|
23
|
+
"type": "single",
|
|
24
|
+
"options": ["Red", "Blue"],
|
|
25
|
+
}
|
|
26
|
+
data.update(overrides)
|
|
27
|
+
return json.dumps(data)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def new_topic_form(**kwargs):
|
|
31
|
+
# flaskbb_form_topic is only invoked by NewTopic.form()/EditTopic.form()
|
|
32
|
+
# in flaskbb/forum/views.py, once per request - never automatically -
|
|
33
|
+
# so a test constructing NewTopicForm() directly has to trigger it
|
|
34
|
+
# first, exactly like those views do.
|
|
35
|
+
pluggy.hook.flaskbb_form_topic(form=NewTopicForm)
|
|
36
|
+
return NewTopicForm(**kwargs)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def reply_form(**kwargs):
|
|
40
|
+
pluggy.hook.flaskbb_form_post(form=ReplyForm)
|
|
41
|
+
return ReplyForm(**kwargs)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@contextmanager
|
|
45
|
+
def topics_only(enabled):
|
|
46
|
+
# default_settings (pulled in transitively by the forum/topic fixtures)
|
|
47
|
+
# already seeds vote's settings group, so this is just a normal update.
|
|
48
|
+
original = flaskbb_config["VOTE_TOPICS_ONLY"]
|
|
49
|
+
flaskbb_config["VOTE_TOPICS_ONLY"] = enabled
|
|
50
|
+
try:
|
|
51
|
+
yield
|
|
52
|
+
finally:
|
|
53
|
+
flaskbb_config["VOTE_TOPICS_ONLY"] = original
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_new_topic_form_has_poll_data_field(request_context):
|
|
57
|
+
assert hasattr(new_topic_form(), "poll_data")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_reply_form_has_poll_data_field(request_context, default_settings):
|
|
61
|
+
# reply_form() now reads the TOPICS_ONLY setting, which needs the
|
|
62
|
+
# settings table to exist.
|
|
63
|
+
assert hasattr(reply_form(), "poll_data")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_submitting_a_new_topic_with_poll_data_creates_a_poll(
|
|
67
|
+
request_context, forum, user
|
|
68
|
+
):
|
|
69
|
+
form = new_topic_form(
|
|
70
|
+
title="A topic with a poll", content="See the attached poll", track_topic=False
|
|
71
|
+
)
|
|
72
|
+
form.poll_data.data = poll_json()
|
|
73
|
+
|
|
74
|
+
topic = form.save(user, forum)
|
|
75
|
+
|
|
76
|
+
poll = Poll.get(Poll.post_id == topic.first_post.id)
|
|
77
|
+
assert poll is not None
|
|
78
|
+
assert poll.question == "Favorite color?"
|
|
79
|
+
assert [o.text for o in poll.options] == ["Red", "Blue"]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_submitting_a_new_topic_without_poll_data_creates_no_poll(
|
|
83
|
+
request_context, forum, user
|
|
84
|
+
):
|
|
85
|
+
form = new_topic_form(
|
|
86
|
+
title="A plain topic", content="No poll here", track_topic=False
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
topic = form.save(user, forum)
|
|
90
|
+
|
|
91
|
+
assert Poll.get(Poll.post_id == topic.first_post.id) is None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_submitting_a_reply_with_poll_data_creates_a_poll(request_context, topic, user):
|
|
95
|
+
form = reply_form(content="A reply with a poll", track_topic=False)
|
|
96
|
+
form.poll_data.data = poll_json(question="Pick one", options=["A", "B", "C"])
|
|
97
|
+
|
|
98
|
+
post = form.save(user, topic)
|
|
99
|
+
|
|
100
|
+
poll = Poll.get(Poll.post_id == post.id)
|
|
101
|
+
assert poll is not None
|
|
102
|
+
assert poll.question == "Pick one"
|
|
103
|
+
assert [o.text for o in poll.options] == ["A", "B", "C"]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_editing_a_reply_with_poll_data_creates_no_poll(request_context, topic, user):
|
|
107
|
+
"""ReplyForm is shared between NewPost and EditPost - poll_data is only
|
|
108
|
+
ever meant to be honored for a brand new post (see the comment on
|
|
109
|
+
flaskbb_tpl_form_new_post_after in vote/__init__.py). Simulating an
|
|
110
|
+
edit here by passing obj=<an existing post> exercises the is_new=False
|
|
111
|
+
guard in flaskbb_event_post_save_after directly, regardless of
|
|
112
|
+
whether the template actually renders the field for edits.
|
|
113
|
+
"""
|
|
114
|
+
existing_post = topic.first_post
|
|
115
|
+
form = reply_form(obj=existing_post, content="Edited content", track_topic=False)
|
|
116
|
+
form.poll_data.data = poll_json()
|
|
117
|
+
|
|
118
|
+
form.save(user, topic)
|
|
119
|
+
|
|
120
|
+
assert Poll.get(Poll.post_id == existing_post.id) is None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_malformed_poll_data_does_not_prevent_topic_creation(
|
|
124
|
+
request_context, forum, user
|
|
125
|
+
):
|
|
126
|
+
form = new_topic_form(
|
|
127
|
+
title="A topic with junk poll data", content="Still works", track_topic=False
|
|
128
|
+
)
|
|
129
|
+
form.poll_data.data = "not json"
|
|
130
|
+
|
|
131
|
+
topic = form.save(user, forum)
|
|
132
|
+
|
|
133
|
+
assert topic is not None
|
|
134
|
+
assert topic.id is not None
|
|
135
|
+
assert Poll.get(Poll.post_id == topic.first_post.id) is None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_topics_only_removes_poll_data_from_reply_form(
|
|
139
|
+
request_context, default_settings
|
|
140
|
+
):
|
|
141
|
+
with topics_only(True):
|
|
142
|
+
assert not hasattr(reply_form(), "poll_data")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_topics_only_does_not_affect_new_topic_form(request_context, default_settings):
|
|
146
|
+
with topics_only(True):
|
|
147
|
+
assert hasattr(new_topic_form(), "poll_data")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_topics_only_disabled_again_restores_the_field(
|
|
151
|
+
request_context, default_settings
|
|
152
|
+
):
|
|
153
|
+
with topics_only(True):
|
|
154
|
+
assert not hasattr(reply_form(), "poll_data")
|
|
155
|
+
with topics_only(False):
|
|
156
|
+
assert hasattr(reply_form(), "poll_data")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_topics_only_prevents_a_reply_poll_end_to_end(request_context, topic, user):
|
|
160
|
+
with topics_only(True):
|
|
161
|
+
form = reply_form(content="Trying to attach a poll anyway")
|
|
162
|
+
assert not hasattr(form, "poll_data")
|
|
163
|
+
|
|
164
|
+
post = form.save(user, topic)
|
|
165
|
+
|
|
166
|
+
assert Poll.get(Poll.post_id == post.id) is None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_editing_a_reply_shows_its_poll_read_only(request_context, poll):
|
|
170
|
+
form = reply_form(obj=poll.post, content="Edited content")
|
|
171
|
+
|
|
172
|
+
html = vote_plugin.flaskbb_tpl_form_new_post_after(form)
|
|
173
|
+
|
|
174
|
+
assert html is not None
|
|
175
|
+
assert poll.question in html
|
|
176
|
+
assert 'name="option_id"' not in html
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_editing_a_reply_without_a_poll_renders_nothing(request_context, topic, user):
|
|
180
|
+
from flaskbb.forum.models import Post
|
|
181
|
+
|
|
182
|
+
post = Post(content="No poll here").save(user=user, topic=topic)
|
|
183
|
+
form = reply_form(obj=post, content="Edited content")
|
|
184
|
+
|
|
185
|
+
assert vote_plugin.flaskbb_tpl_form_new_post_after(form) is None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_editing_a_topic_shows_its_poll_read_only(request_context, topic, user):
|
|
189
|
+
poll = Poll(post_id=topic.first_post.id, question="Best pizza?", poll_type="single")
|
|
190
|
+
poll.save()
|
|
191
|
+
PollOption(poll_id=poll.id, text="Margherita", position=0).save()
|
|
192
|
+
PollOption(poll_id=poll.id, text="Pepperoni", position=1).save()
|
|
193
|
+
|
|
194
|
+
form = EditTopicForm(obj=topic.first_post, title=topic.title)
|
|
195
|
+
html = vote_plugin.flaskbb_tpl_form_new_topic_after(form)
|
|
196
|
+
|
|
197
|
+
assert html is not None
|
|
198
|
+
assert "Best pizza?" in html
|
|
199
|
+
assert 'name="option_id"' not in html
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_editing_a_topic_without_a_poll_renders_nothing(request_context, topic):
|
|
203
|
+
form = EditTopicForm(obj=topic.first_post, title=topic.title)
|
|
204
|
+
|
|
205
|
+
assert vote_plugin.flaskbb_tpl_form_new_topic_after(form) is None
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from vote.models import Poll, PollOption, PollVote
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_poll_total_votes_with_no_votes(poll):
|
|
5
|
+
assert poll.total_votes == 0
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_poll_total_votes_counts_across_options(poll, user, admin_user):
|
|
9
|
+
red, green = poll.options[0], poll.options[1]
|
|
10
|
+
PollVote(poll_option_id=red.id, user_id=user.id).save()
|
|
11
|
+
PollVote(poll_option_id=green.id, user_id=admin_user.id).save()
|
|
12
|
+
|
|
13
|
+
poll = Poll.get(Poll.id == poll.id)
|
|
14
|
+
assert poll.total_votes == 2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_option_vote_count(poll, user):
|
|
18
|
+
red = poll.options[0]
|
|
19
|
+
PollVote(poll_option_id=red.id, user_id=user.id).save()
|
|
20
|
+
|
|
21
|
+
assert red.vote_count == 1
|
|
22
|
+
assert poll.options[1].vote_count == 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_option_ids_voted_by_returns_only_that_users_votes(poll, user, admin_user):
|
|
26
|
+
red, green = poll.options[0], poll.options[1]
|
|
27
|
+
PollVote(poll_option_id=red.id, user_id=user.id).save()
|
|
28
|
+
PollVote(poll_option_id=green.id, user_id=admin_user.id).save()
|
|
29
|
+
|
|
30
|
+
assert poll.option_ids_voted_by(user.id) == [red.id]
|
|
31
|
+
assert poll.option_ids_voted_by(admin_user.id) == [green.id]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_option_ids_voted_by_empty_for_nonvoter(poll, admin_user):
|
|
35
|
+
assert poll.option_ids_voted_by(admin_user.id) == []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_is_multiple_choice(poll, multi_poll):
|
|
39
|
+
assert poll.is_multiple_choice is False
|
|
40
|
+
assert multi_poll.is_multiple_choice is True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_deleting_post_cascades_to_poll_and_options(poll):
|
|
44
|
+
post = poll.post
|
|
45
|
+
option_ids = [option.id for option in poll.options]
|
|
46
|
+
poll_id = poll.id
|
|
47
|
+
|
|
48
|
+
post.delete()
|
|
49
|
+
|
|
50
|
+
assert Poll.get(Poll.id == poll_id) is None
|
|
51
|
+
assert PollOption.get_all(PollOption.id.in_(option_ids)) == []
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_deleting_option_cascades_to_votes(poll, user):
|
|
55
|
+
red = poll.options[0]
|
|
56
|
+
vote = PollVote(poll_option_id=red.id, user_id=user.id).save()
|
|
57
|
+
vote_id = vote.id
|
|
58
|
+
|
|
59
|
+
red.delete()
|
|
60
|
+
|
|
61
|
+
assert PollVote.get(PollVote.id == vote_id) is None
|