Skip to content

Commit

Permalink
Merge pull request #283 from liqd/2017-05-jd-polls-frontend
Browse files Browse the repository at this point in the history
Allow votes from the user frontend
  • Loading branch information
vellip committed May 8, 2017
2 parents 476bf8c + baf53a5 commit ef8c037
Show file tree
Hide file tree
Showing 22 changed files with 967 additions and 149 deletions.
Empty file added apps/contrib/api/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions apps/contrib/api/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.http import Http404

from rest_framework import status
from rest_framework.request import clone_request
from rest_framework.response import Response


class AllowPUTAsCreateMixin(object):
"""Allow Put-as-create behaviour for incoming requests."""

def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object_or_none()
serializer = self.get_serializer(instance,
data=request.data,
partial=partial)
serializer.is_valid(raise_exception=True)

if instance is None:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)

serializer.save()
return Response(serializer.data)

def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)

def get_object_or_none(self):
try:
return self.get_object()
except Http404:
if self.request.method == 'PUT':
# For PUT-as-create operation, we need to ensure that we have
# relevant permissions, as if this was a POST request. This
# will either raise a PermissionDenied exception, or simply
# return None.
self.check_permissions(clone_request(self.request, 'POST'))
else:
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
15 changes: 15 additions & 0 deletions apps/contrib/static/js/Alert.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
var React = require('react')

const Alert = ({type, message}) => {
if (type) {
return (
<p className={`alert ${type}`}>
{message}
</p>
)
}

return null
}

module.exports = Alert
17 changes: 17 additions & 0 deletions apps/contrib/static/js/ErrorList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var React = require('react')

const ErrorList = ({errors}) => {
if (errors && errors.label) {
return (
<ul className="errorlist">
{errors.label.map(function (msg, index) {
return <li key={msg}>{msg}</li>
})}
</ul>
)
}

return null
}

module.exports = ErrorList
64 changes: 64 additions & 0 deletions apps/polls/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from django.shortcuts import get_object_or_404
from rest_framework import mixins
from rest_framework import viewsets

from adhocracy4.api.permissions import ViewSetRulesPermission
from apps.contrib.api.mixins import AllowPUTAsCreateMixin
from .models import Poll
from .models import Question
from .models import Vote
from .serializers import PollSerializer
from .serializers import VoteSerializer


class PollViewSet(mixins.UpdateModelMixin,
viewsets.GenericViewSet):
queryset = Poll.objects.all()
serializer_class = PollSerializer
permission_classes = (ViewSetRulesPermission,)

def get_permission_object(self):
poll = self.get_object()
return poll.module


class VoteViewSetRulesPermission(ViewSetRulesPermission):
"""Ensures the permission object is returned on update."""

non_object_actions = ['list', 'create', 'update']


class VoteViewSet(AllowPUTAsCreateMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
queryset = Vote.objects.all()
serializer_class = VoteSerializer
permission_classes = (VoteViewSetRulesPermission,)

def dispatch(self, request, *args, **kwargs):
self.question_pk = int(kwargs['pk'])
return super().dispatch(request, *args, **kwargs)

@property
def question(self):
return get_object_or_404(
Question,
pk=self.question_pk
)

def get_object(self):
return get_object_or_404(
Vote,
creator=self.request.user,
choice__question=self.question_pk
)

def get_permission_object(self):
return self.question.poll.module

def get_serializer_context(self):
context = super(VoteViewSet, self).get_serializer_context()
context.update({
'question_pk': self.question_pk,
})
return context
43 changes: 43 additions & 0 deletions apps/polls/assets/ChoiceForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
var React = require('react')
var django = require('django')
var ErrorList = require('../../contrib/static/js/ErrorList')

let ChoiceForm = React.createClass({
handleLabelChange: function (e) {
var index = this.props.index
var label = e.target.value
this.props.updateChoiceLabel(index, label)
},

handleDelete: function () {
this.props.deleteChoice(this.props.index)
},

render: function () {
return (
<div>
<label
htmlFor={'id_choices-' + this.props.key + '-name'}>
{django.gettext('Choice:')}
</label>
<input
id={'id_choices-' + this.props.key + '-name'}
name={'choices-' + this.props.key + '-name'}
type="text"
defaultValue={this.props.choice.label}
onChange={this.handleLabelChange} />
<div className="button-group">
<button
className="button"
onClick={this.handleDelete}
type="button">
<i className="fa fa-trash" />
</button>
</div>
<ErrorList errors={this.props.errors} />
</div>
)
}
})

module.exports = ChoiceForm
Loading

0 comments on commit ef8c037

Please sign in to comment.