Commit 043445a2 authored by Robert's avatar Robert

Move polls rest api, refs #661

parent 41872dc2
Pipeline #1841 passed with stage
in 7 minutes and 23 seconds
from contextlib import contextmanager
from django.http import Http404
from django.core.exceptions import PermissionDenied
from rest_framework import viewsets, mixins, serializers, permissions
from rest_framework.decorators import permission_classes, api_view
from rest_framework.response import Response
from grouprise.features.rest_api.frontend.serializers import GestaltSerializer, GestaltOrAnonSerializer
from . import models
class OptionSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField()
def get_title(self, instance: models.Option):
return str(instance)
class Meta:
model = models.Option
fields = ('id', 'title',)
class VoterSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
if type(instance) is str:
return instance
else:
return GestaltSerializer().to_representation(instance)
class PollSerializer(serializers.ModelSerializer):
options = OptionSerializer(many=True, read_only=True)
last_voted = serializers.SerializerMethodField()
def get_last_voted(self, instance: models.WorkaroundPoll):
last_vote = models.Vote.objects\
.filter(option__poll=instance)\
.order_by('time_updated')\
.last()
return last_vote.time_updated if last_vote else None
def to_representation(self, instance):
representation = super().to_representation(instance)
vote_data = models.resolve_vote(instance)
voter_serializer = VoterSerializer()
if instance.vote_type is models.VoteType.CONDORCET:
representation.update({
'options_winner': getattr(vote_data['winner'], 'id', None),
'options_ranking': [option.id for option in vote_data['ranking']],
'votes': [
dict(
voter=voter_serializer.to_representation(voter),
ranking=[
option.id for option in sorted(options.keys(),
key=lambda o: options[o], reverse=True)
]
) for voter, options in vote_data['votes'].items()
]
})
elif instance.vote_type is models.VoteType.SIMPLE:
representation.update({
'options_winner': getattr(vote_data['winner'], 'id', None),
'options_ranking': [
option.id for option, counts in
sorted(vote_data['vote_count'].items(),
key=lambda x: x[1].score, reverse=True)
],
'vote_counts': [
dict(option=option.id, score=counts.score, counts=counts.serialize())
for option, counts in vote_data['vote_count'].items()
],
'votes': [
dict(
voter=voter_serializer.to_representation(voter),
endorsements=[
dict(option=option.id, endorsement=vote.simplevote.endorse)
for option, vote in votes.items()
if isinstance(option, models.Option)
]
) for voter, votes in vote_data['votes'].items()
]
})
return representation
class Meta:
model = models.WorkaroundPoll
fields = ('id', 'options', 'last_voted',)
class EndorsementsSerializer(serializers.Serializer):
option = serializers.PrimaryKeyRelatedField(queryset=models.Option.objects)
endorsement = serializers.NullBooleanField()
class PollVoteSerializer(serializers.Serializer):
gestalt = GestaltOrAnonSerializer()
ranking = serializers.PrimaryKeyRelatedField(many=True, required=False,
queryset=models.Option.objects)
endorsements = EndorsementsSerializer(many=True, required=False)
@api_view(('POST',))
@permission_classes((permissions.AllowAny,))
def vote(request, pk):
try:
poll = models.WorkaroundPoll.objects.get(pk=pk)
except models.WorkaroundPoll.DoesNotExist:
raise Http404()
payload = PollVoteSerializer().to_internal_value(request.data)
can_vote = request.user.has_perm('polls.vote', poll.content.associations.first())
can_anon_vote = not request.user.is_anonymous or models.Vote.objects \
.filter(option__in=poll.options.all(), anonymous=payload['gestalt']['name']) \
.count() == 0
if not (can_vote and can_anon_vote):
if can_anon_vote:
raise PermissionDenied('VOTE_ERR_ANON_VOTED')
else:
raise PermissionDenied('VOTE_ERR_GESTALT_VOTED')
@contextmanager
def create_vote(base_model, option: models.Option):
new_vote = base_model() # type: models.Vote
new_vote.option = option
try:
new_vote.voter = request.user.gestalt
except AttributeError:
new_vote.anonymous = payload['gestalt']['name']
yield new_vote
new_vote.save()
if poll.vote_type is models.VoteType.CONDORCET:
num_options = len(payload['ranking'])
for (ranking, option) in enumerate(payload['ranking']):
with create_vote(models.CondorcetVote, option) as condorcet_vote:
condorcet_vote.rank = num_options - ranking
elif poll.vote_type is models.VoteType.SIMPLE:
for vote in payload['endorsements']:
with create_vote(models.SimpleVote, vote['option']) as simple_vote:
simple_vote.endorse = vote['endorsement']
return Response(status=200)
@permission_classes((permissions.AllowAny,))
class PollSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = PollSerializer
filter_fields = ('id',)
queryset = models.WorkaroundPoll.objects.all()
from django.conf.urls import url
from . import views, rest_api
from . import views
urlpatterns = [
url(
......@@ -17,5 +17,4 @@ urlpatterns = [
r'^(?P<entity_slug>[\w-]+)/(?P<association_slug>[\w-]+)/vote/$',
views.Vote.as_view(),
name='vote'),
url(r'^stadt/api/polls/(?P<pk>[^/.]+)/vote', rest_api.vote, name='polls-vote'),
]
......@@ -6,6 +6,8 @@ from grouprise import core
from grouprise.features.gestalten.models import Gestalt, GestaltSetting
from grouprise.features.groups.models import Group
from grouprise.features.images.models import Image
from grouprise.features.polls.models import Option, Vote, VoteType, WorkaroundPoll, \
resolve_vote
def validate_file_size(image):
......@@ -106,3 +108,95 @@ class ImageSerializer(serializers.ModelSerializer):
repr = super().to_representation(instance)
repr['preview'] = instance.preview_api.url
return repr
class OptionSerializer(serializers.ModelSerializer):
title = serializers.SerializerMethodField()
def get_title(self, instance: Option):
return str(instance)
class Meta:
model = Option
fields = ('id', 'title',)
class VoterSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
if type(instance) is str:
return instance
else:
return GestaltSerializer().to_representation(instance)
class PollSerializer(serializers.ModelSerializer):
options = OptionSerializer(many=True, read_only=True)
last_voted = serializers.SerializerMethodField()
def get_last_voted(self, instance: WorkaroundPoll):
last_vote = Vote.objects\
.filter(option__poll=instance)\
.order_by('time_updated')\
.last()
return last_vote.time_updated if last_vote else None
def to_representation(self, instance):
representation = super().to_representation(instance)
vote_data = resolve_vote(instance)
voter_serializer = VoterSerializer()
if instance.vote_type is VoteType.CONDORCET:
representation.update({
'options_winner': getattr(vote_data['winner'], 'id', None),
'options_ranking': [option.id for option in vote_data['ranking']],
'votes': [
dict(
voter=voter_serializer.to_representation(voter),
ranking=[
option.id for option in sorted(options.keys(),
key=lambda o: options[o], reverse=True)
]
) for voter, options in vote_data['votes'].items()
]
})
elif instance.vote_type is VoteType.SIMPLE:
representation.update({
'options_winner': getattr(vote_data['winner'], 'id', None),
'options_ranking': [
option.id for option, counts in
sorted(vote_data['vote_count'].items(),
key=lambda x: x[1].score, reverse=True)
],
'vote_counts': [
dict(option=option.id, score=counts.score, counts=counts.serialize())
for option, counts in vote_data['vote_count'].items()
],
'votes': [
dict(
voter=voter_serializer.to_representation(voter),
endorsements=[
dict(option=option.id, endorsement=vote.simplevote.endorse)
for option, vote in votes.items()
if isinstance(option, Option)
]
) for voter, votes in vote_data['votes'].items()
]
})
return representation
class Meta:
model = WorkaroundPoll
fields = ('id', 'options', 'last_voted',)
class EndorsementsSerializer(serializers.Serializer):
option = serializers.PrimaryKeyRelatedField(queryset=Option.objects)
endorsement = serializers.NullBooleanField()
class PollVoteSerializer(serializers.Serializer):
gestalt = GestaltOrAnonSerializer()
ranking = serializers.PrimaryKeyRelatedField(many=True, required=False,
queryset=Option.objects)
endorsements = EndorsementsSerializer(many=True, required=False)
from django.conf.urls import url
from rest_framework.routers import SimpleRouter
from grouprise.features.groups.rest_api import GroupSet
from grouprise.features.polls.rest_api import PollSet
from .views import GestaltSet, GestaltSettingSet, ImageSet, MarkdownView
from .views import GestaltSet, GestaltSettingSet, GroupSet, ImageSet, MarkdownView, PollSet, vote
router = SimpleRouter()
......@@ -12,3 +11,7 @@ router.register(r'gestalten/(?P<gestalt>\d+)/settings', GestaltSettingSet, 'gest
router.register(r'groups', GroupSet, 'group')
router.register(r'images', ImageSet, 'image')
router.register(r'polls', PollSet, 'polls')
urlpatterns = [
url(r'^polls/(?P<pk>[^/.]+)/vote', vote, name='polls-vote'),
]
from contextlib import contextmanager
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import Http404
from rest_framework import mixins, permissions, viewsets
from rest_framework.decorators import permission_classes
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from grouprise.core.templatetags.defaulttags import markdown
......@@ -12,9 +15,11 @@ from grouprise.features.galleries.models import GalleryImage
from grouprise.features.gestalten.models import Gestalt, GestaltSetting
from grouprise.features.images.models import Image
from grouprise.features.memberships.models import Membership
from grouprise.features.polls.models import CondorcetVote, Option, SimpleVote, Vote, \
VoteType, WorkaroundPoll
from .filters import GroupFilter, ImageFilter
from .serializers import GestaltSerializer, GestaltSettingSerializer, GroupSerializer, \
ImageSerializer
ImageSerializer, PollSerializer, PollVoteSerializer
_PRESETS = {
'content': {
......@@ -117,3 +122,55 @@ class MarkdownView(viewsets.ViewSet):
return Response({
'content': str(markdown(text, **preset))
})
@api_view(('POST',))
@permission_classes((permissions.AllowAny,))
def vote(request, pk):
try:
poll = WorkaroundPoll.objects.get(pk=pk)
except WorkaroundPoll.DoesNotExist:
raise Http404()
payload = PollVoteSerializer().to_internal_value(request.data)
can_vote = request.user.has_perm('polls.vote', poll.content.associations.first())
can_anon_vote = not request.user.is_anonymous or Vote.objects \
.filter(option__in=poll.options.all(), anonymous=payload['gestalt']['name']) \
.count() == 0
if not (can_vote and can_anon_vote):
if can_anon_vote:
raise PermissionDenied('VOTE_ERR_ANON_VOTED')
else:
raise PermissionDenied('VOTE_ERR_GESTALT_VOTED')
@contextmanager
def create_vote(base_model, option: Option):
new_vote = base_model() # type: Vote
new_vote.option = option
try:
new_vote.voter = request.user.gestalt
except AttributeError:
new_vote.anonymous = payload['gestalt']['name']
yield new_vote
new_vote.save()
if poll.vote_type is VoteType.CONDORCET:
num_options = len(payload['ranking'])
for (ranking, option) in enumerate(payload['ranking']):
with create_vote(CondorcetVote, option) as condorcet_vote:
condorcet_vote.rank = num_options - ranking
elif poll.vote_type is VoteType.SIMPLE:
for vote in payload['endorsements']:
with create_vote(SimpleVote, vote['option']) as simple_vote:
simple_vote.endorse = vote['endorsement']
return Response(status=200)
@permission_classes((permissions.AllowAny,))
class PollSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = PollSerializer
filter_fields = ('id',)
queryset = WorkaroundPoll.objects.all()
from django.urls import include, path
from .frontend.urls import router as frontend_router
from .frontend.urls import router as frontend_router, urlpatterns as frontend_patterns
from .transition_connect.urls import router as tc_router
......@@ -8,4 +8,4 @@ urlpatterns = [
path('stadt/api/', include(frontend_router.urls)),
path('stadt/api/auth/', include('rest_framework.urls', namespace='rest_framework')),
path('stadt/tc-api/', include(tc_router.urls)),
]
] + frontend_patterns
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment