Add Poll-system to the new motion app
This commit is contained in:
parent
adeaf248ef
commit
da7a7a9d68
@ -267,6 +267,13 @@ class Motion(SlideMixin, models.Model):
|
|||||||
#else:
|
#else:
|
||||||
#self.writelog(_("Supporter: -%s") % (person))
|
#self.writelog(_("Supporter: -%s") % (person))
|
||||||
|
|
||||||
|
def create_poll(self):
|
||||||
|
# TODO: auto increment the poll_number in the Database
|
||||||
|
poll_number = self.polls.aggregate(Max('poll_number'))['poll_number__max'] or 0
|
||||||
|
poll = MotionPoll.objects.create(motion=self, poll_number=poll_number + 1)
|
||||||
|
poll.set_options()
|
||||||
|
return poll
|
||||||
|
|
||||||
|
|
||||||
class MotionVersion(models.Model):
|
class MotionVersion(models.Model):
|
||||||
title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title"))
|
title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title"))
|
||||||
@ -307,3 +314,42 @@ class Comment(models.Model):
|
|||||||
text = models.TextField()
|
text = models.TextField()
|
||||||
author = PersonField()
|
author = PersonField()
|
||||||
creation_time = models.DateTimeField(auto_now=True)
|
creation_time = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|
||||||
|
class MotionVote(BaseVote):
|
||||||
|
option = models.ForeignKey('MotionOption')
|
||||||
|
|
||||||
|
|
||||||
|
class MotionOption(BaseOption):
|
||||||
|
poll = models.ForeignKey('MotionPoll')
|
||||||
|
vote_class = MotionVote
|
||||||
|
|
||||||
|
|
||||||
|
class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
|
||||||
|
option_class = MotionOption
|
||||||
|
vote_values = [
|
||||||
|
ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')]
|
||||||
|
|
||||||
|
motion = models.ForeignKey(Motion, related_name='polls')
|
||||||
|
poll_number = models.PositiveIntegerField(default=1)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("motion", "poll_number")
|
||||||
|
|
||||||
|
def get_absolute_url(self, link='edit'):
|
||||||
|
return reverse('motion_poll_edit', args=[str(self.motion.pk),
|
||||||
|
str(self.poll_number)])
|
||||||
|
|
||||||
|
def get_motion(self):
|
||||||
|
return self.motion
|
||||||
|
|
||||||
|
def set_options(self):
|
||||||
|
#TODO: maybe it is possible with .create() to call this without poll=self
|
||||||
|
self.get_option_class()(poll=self).save()
|
||||||
|
|
||||||
|
def append_pollform_fields(self, fields):
|
||||||
|
CountInvalid.append_pollform_fields(self, fields)
|
||||||
|
CountVotesCast.append_pollform_fields(self, fields)
|
||||||
|
|
||||||
|
def get_ballot(self):
|
||||||
|
return self.motion.motionpoll_set.filter(id__lte=self.id).count()
|
||||||
|
@ -22,4 +22,64 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>{% trans "Vote results" %}:</h4>
|
||||||
|
{% with motion.polls.all as polls %}
|
||||||
|
{% if not polls.exists %}
|
||||||
|
{% if perms.motion.can_manage_motion %}
|
||||||
|
<a href="{% url 'motion_poll_create' motion.id %}">
|
||||||
|
<span class="button">
|
||||||
|
<span class="icon statistics">{% trans 'New vote' %}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<ul class="results">
|
||||||
|
{% for poll in polls %}
|
||||||
|
{% if perms.motion.can_manage_motion or poll.has_votes %}
|
||||||
|
<li>
|
||||||
|
{% if perms.motion.can_manage_motion %}
|
||||||
|
<strong>{{ forloop.counter }}. {% trans "Vote" %} </strong>
|
||||||
|
<a class="icon edit" href="{% model_url poll %}" title="{% trans 'Edit Vote' %}">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
<a class="icon delete" href="{% model_url poll %}" title="{% trans 'Delete Vote' %}">
|
||||||
|
<span></span>
|
||||||
|
</a>
|
||||||
|
{% elif poll.has_votes %}
|
||||||
|
<strong>{{ forloop.counter }}. {% trans "Vote" %}:</strong>
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% if poll.has_votes %}
|
||||||
|
{% with poll.get_options.0 as option %}
|
||||||
|
<img src="{% static 'images/icons/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }}<br>
|
||||||
|
<img src="{% static 'images/icons/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }}<br>
|
||||||
|
<img src="{% static 'images/icons/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br>
|
||||||
|
<img src="{% static 'images/icons/voting-invalid.png' %}" title="{% trans 'Invalid' %}"> {{ poll.print_votesinvalid }}<br>
|
||||||
|
<div style="border-top: 1px solid; padding-top: 5px; margin: 5px 0; width: 10em;">
|
||||||
|
<img src="{% static 'images/icons/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% if perms.motion.can_manage_motion %}
|
||||||
|
{% if forloop.last %}
|
||||||
|
<a href="{% url 'motion_poll_create' motion.pk %}">
|
||||||
|
<span class="button"><span class="icon statistics">{% trans 'New vote' %}</span></span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if perms.motion.can_manage_motion %}
|
||||||
|
<a href="{% model_url poll %}">
|
||||||
|
<span class="button"><span class="icon statistics">{% trans 'Enter result' %}</span></span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endwith %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
48
openslides/motion/templates/motion/poll_form.html
Normal file
48
openslides/motion/templates/motion/poll_form.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ motion }}</h1>
|
||||||
|
<i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i>
|
||||||
|
<form action="" method="post" class="small-form">{% csrf_token %}
|
||||||
|
{{ pre_form }}
|
||||||
|
<span id="poll_id" style="display:none">{{ object.pk }}</span>
|
||||||
|
<table class="table" style="width: auto;">
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Option" %}</th>
|
||||||
|
<th>{% trans "Votes" %}</th>
|
||||||
|
</tr>
|
||||||
|
{% for value in forms.0 %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ value.label }}</td>
|
||||||
|
<td>{{ value.errors }}{{ value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
<tr class="total">
|
||||||
|
<td>{% trans "Invalid votes" %}</td>
|
||||||
|
<td>{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="total">
|
||||||
|
<td>{% trans "Votes cast" %}</td>
|
||||||
|
<td>{{ pollform.votescast.errors }}{{ pollform.votescast }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{ post_form }}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button class="button" type="submit">
|
||||||
|
<span class="icon ok">{% trans 'Save' %}</span>
|
||||||
|
</button>
|
||||||
|
<button class="button" type="submit" name="apply">
|
||||||
|
<span class="icon apply">{% trans 'Apply' %}</span>
|
||||||
|
</button>
|
||||||
|
<a href="{% model_url motion %}">
|
||||||
|
<span class="icon cancel">{% trans 'Cancel' %}</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -47,4 +47,19 @@ urlpatterns = patterns('openslides.motion.views',
|
|||||||
'motion_unsupport',
|
'motion_unsupport',
|
||||||
name='motion_unsupport',
|
name='motion_unsupport',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/create_poll/$',
|
||||||
|
'poll_create',
|
||||||
|
name='motion_poll_create',
|
||||||
|
),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/poll/(?P<poll_number>\d+)/edit$',
|
||||||
|
'poll_edit',
|
||||||
|
name='motion_poll_edit',
|
||||||
|
),
|
||||||
|
|
||||||
|
## url(r'^poll/(?P<poll_id>\d+)/del/$',
|
||||||
|
## 'delete_poll',
|
||||||
|
## name='motion_poll_delete',
|
||||||
|
## ),
|
||||||
)
|
)
|
||||||
|
@ -22,13 +22,14 @@ from django.http import Http404
|
|||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.views import (
|
from openslides.utils.views import (
|
||||||
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
|
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
|
||||||
DetailView, ListView, FormView, QuestionMixin)
|
DetailView, ListView, FormView, QuestionMixin, SingleObjectMixin)
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.template import Tab
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
|
from openslides.poll.views import PollFormView
|
||||||
from openslides.projector.api import get_active_slide
|
from openslides.projector.api import get_active_slide
|
||||||
from openslides.projector.projector import Widget, SLIDE
|
from openslides.projector.projector import Widget, SLIDE
|
||||||
from openslides.config.models import config
|
from openslides.config.models import config
|
||||||
from .models import Motion, MotionSubmitter, MotionSupporter
|
from .models import Motion, MotionSubmitter, MotionSupporter, MotionPoll
|
||||||
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
|
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
|
||||||
MotionCreateNewVersionMixin, ConfigForm)
|
MotionCreateNewVersionMixin, ConfigForm)
|
||||||
|
|
||||||
@ -81,14 +82,16 @@ class MotionMixin(object):
|
|||||||
def post_save(self, form):
|
def post_save(self, form):
|
||||||
super(MotionMixin, self).post_save(form)
|
super(MotionMixin, self).post_save(form)
|
||||||
# TODO: only delete and save neccessary submitters and supporter
|
# TODO: only delete and save neccessary submitters and supporter
|
||||||
self.object.submitter.all().delete()
|
if 'submitter' in form.cleaned_data:
|
||||||
self.object.supporter.all().delete()
|
self.object.submitter.all().delete()
|
||||||
MotionSubmitter.objects.bulk_create(
|
MotionSubmitter.objects.bulk_create(
|
||||||
[MotionSubmitter(motion=self.object, person=person)
|
[MotionSubmitter(motion=self.object, person=person)
|
||||||
for person in form.cleaned_data['submitter']])
|
for person in form.cleaned_data['submitter']])
|
||||||
MotionSupporter.objects.bulk_create(
|
if 'supporter' in form.cleaned_data:
|
||||||
[MotionSupporter(motion=self.object, person=person)
|
self.object.supporter.all().delete()
|
||||||
for person in form.cleaned_data['supporter']])
|
MotionSupporter.objects.bulk_create(
|
||||||
|
[MotionSupporter(motion=self.object, person=person)
|
||||||
|
for person in form.cleaned_data['supporter']])
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
form_classes = [BaseMotionForm]
|
form_classes = [BaseMotionForm]
|
||||||
@ -180,6 +183,47 @@ motion_support = SupportView.as_view(support=True)
|
|||||||
motion_unsupport = SupportView.as_view(support=False)
|
motion_unsupport = SupportView.as_view(support=False)
|
||||||
|
|
||||||
|
|
||||||
|
class PollCreateView(SingleObjectMixin, RedirectView):
|
||||||
|
permission_required = 'motion.can_manage_motion'
|
||||||
|
model = Motion
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
return super(PollCreateView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
|
self.poll = self.object.create_poll()
|
||||||
|
messages.success(request, _("New vote was successfully created."))
|
||||||
|
|
||||||
|
def get_redirect_url(self, **kwargs):
|
||||||
|
return reverse('motion_poll_edit', args=[self.object.pk, self.poll.poll_number])
|
||||||
|
|
||||||
|
poll_create = PollCreateView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
class PollUpdateView(PollFormView):
|
||||||
|
permission_required = 'motion.can_manage_motion'
|
||||||
|
poll_class = MotionPoll
|
||||||
|
template_name = 'motion/poll_form.html'
|
||||||
|
success_url_name = 'motion_detail'
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return MotionPoll.objects.filter(
|
||||||
|
motion=self.kwargs['pk'],
|
||||||
|
poll_number=self.kwargs['poll_number']).get()
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(PollUpdateView, self).get_context_data(**kwargs)
|
||||||
|
context.update({
|
||||||
|
'motion': self.poll.motion})
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_url_name_args(self):
|
||||||
|
return [self.poll.motion.pk]
|
||||||
|
|
||||||
|
poll_edit = PollUpdateView.as_view()
|
||||||
|
|
||||||
|
|
||||||
class Config(FormView):
|
class Config(FormView):
|
||||||
permission_required = 'config.can_manage_config'
|
permission_required = 'config.can_manage_config'
|
||||||
form_class = ConfigForm
|
form_class = ConfigForm
|
||||||
|
Loading…
Reference in New Issue
Block a user