diff --git a/meinberlin/apps/budgeting/api.py b/meinberlin/apps/budgeting/api.py index 3c65064359..c4ecd9dba2 100644 --- a/meinberlin/apps/budgeting/api.py +++ b/meinberlin/apps/budgeting/api.py @@ -37,11 +37,13 @@ def list(self, request, *args, **kwargs): return response -class ProposalFilterInfoMixin(ModuleMixin): +class ProposalFilterInfoMixin: def list(self, request, *args, **kwargs): """Add the filter information to the data of the Proposal API. Needs to be used with rest_framework.mixins.ListModelMixin + and adhocracy4.api.mixins.ModuleMixin or some other mixin that + fetches the module """ filters = {} @@ -92,7 +94,8 @@ def list(self, request, *args, **kwargs): return response -class ProposalViewSet(ProposalFilterInfoMixin, +class ProposalViewSet(ModuleMixin, + ProposalFilterInfoMixin, LocaleInfoMixin, VotingTokenInfoMixin, mixins.ListModelMixin, diff --git a/meinberlin/apps/budgeting/serializers.py b/meinberlin/apps/budgeting/serializers.py index 4304fab0af..b912129136 100644 --- a/meinberlin/apps/budgeting/serializers.py +++ b/meinberlin/apps/budgeting/serializers.py @@ -3,6 +3,7 @@ from adhocracy4.categories.models import Category from meinberlin.apps.votes.models import TokenVote +from meinberlin.apps.votes.models import VotingToken from .models import Proposal @@ -89,8 +90,17 @@ def get_session_token_voted(self, proposal): """ if 'request' in self.context: if 'voting_token' in self.context['request'].session: + token = None + try: + token = VotingToken.objects.get( + token=self.context['request'].session['voting_token'], + module=self.context['view'].module + ) + except VotingToken.DoesNotExist: + pass + vote = TokenVote.objects.filter( - token__pk=self.context['request'].session['voting_token'], + token=token, content_type=ContentType.objects.get_for_model( proposal.__class__), object_pk=proposal.pk diff --git a/meinberlin/apps/budgeting/templates/meinberlin_budgeting/proposal_list.html b/meinberlin/apps/budgeting/templates/meinberlin_budgeting/proposal_list.html index fd9b906212..7d22756a3a 100644 --- a/meinberlin/apps/budgeting/templates/meinberlin_budgeting/proposal_list.html +++ b/meinberlin/apps/budgeting/templates/meinberlin_budgeting/proposal_list.html @@ -26,9 +26,7 @@ {% if module.phases.count == 3 %} {% has_perm 'meinberlin_budgeting.add_vote' request.user module as vote_allowed %} {% if vote_allowed %} - {% if request.session.voting_token %} - You are using token {{ request.session.voting_token }} - {% else %} + {% if not valid_token_present %}
{% csrf_token %} diff --git a/meinberlin/apps/budgeting/templatetags/react_proposals_vote.py b/meinberlin/apps/budgeting/templatetags/react_proposals_vote.py index 0b038852ff..dfd49ede76 100644 --- a/meinberlin/apps/budgeting/templatetags/react_proposals_vote.py +++ b/meinberlin/apps/budgeting/templatetags/react_proposals_vote.py @@ -32,17 +32,18 @@ def react_proposals_vote(context, module, proposal): if 'voting_token' in request.session: try: token = VotingToken.objects.get( - pk=request.session['voting_token'] + token=request.session['voting_token'], + module=module ) + serializer = VotingTokenSerializer(token) + token_info = serializer.data + session_token_voted = TokenVote.objects.filter( + token__pk=token.pk, + content_type=proposal_ct, + object_pk=proposal.pk + ).exists() except VotingToken.DoesNotExist: pass - serializer = VotingTokenSerializer(token) - token_info = serializer.data - session_token_voted = TokenVote.objects.filter( - token__pk=token.pk, - content_type=proposal_ct, - object_pk=proposal.pk - ).exists() attributes = {'proposals_api_url': proposals_api_url, 'tokenvote_api_url': tokenvote_api_url, diff --git a/meinberlin/apps/budgeting/views.py b/meinberlin/apps/budgeting/views.py index d46c16905f..8486e57228 100644 --- a/meinberlin/apps/budgeting/views.py +++ b/meinberlin/apps/budgeting/views.py @@ -9,6 +9,7 @@ from meinberlin.apps.ideas import views as idea_views from meinberlin.apps.projects.views import ArchivedWidget from meinberlin.apps.votes.forms import TokenForm +from meinberlin.apps.votes.models import VotingToken from . import forms from . import models @@ -45,6 +46,19 @@ class ProposalListView(idea_views.AbstractIdeaListView, model = models.Proposal filter_set = ProposalFilterSet + def has_valid_token_in_session(self, request): + """Return whether a valid token is stored in the session. + + The token is valid if it is valid for the respective module. + """ + if 'voting_token' in request.session: + token_queryset = VotingToken.objects.filter( + token=request.session['voting_token'], + module=self.module + ) + return token_queryset.exists() + return False + def dispatch(self, request, **kwargs): self.mode = request.GET.get('mode', 'map') if self.mode == 'map': @@ -59,6 +73,8 @@ def get_context_data(self, **kwargs): if 'token_form' not in kwargs: token_form = TokenForm(module_id=self.module.id) kwargs['token_form'] = token_form + kwargs['valid_token_present'] = \ + self.has_valid_token_in_session(self.request) return super().get_context_data(**kwargs) def post(self, request, *args, **kwargs): @@ -66,6 +82,8 @@ def post(self, request, *args, **kwargs): token_form = TokenForm(request.POST, module_id=self.module.id) if token_form.is_valid(): request.session['voting_token'] = token_form.cleaned_data['token'] + kwargs['valid_token_present'] = True + self.mode = 'list' kwargs['token_form'] = token_form context = super().get_context_data(**kwargs) return self.render_to_response(context) diff --git a/meinberlin/apps/votes/api.py b/meinberlin/apps/votes/api.py index bc9b92ad53..b507ee25bb 100644 --- a/meinberlin/apps/votes/api.py +++ b/meinberlin/apps/votes/api.py @@ -23,6 +23,8 @@ class VotingTokenInfoMixin: """Adds token info to response data of an api list view. Needs to be used with rest_framework.mixins.ListModelMixin + and adhocracy4.api.mixins.ModuleMixin or some other mixin that + fetches the module """ def list(self, request, *args, **kwargs): @@ -31,12 +33,13 @@ def list(self, request, *args, **kwargs): if 'voting_token' in request.session: try: token = VotingToken.objects.get( - pk=request.session['voting_token'] + token=request.session['voting_token'], + module=self.module ) + serializer = VotingTokenSerializer(token) + token_info = serializer.data except VotingToken.DoesNotExist: pass - serializer = VotingTokenSerializer(token) - token_info = serializer.data response.data['token_info'] = token_info return response @@ -53,10 +56,13 @@ def dispatch(self, request, *args, **kwargs): self.content_type_id = kwargs.get('content_type', '') try: session = Session.objects.get(pk=request.session.session_key) - token_id = session.get_decoded()['voting_token'] - self.token = VotingToken.objects.get(pk=token_id) + self.token = VotingToken.objects.get( + token=session.get_decoded()['voting_token'], + module=self.module + ) except ObjectDoesNotExist: pass + except KeyError: pass @@ -81,18 +87,13 @@ def check_permissions(self, request): if not hasattr(self, 'token'): self.permission_denied( request, - message=_('No token given.') + message=_('No token given or token not valid for module.') ) elif not self.token.is_active: self.permission_denied( request, message=_('Token is inactive.') ) - elif not self.token.module == self.module: - self.permission_denied( - request, - message=_('Token not valid for module.') - ) super().check_permissions(request) diff --git a/meinberlin/apps/votes/forms.py b/meinberlin/apps/votes/forms.py index bb49a28c58..0d81bf0579 100644 --- a/meinberlin/apps/votes/forms.py +++ b/meinberlin/apps/votes/forms.py @@ -44,6 +44,6 @@ def clean(self): if not token_queryset: self.add_error('token', _('This token is not valid')) else: - cleaned_data['token'] = token_queryset.first().id + cleaned_data['token'] = token_queryset.first().token return cleaned_data diff --git a/tests/budgeting/test_proposals_api.py b/tests/budgeting/test_proposals_api.py index 0595794091..a1fe78fb52 100644 --- a/tests/budgeting/test_proposals_api.py +++ b/tests/budgeting/test_proposals_api.py @@ -187,7 +187,7 @@ def test_proposal_list_pagination(apiclient, module, proposal_factory): url_tmp = url + '?page=2' response = apiclient.get(url_tmp) assert not response.data['next'] - assert not response.data['previous'].endswith('?page=1') + assert response.data['previous'].endswith(url) assert len(response.data['results']) == 1 proposal = Proposal.objects.last() diff --git a/tests/votes/test_token_vote_api.py b/tests/votes/test_token_vote_api.py index a8f487be5f..8069979eee 100644 --- a/tests/votes/test_token_vote_api.py +++ b/tests/votes/test_token_vote_api.py @@ -11,7 +11,7 @@ def add_token_to_session(apiclient, token): session = apiclient.session - session['voting_token'] = token.pk + session['voting_token'] = token.token session.save() @@ -310,7 +310,8 @@ def test_voting_phase_active_no_token( assert apiclient.login(username=admin.email, password='password') response = apiclient.post(url, data, format='json') assert response.status_code == status.HTTP_403_FORBIDDEN - assert 'No token given.' in response.content.decode() + assert 'No token given or token not valid for module.' in \ + response.content.decode() assert TokenVote.objects.all().count() == 0 @@ -342,7 +343,8 @@ def test_voting_phase_active_token_wrong_module( assert apiclient.login(username=admin.email, password='password') response = apiclient.post(url, data, format='json') assert response.status_code == status.HTTP_403_FORBIDDEN - assert 'Token not valid for module.' in response.content.decode() + assert 'No token given or token not valid for module.' in \ + response.content.decode() assert TokenVote.objects.all().count() == 0 @@ -816,7 +818,8 @@ def test_voting_phase_active_no_token_cannot_delete( assert apiclient.login(username=admin.email, password='password') response = apiclient.delete(url, format='json') assert response.status_code == status.HTTP_403_FORBIDDEN - assert 'No token given.' in response.content.decode() + assert 'No token given or token not valid for module.' in \ + response.content.decode() assert TokenVote.objects.all().count() == 1