From fc7d8fc9c8e7d4d37ac30b8d8d9d718bffaf0648 Mon Sep 17 00:00:00 2001 From: Julian Dehm Date: Wed, 6 Nov 2024 12:39:48 +0100 Subject: [PATCH] polls: add new signal which is called after a user voted on a poll --- adhocracy4/polls/api.py | 4 +++ adhocracy4/polls/signals.py | 9 +++++ changelog/8381.md | 2 +- tests/polls/test_vote_api.py | 67 ++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 adhocracy4/polls/signals.py diff --git a/adhocracy4/polls/api.py b/adhocracy4/polls/api.py index 783d6dbf1..101cb6ee7 100644 --- a/adhocracy4/polls/api.py +++ b/adhocracy4/polls/api.py @@ -30,6 +30,7 @@ from .models import Question from .models import Vote from .serializers import PollSerializer +from .signals import poll_voted class PollViewSet( @@ -178,6 +179,9 @@ def vote(self, request, pk): self.save_vote(question, vote_data, creator, content_id) poll = self.get_object() + poll_voted.send( + sender=self.__class__, poll=poll, creator=creator, content_id=content_id + ) poll_serializer = self.get_serializer(poll) poll_data = self.add_terms_of_use_info(request, poll_serializer.data) if not self.request.user.is_authenticated: diff --git a/adhocracy4/polls/signals.py b/adhocracy4/polls/signals.py new file mode 100644 index 000000000..a21695b09 --- /dev/null +++ b/adhocracy4/polls/signals.py @@ -0,0 +1,9 @@ +from django.dispatch import Signal + +"""Signal which is called after a user participated in a poll. The signal +receives the following arguments: + poll: Poll + creator: User + content_id: uuid4 +""" +poll_voted = Signal() diff --git a/changelog/8381.md b/changelog/8381.md index f7fcfb633..a990fbb18 100644 --- a/changelog/8381.md +++ b/changelog/8381.md @@ -3,4 +3,4 @@ - add option to allow unregistered users to vote in a poll: - the feature is controlled via a new django setting `A4_POLL_ENABLE_UNREGISTERED_USERS` to enable or disable it - add a new captcha react component to integrate the captcha in the poll - +- add a poll_voted signal which is sent when a user has voted on a poll. diff --git a/tests/polls/test_vote_api.py b/tests/polls/test_vote_api.py index b69e21cab..5041b14fa 100644 --- a/tests/polls/test_vote_api.py +++ b/tests/polls/test_vote_api.py @@ -1,11 +1,15 @@ +from unittest.mock import MagicMock + import pytest from django.urls import reverse from rest_framework import status +from adhocracy4.polls.api import PollViewSet from adhocracy4.polls.models import Answer from adhocracy4.polls.models import OtherVote from adhocracy4.polls.models import Vote from adhocracy4.polls.phases import VotingPhase +from adhocracy4.polls.signals import poll_voted from adhocracy4.projects.enums import Access from tests.helpers import active_phase @@ -700,3 +704,66 @@ def test_validate_question_belongs_to_poll( "Question has to belong to the poll set in the url." in response.content.decode() ) + + +@pytest.mark.django_db +def test_poll_voted_signal_is_dispatched_on_vote( + user, apiclient, poll_factory, question_factory, choice_factory +): + signal_handler = MagicMock() + poll_voted.connect(signal_handler) + + poll = poll_factory() + poll.allow_unregistered_users = True + poll.save() + question = question_factory(poll=poll) + choice1 = choice_factory(question=question) + choice_factory(question=question) + open_question = question_factory(poll=poll, is_open=True) + + assert Vote.objects.count() == 0 + + apiclient.force_authenticate(user=user) + + url = reverse("polls-vote", kwargs={"pk": poll.pk}) + + data = { + "votes": { + question.pk: { + "choices": [choice1.pk], + "other_choice_answer": "", + "open_answer": "", + }, + open_question.pk: { + "choices": [], + "other_choice_answer": "", + "open_answer": "an open answer", + }, + } + } + + with active_phase(poll.module, VotingPhase): + response = apiclient.post(url, data, format="json") + assert response.status_code == status.HTTP_201_CREATED + signal_handler.assert_called_once_with( + signal=poll_voted, + sender=PollViewSet, + poll=poll, + creator=user, + content_id=None, + ) + + # test for unregistered user + signal_handler.reset_mock() + apiclient.logout() + data["captcha"] = "testpass:0" + response = apiclient.post(url, data, format="json") + assert response.status_code == status.HTTP_201_CREATED + content_id = Vote.objects.filter(content_id__isnull=False).first().content_id + signal_handler.assert_called_once_with( + signal=poll_voted, + sender=PollViewSet, + poll=poll, + creator=None, + content_id=content_id, + )