summaryrefslogtreecommitdiff
path: root/papillon/polls
diff options
context:
space:
mode:
authoretienne2010-03-27 17:06:03 +0000
committeretienne2010-03-27 17:06:03 +0000
commit95729368a48257b110ea33dfb005785f8ed6734f (patch)
tree8b99b375a4b4a2e121a9192910af15485ea2b7c3 /papillon/polls
parent8ded76ae513b38d14773449e46543b54cf48ef49 (diff)
Rearrange directories - include documentation
Diffstat (limited to 'papillon/polls')
-rw-r--r--papillon/polls/__init__.py0
-rw-r--r--papillon/polls/admin.py34
-rw-r--r--papillon/polls/feeds.py55
-rw-r--r--papillon/polls/forms.py132
-rw-r--r--papillon/polls/models.py227
-rw-r--r--papillon/polls/templatetags/__init__.py0
-rw-r--r--papillon/polls/templatetags/get_range.py25
-rw-r--r--papillon/polls/views.py491
8 files changed, 964 insertions, 0 deletions
diff --git a/papillon/polls/__init__.py b/papillon/polls/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/papillon/polls/__init__.py
diff --git a/papillon/polls/admin.py b/papillon/polls/admin.py
new file mode 100644
index 0000000..2207c60
--- /dev/null
+++ b/papillon/polls/admin.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+"""
+Settings for administration pages
+"""
+
+from papillon.polls.models import Poll, Category
+from django.contrib import admin
+
+class PollAdmin(admin.ModelAdmin):
+ search_fields = ("name",)
+ list_display = ('name', 'category', 'modification_date', 'public', 'open')
+ list_filter = ('public', 'open', 'category')
+
+# register of differents database fields
+admin.site.register(Category)
+admin.site.register(Poll, PollAdmin)
diff --git a/papillon/polls/feeds.py b/papillon/polls/feeds.py
new file mode 100644
index 0000000..2d52dc7
--- /dev/null
+++ b/papillon/polls/feeds.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+import time
+
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.syndication.feeds import Feed
+from django.utils.translation import gettext_lazy as _
+
+from papillon.settings import BASE_SITE
+from papillon.polls.models import Poll, Vote, Voter
+
+
+class PollLatestEntries(Feed):
+ def get_object(self, poll_url):
+ if len(poll_url) < 1:
+ raise ObjectDoesNotExist
+ return Poll.objects.get(base_url=poll_url[0])
+
+ def title(self, obj):
+ return _("Papillon - poll : ") + obj.name
+
+ def link(self, obj):
+ if not obj:
+ raise FeedDoesNotExist
+ return BASE_SITE + "/poll/" + obj.base_url
+
+ def description(self, obj):
+ return obj.description
+
+ def item_link(self, voter):
+ url = "%s/poll/%s_%d" % (BASE_SITE, voter.poll.base_url,
+ time.mktime(voter.modification_date.timetuple()))
+ return url
+
+ def items(self, obj):
+ voters = Voter.objects.filter(poll__id=obj.id).\
+order_by('-modification_date')[:10]
+ return voters \ No newline at end of file
diff --git a/papillon/polls/forms.py b/papillon/polls/forms.py
new file mode 100644
index 0000000..3a151aa
--- /dev/null
+++ b/papillon/polls/forms.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2009 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+'''
+Forms management
+'''
+
+from datetime import datetime
+
+from django import forms
+from django.contrib.admin import widgets as adminwidgets
+from django.utils.translation import gettext_lazy as _
+
+from papillon.polls.models import Poll, Category, Choice, Comment
+from papillon import settings
+
+class TextareaWidget(forms.Textarea):
+ """
+ Manage the edition of a text using TinyMCE
+ """
+ class Media:
+ js = ["%stiny_mce.js" % settings.TINYMCE_URL,
+ "%stextareas.js" % settings.MEDIA_URL,]
+
+class PollForm(forms.ModelForm):
+ def __init__(self, *args, **kwargs):
+ super(PollForm, self).__init__(*args, **kwargs)
+ self.fields['description'].widget = TextareaWidget()
+
+class CreatePollForm(PollForm):
+ class Meta:
+ model = Poll
+ exclude = ['base_url', 'admin_url', 'open', 'author', 'enddate',
+ 'public', 'opened_admin', 'hide_choices']
+ if not Category.objects.all():
+ exclude.append('category')
+
+class CommentForm(forms.ModelForm):
+ class Meta:
+ model = Comment
+ exclude = ['date',]
+ def __init__(self, *args, **kwargs):
+ super(CommentForm, self).__init__(*args, **kwargs)
+ self.fields['text'].widget = TextareaWidget()
+
+# workaround for SplitDateTime with required=False
+class SplitDateTimeJSField(forms.SplitDateTimeField):
+ def __init__(self, *args, **kwargs):
+ super(SplitDateTimeJSField, self).__init__(*args, **kwargs)
+ self.widget.widgets[0].attrs = {'class': 'vDateField'}
+ self.widget.widgets[1].attrs = {'class': 'vTimeField'}
+
+class AdminPollForm(PollForm):
+ class Meta:
+ model = Poll
+ exclude = ['author', 'author_name', 'base_url', 'admin_url',
+ 'dated_choices', 'type']
+ if not Category.objects.all():
+ exclude.append('category')
+ enddate = SplitDateTimeJSField(widget=adminwidgets.AdminSplitDateTime(),
+ required=False, label=Poll._meta.get_field('enddate').verbose_name,
+ help_text=Poll._meta.get_field('enddate').help_text)
+
+class ChoiceForm(forms.ModelForm):
+ class Meta:
+ model = Choice
+ fields = ('name', 'limit', 'poll', 'order',)
+ def __init__(self, *args, **kwargs):
+ super(ChoiceForm, self).__init__(*args, **kwargs)
+ self.fields['poll'].widget = forms.HiddenInput()
+ self.fields['order'].widget = forms.HiddenInput()
+
+class DatedChoiceForm(ChoiceForm):
+ def __init__(self, *args, **kwargs):
+ super(DatedChoiceForm, self).__init__(*args, **kwargs)
+ self.fields['name'].widget = adminwidgets.AdminSplitDateTime()
+
+ def clean_name(self):
+ try:
+ poll_id = self.data['poll']
+ poll = Poll.objects.get(id=int(poll_id))
+ except (ValueError, Poll.DoesNotExist):
+ raise forms.ValidationError(_('Invalid poll'))
+ data = self.cleaned_data['name']
+ if poll.dated_choices:
+ # management of dates fields
+ if data.startswith('[') and data.endswith(']') and "'" in data:
+ datas = data.split("'")
+ try:
+ assert len(datas) == 5
+ time = datas[3]
+ if not time:
+ time = '00:00:00'
+ date = "%s %s" % (datas[1], time)
+ datetime.strptime(date, '%Y-%m-%d %H:%M:%S')
+ data = date
+ except (ValueError, AssertionError):
+ raise forms.ValidationError(_('Invalid date format: \
+YYYY-MM-DD HH:MM:SS'))
+ return data
+
+ def clean_limit(self):
+ """
+ data = eval(self.cleaned_data['name'])
+
+ new_limit = int(request.POST[key])
+ sum = choice.getSum()
+ if new_limit < sum:
+ response_dct['error'] = _("You cannot lower \
+%(name)s's limit to this number : there is currently %(sum)d votes for this \
+choice.") % {'name':choice.name, 'sum':sum}
+ else:
+ choice.limit = new_limit
+ choice.save()
+"""
+ pass
diff --git a/papillon/polls/models.py b/papillon/polls/models.py
new file mode 100644
index 0000000..f8b3b22
--- /dev/null
+++ b/papillon/polls/models.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+'''
+Models management
+'''
+
+import datetime
+
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+from papillon.settings import DAYS_TO_LIVE
+
+class Category(models.Model):
+ name = models.CharField(max_length=100)
+ description = models.TextField()
+ def __unicode__(self):
+ return self.name
+
+class PollUser(models.Model):
+ name = models.CharField(max_length=100)
+ email = models.CharField(max_length=100)
+ password = models.CharField(max_length=100)
+ modification_date = models.DateTimeField(auto_now=True)
+
+class Poll(models.Model):
+ base_url = models.CharField(max_length=100, help_text=_('Copy this \
+address and send it to voters who want to participate to this poll'))
+ admin_url = models.CharField(max_length=100, help_text=_("Address to \
+modify the current poll"))
+ author_name = models.CharField(verbose_name=_("Author name"),
+ max_length=100, help_text=_("Name, firstname or nickname of the author"))
+ author = models.ForeignKey(PollUser, null=True, blank=True)
+ name = models.CharField(max_length=200, verbose_name=_("Poll name"),
+ help_text=_("Global name to present the poll"))
+ description = models.CharField(max_length=1000,
+ verbose_name=_("Poll description"),
+ help_text=_("Precise description of the poll"))
+ category = models.ForeignKey(Category, null=True, blank=True)
+ TYPE = (('P', _('Yes/No poll')),
+ ('B', _('Yes/No/Maybe poll')),
+ ('O', _('One choice poll')),
+ ('V', _('Valuable choice poll')),)
+ type = models.CharField(max_length=1, choices=TYPE,
+ verbose_name=_("Type of the poll"),
+ help_text=_("""Type of the poll:
+
+ - "Yes/No poll" is the appropriate type for a simple multi-choice poll
+ - "Yes/No/Maybe poll" allows voters to stay undecided
+ - "One choice poll" gives only one option to choose from
+ - "Valuable choice poll" permit users to give a note between 0 to 9 to \
+different choices
+"""))
+ dated_choices = models.BooleanField(verbose_name=_("Choices are dates"),
+ default=False, help_text=_("Check this option to choose between dates"))
+ enddate = models.DateTimeField(null=True, blank=True,
+verbose_name=_("Closing date"), help_text=_("Closing date for participating to \
+the poll"))
+ modification_date = models.DateTimeField(auto_now=True)
+ public = models.BooleanField(default=False,
+verbose_name=_("Display the poll on main page"), help_text=_("Check this \
+option to make the poll public"))
+ opened_admin = models.BooleanField(default=False,
+verbose_name=_("Allow users to add choices"), help_text=_("Check this option \
+to open the poll to new choices submitted by users"))
+ hide_choices = models.BooleanField(default=False,
+verbose_name=_("Hide votes to new voters"), help_text=_("Check this option to \
+hide poll results to new users"))
+ open = models.BooleanField(default=True,
+verbose_name=_("State of the poll"), help_text=_("Uncheck this option to close \
+the poll/check this option to reopen it"))
+
+ def getTypeLabel(self):
+ idx = [type[0] for type in self.TYPE].index(self.type)
+ return Poll.TYPE[idx][1]
+
+ def checkForErasement(self):
+ '''Check if the poll has to be deleted'''
+ if not DAYS_TO_LIVE:
+ return
+ now = datetime.datetime.now()
+ dtl = datetime.timedelta(days=DAYS_TO_LIVE)
+ if self.modification_date + dtl > now:
+ return
+ voters = Voter.objects.filter(poll=self)
+ for voter in voters:
+ if voter.modification_date + dtl > now:
+ return
+ for voter in voters:
+ voter.user.delete()
+ voter.delete()
+ comments = Comment.objects.filter(poll=self)
+ for comment in comments:
+ comment.delete()
+ self.delete()
+
+ def getChoices(self):
+ """
+ Get choices associated to this vote"""
+ return Choice.objects.filter(poll=self)
+
+ def reorder(self):
+ """
+ Reorder choices of the poll"""
+ if not self.dated_choices:
+ return
+ choices = self.getChoices()
+ sort_fct = lambda x:datetime.datetime.strptime(x.name,
+ '%Y-%m-%d %H:%M:%S')
+ choices = sorted(choices, key=sort_fct)
+ for idx, choice in enumerate(choices):
+ choice.order = idx
+ choice.save()
+
+ class Admin:
+ pass
+ class Meta:
+ ordering = ['-modification_date']
+ def __unicode__(self):
+ return self.name
+
+class Comment(models.Model):
+ '''Comment for a poll'''
+ poll = models.ForeignKey(Poll)
+ author_name = models.CharField(max_length=100)
+ text = models.CharField(max_length=1000)
+ date = models.DateTimeField(auto_now_add=True)
+ class Meta:
+ ordering = ['date']
+
+class Voter(models.Model):
+ user = models.ForeignKey(PollUser)
+ poll = models.ForeignKey(Poll)
+ creation_date = models.DateTimeField(auto_now_add=True)
+ modification_date = models.DateTimeField(auto_now=True)
+ class Meta:
+ ordering = ['creation_date']
+ def __unicode__(self):
+ return _("Vote from %(user)s") % {'user':self.user.name}
+ def getVotes(self, choice_ids):
+ '''Get votes for a subset of choices
+ '''
+ query = Vote.objects.filter(voter=self)
+ query = query.extra(where=['choice_id IN (%s)' \
+ % ",".join([str(choice_id) for choice_id in choice_ids])])
+ return list(query.order_by('choice'))
+
+class Choice(models.Model):
+ poll = models.ForeignKey(Poll)
+ name = models.CharField(max_length=200)
+ order = models.IntegerField()
+ limit = models.IntegerField(null=True, blank=True)
+ available = models.BooleanField(default=True)
+ class Admin:
+ pass
+ class Meta:
+ ordering = ['order']
+
+ def get_date(self):
+ if not self.poll.dated_choices:
+ return self.name
+ return datetime.datetime.strptime(self.name, '%Y-%m-%d %H:%M:%S')
+
+ def set_date(self, value):
+ self._date = value
+ #if not self.poll.dated_choices:
+ # self.name = value
+ #self.name = datetime.strftime(value, '%Y-%m-%d %H:%M:%S')
+ date = property(get_date, set_date)
+
+ def getSum(self, balanced_poll=None):
+ '''Get the sum of votes for this choice'''
+ sum = 0
+ for vote in Vote.objects.filter(choice=self, value__isnull=False):
+ sum += vote.value
+ if balanced_poll:
+ return sum/2
+ return sum
+
+ def changeOrder(self, idx=1):
+ '''
+ Change a choice in the list
+ '''
+ if (self.order + idx) < 0:
+ return
+ choices = Choice.objects.filter(poll=self.poll)
+ if self.order + idx > len(choices):
+ return
+ new_order = self.order + idx
+ for choice in choices:
+ if choice == self:
+ continue
+ if idx < 0 and choice.order < self.order \
+ and choice.order >= new_order:
+ choice.order += 1
+ choice.save()
+ if idx > 0 and choice.order > self.order \
+ and choice.order <= new_order:
+ choice.order -= 1
+ choice.save()
+ self.order = new_order
+ self.save()
+
+class Vote(models.Model):
+ voter = models.ForeignKey(Voter)
+ choice = models.ForeignKey(Choice)
+ VOTE = ((1, (_('Yes'), _('Yes'))),
+ (0, (_('No'), _('Maybe')), ),
+ (-1, (_('No'), _('No'))),)
+ value = models.IntegerField(choices=VOTE, blank=True, null=True)
diff --git a/papillon/polls/templatetags/__init__.py b/papillon/polls/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/papillon/polls/templatetags/__init__.py
diff --git a/papillon/polls/templatetags/get_range.py b/papillon/polls/templatetags/get_range.py
new file mode 100644
index 0000000..b9d8328
--- /dev/null
+++ b/papillon/polls/templatetags/get_range.py
@@ -0,0 +1,25 @@
+from django.template import Library
+
+register = Library()
+
+@register.filter
+def get_range( value ):
+ """
+ Filter - returns a list containing range made from given value
+ Usage (in template):
+
+ <ul>{% for i in 3|get_range %}
+ <li>{{ i }}. Do something</li>
+{% endfor %}</ul>
+
+Results with the HTML:
+<ul>
+<li>0. Do something</li>
+<li>1. Do something</li>
+<li>2. Do something</li>
+</ul>
+
+Instead of 3 one may use the variable set in the views
+ """
+ return range(value)
+
diff --git a/papillon/polls/views.py b/papillon/polls/views.py
new file mode 100644
index 0000000..e9ef572
--- /dev/null
+++ b/papillon/polls/views.py
@@ -0,0 +1,491 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+'''
+Views management
+'''
+
+from random import choice as random_choice
+import string
+import time
+from datetime import datetime
+
+from django.utils.translation import gettext_lazy as _
+from django.shortcuts import render_to_response
+from django.http import HttpResponseRedirect
+
+from papillon.settings import LANGUAGES, BASE_SITE
+from papillon.polls.models import Poll, PollUser, Choice, Voter, Vote, \
+ Category, Comment
+from papillon.polls.forms import CreatePollForm, AdminPollForm, ChoiceForm, \
+ DatedChoiceForm, CommentForm
+
+def getBaseResponse(request):
+ """Manage basic fields for the template
+ If not null the second argument returned is a redirection.
+ """
+ url = BASE_SITE
+ # setting the current language and available languages
+ if 'language' in request.GET:
+ if request.GET['language'] in [language[0] for language in LANGUAGES]:
+ request.session['django_language'] = request.GET['language']
+ return None, HttpResponseRedirect(request.path)
+ languages = []
+ for language_code, language_label in LANGUAGES:
+ languages.append((language_code, language_label))
+ return {'root_url':url, 'languages':languages}, None
+
+def index(request):
+ "Main page"
+ response_dct, redirect = getBaseResponse(request)
+ if redirect:
+ return redirect
+ response_dct['polls'] = Poll.objects.filter(public=True, category=None)
+ response_dct['categories'] = Category.objects.all()
+ error = ''
+ if 'bad_poll' in request.GET:
+ response_dct['error'] = _("The poll requested don't exist (anymore?)")
+ return render_to_response('main.html', response_dct)
+
+def category(request, category_id):
+ "Page for a category"
+ response_dct, redirect = getBaseResponse(request)
+ if redirect:
+ return redirect
+ category = Category.objects.get(id=int(category_id))
+ response_dct['category'] = category
+ response_dct['polls'] = Poll.objects.filter(public=True, category=category)
+ return render_to_response('category.html', response_dct)
+
+def create(request):
+ '''Creation of a poll.
+ '''
+ def genRandomURL():
+ "Generation of a random url"
+ url = ''
+ while not url or Poll.objects.filter(base_url=url).count() or\
+ Poll.objects.filter(admin_url=url).count():
+ url = ''
+ chars = string.letters + string.digits
+ for i in xrange(6):
+ url += random_choice(chars)
+ url += str(int(time.time()))
+ return url
+
+ response_dct, redirect = getBaseResponse(request)
+ if redirect:
+ return redirect
+
+ if request.method == 'POST':
+ form = CreatePollForm(request.POST)
+ if form.is_valid():
+ poll = form.save()
+ poll.admin_url = genRandomURL()
+ poll.base_url = genRandomURL()
+ poll.save()
+ return HttpResponseRedirect('%seditChoicesAdmin/%s/' % (
+ response_dct['root_url'], poll.admin_url))
+ else:
+ form = CreatePollForm()
+ response_dct['form'] = form
+ return render_to_response('create.html', response_dct)
+
+def edit(request, admin_url):
+ '''Edition of a poll.
+ '''
+ response_dct, redirect = getBaseResponse(request)
+ if redirect:
+ return redirect
+ try:
+ poll = Poll.objects.filter(admin_url=admin_url)[0]
+ except IndexError:
+ # if the poll don't exist redirect to the creation page
+ url = response_dct['root_url']
+ return HttpResponseRedirect('%screate' % (
+ response_dct['root_url']))
+ Form = AdminPollForm
+
+ if request.method == 'POST':
+ form = Form(request.POST, instance=poll)
+ if form.is_valid():
+ poll = form.save()
+ return HttpResponseRedirect('%sedit/%s/' % (
+ response_dct['root_url'], poll.admin_url))
+ else:
+ form = Form(instance=poll)
+ response_dct['form'] = form
+ response_dct['poll'] = poll
+ return render_to_response('edit.html', response_dct)
+
+def editChoicesAdmin(request, admin_url):
+ response_dct, redirect = getBaseResponse(request)
+ if redirect:
+ return redirect
+ try:
+ poll = Poll.objects.filter(admin_url=admin_url)[0]
+ except IndexError:
+ # if the poll don't exist redirect to the main page
+ url = "/".join(request.path.split('/')[:-2])
+ return response_dct, HttpResponseRedirect(url)
+ response_dct['poll'] = poll
+ return editChoices(request, response_dct, admin=True)
+
+def editChoicesUser(request, poll_url):
+ response_dct, redirect = getBaseResponse(request)
+ if redirect:
+ return redirect
+ try:
+ poll = Poll.objects.filter(base_url=poll_url)[0]
+ except IndexError:
+ poll = None
+ if not poll or not poll.opened_admin:
+ # if the poll don't exist redirect to the main page
+ url = "/".join(request.path.split('/')[:-2])
+ return HttpResponseRedirect(url)
+ response_dct['poll'] = poll
+ return editChoices(request, response_dct)
+
+def editChoices(request, response_dct, admin=False):
+ '''Edition of choices.
+ '''
+ poll = response_dct['poll']
+ tpl = 'editChoicesAdmin.html'
+ if not admin:
+ tpl = 'editChoicesUser.html'
+ Form = ChoiceForm
+ if poll.dated_choices:
+ Form = DatedChoiceForm
+ try:
+ order = Choice.objects.order_by('-order')[0].order
+ order += 1
+ except IndexError:
+ order = 0
+ form = Form(initial={'poll':poll.id, 'order':str(order)})
+
+ if request.method == 'POST':
+ # if a new choice is submitted
+ if 'add' in request.POST and request.POST['poll'] == str(poll.id):
+ f = Form(request.POST)
+ if f.is_valid():
+ choice = f.save()
+ poll.reorder()
+ else:
+ form = f
+ if admin and 'edit' in request.POST \
+ and request.POST['poll'] == str(poll.id):
+ try:
+ choice = Choice.objects.get(id=int(request.POST['edit']))
+ if choice.poll != poll:
+ raise ValueError
+ f = Form(request.POST, instance=choice)
+ if f.is_valid():
+ choice = f.save()
+ poll.reorder()
+ except (Choice.DoesNotExist, ValueError):
+ pass
+ if admin:
+ # check if a choice has been choosen for deletion
+ for key in request.POST:
+ if key.startswith('delete_') and request.POST[key]:
+ try:
+ choice = Choice.objects.get(id=int(key[len('delete_'):]))
+ if choice.poll != poll:
+ raise ValueError
+ Vote.objects.filter(choice=choice).delete()
+ choice.delete()
+ except (Choice.DoesNotExist, ValueError):
+ pass
+ # check if the order of a choice has to be changed
+ if admin and request.method == 'GET':
+ for key in request.GET:
+ try:
+ current_url = request.path.split('?')[0]
+ if 'up_choice' in key:
+ choice = Choice.objects.get(id=int(request.GET[key]))
+ if choice.poll != poll:
+ raise ValueError
+ choice.changeOrder(-1)
+ poll.reorder()
+ # redirect in order to avoid a change with a refresh
+ return HttpResponseRedirect(current_url)
+ if 'down_choice' in key:
+ choice = Choice.objects.get(id=int(request.GET[key]))
+ if choice.poll != poll:
+ raise ValueError
+ choice.changeOrder(1)
+ poll.reorder()
+ # redirect in order to avoid a change with a refresh
+ return HttpResponseRedirect(current_url)
+ except (ValueError, Choice.DoesNotExist):
+ pass
+ choices = Choice.objects.filter(poll=poll).order_by('order')
+ for choice in choices:
+ if admin and poll.dated_choices:
+ choice.name = choice.date
+ choice.form = Form(instance=choice)
+ response_dct['choices'] = choices
+ response_dct['form_new_choice'] = form
+ return render_to_response(tpl, response_dct)
+
+def poll(request, poll_url):
+ """Display a poll
+ poll_url is given to identify the poll. If '_' is in the poll_url the second
+ part of the url is the unix time given to highlight a particular vote
+ modification
+ """
+
+ def modifyVote(request, choices):
+ "Modify user's votes"
+ try:
+ voter = Voter.objects.filter(
+ id=int(request.POST['voter']))[0]
+ except (ValueError, IndexError):
+ return
+ # if no author_name is given deletion of associated votes and
+ # author
+ if not request.POST['author_name']:
+ # verify if the author can be deleted
+ delete_user = None
+ if not voter.user.password:
+ v = Voter.objects.filter(user=voter.user)
+ if len(v) == 1 and v[0] == voter:
+ delete_user = voter.user
+ for choice in choices:
+ v = Vote.objects.filter(voter=voter, choice=choice)
+ v.delete()
+ voter.delete()
+ if delete_user:
+ delete_user.delete()
+ return
+ # update the name
+ voter.user.name = request.POST['author_name']
+ voter.user.save()
+ # update the modification date
+ voter.save()
+ selected_choices = []
+ # set the selected choices
+ for key in request.POST:
+ # modify a one choice poll
+ if key == 'vote' and request.POST[key]:
+ try:
+ id = int(request.POST[key])
+ vote = Vote.objects.filter(id=id)[0]
+ if vote.choice not in choices:
+ # bad vote id : the associated choice has
+ # probably been deleted
+ vote.delete()
+ else:
+ vote.value = 1
+ vote.save()
+ selected_choices.append(vote.choice)
+ except (ValueError, IndexError):
+ # the vote don't exist anymore
+ pass
+ # modify an existing vote
+ if key.startswith('vote_') and request.POST[key]:
+ try:
+ id = int(key.split('_')[1])
+ vote = Vote.objects.filter(id=id)[0]
+ if vote.choice not in choices:
+ # bad vote id : the associated choice has
+ # probably been deleted
+ vote.delete()
+ else:
+ # try if a specific value is specified in the form
+ # like in balanced poll
+ try:
+ value = int(request.POST[key])
+ except ValueError:
+ value = 1
+ vote.value = value
+ vote.save()
+ selected_choices.append(vote.choice)
+ except (ValueError, IndexError):
+ # the vote don't exist anymore
+ pass
+ # update non selected choices
+ for choice in choices:
+ if choice not in selected_choices:
+ try:
+ v = Vote.objects.filter(voter=voter, choice=choice)[0]
+ v.value = 0
+ except IndexError:
+ # the vote don't exist with this choice : probably
+ # a new choice
+ v = Vote(voter=voter, choice=choice, value=0)
+ v.save()
+ def newComment(request, poll):
+ "Comment the poll"
+ if 'comment_author' not in request.POST \
+ or not request.POST['comment_author'] \
+ or not request.POST['comment']:
+ return
+ c = Comment(poll=poll, author_name=request.POST['comment_author'],
+ text=request.POST['comment'])
+ c.save()
+
+ def newVote(request, choices):
+ "Create new votes"
+ if not request.POST['author_name']:
+ return
+ author = PollUser(name=request.POST['author_name'])
+ author.save()
+ voter = Voter(user=author, poll=poll)
+ voter.save()
+ selected_choices = []
+
+ # set the selected choices
+ for key in request.POST:
+ # standard vote
+ if key.startswith('choice_') and request.POST[key]:
+ try:
+ id = int(key.split('_')[1])
+ choice = Choice.objects.filter(id=id)[0]
+ if choice not in choices:
+ raise ValueError
+ # try if a specific value is specified in the form
+ # like in balanced poll
+ try:
+ value = int(request.POST[key])
+ except ValueError:
+ value = 1
+ v = Vote(voter=voter, choice=choice, value=value)
+ v.save()
+ selected_choices.append(choice)
+ except (ValueError, IndexError):
+ # bad choice id : the choice has probably been deleted
+ pass
+ # one choice vote
+ if key == 'choice' and request.POST[key]:
+ try:
+ id = int(request.POST[key])
+ choice = Choice.objects.filter(id=id)[0]
+ if choice not in choices:
+ raise ValueError
+ v = Vote(voter=voter, choice=choice, value=1)
+ v.save()
+ selected_choices.append(choice)
+ except (ValueError, IndexError):
+ # bad choice id : the choice has probably been deleted
+ pass
+ # set non selected choices
+ for choice in choices:
+ if choice not in selected_choices:
+ v = Vote(voter=voter, choice=choice, value=0)
+ v.save()
+ # results can now be displayed
+ request.session['knowned_vote_' + poll.base_url] = 1
+ response_dct, redirect = getBaseResponse(request)
+ if redirect:
+ return redirect
+ highlight_vote_date = None
+ if '_' in poll_url:
+ url_spl = poll_url.split('_')
+ if len(url_spl) == 2:
+ poll_url, highlight_vote_date = url_spl
+ try:
+ highlight_vote_date = int(highlight_vote_date)
+ except ValueError:
+ highlight_vote_date = None
+ try:
+ poll = Poll.objects.filter(base_url=poll_url)[0]
+ except IndexError:
+ poll = None
+ choices = list(Choice.objects.filter(poll=poll))
+ # if the poll don't exist or if it has no choices the user is
+ # redirected to the main page
+ if not choices or not poll:
+ url = "/".join(request.path.split('/')[:-3])
+ url += "/?bad_poll=1"
+ return HttpResponseRedirect(url)
+
+ # a vote is submitted
+ if 'author_name' in request.POST and poll.open:
+ if 'voter' in request.POST:
+ # modification of an old vote
+ modifyVote(request, choices)
+ else:
+ newVote(request, choices)
+ # update the modification date of the poll
+ poll.save()
+ if 'comment' in request.POST and poll.open:
+ # comment posted
+ newComment(request, poll)
+
+ # 'voter' is in request.GET when the edit button is pushed
+ if 'voter' in request.GET and poll.open:
+ try:
+ response_dct['current_voter_id'] = int(request.GET['voter'])
+ except ValueError:
+ pass
+
+ response_dct.update({'poll':poll,
+ 'VOTE':Vote.VOTE,})
+ response_dct['base_url'] = "/".join(request.path.split('/')[:-2]) \
+ + '/%s/' % poll.base_url
+
+ # get voters and sum for each choice for this poll
+ voters = Voter.objects.filter(poll=poll)
+ choice_ids = [choice.id for choice in choices]
+ for voter in voters:
+ # highlight a voter
+ if time.mktime(voter.modification_date.timetuple()) \
+ == highlight_vote_date:
+ voter.highlight = True
+ voter.votes = voter.getVotes(choice_ids)
+ # initialize undefined vote
+ choice_vote_ids = [vote.choice.id for vote in voter.votes]
+ for choice in choices:
+ if choice.id not in choice_vote_ids:
+ vote = Vote(voter=voter, choice=choice, value=None)
+ vote.save()
+ idx = choices.index(choice)
+ voter.votes.insert(idx, vote)
+ sums = [choice.getSum(poll.type == 'B') for choice in choices]
+ vote_max = max(sums)
+ c_idx = 0
+ while c_idx < len(choices):
+ try:
+ c_idx = sums.index(vote_max, c_idx)
+ choices[c_idx].highlight = True
+ c_idx += 1
+ except ValueError:
+ c_idx = len(choices)
+ # set non-available choices if the limit is reached for a choice
+ response_dct['limit_set'] = None
+ for choice in choices:
+ if choice.limit:
+ response_dct['limit_set'] = True
+ if choice.limit and sums[choices.index(choice)] >= choice.limit:
+ choice.available = False
+ else:
+ choice.available = True
+ choice.save()
+ response_dct['voters'] = voters
+ response_dct['choices'] = choices
+ response_dct['comments'] = Comment.objects.filter(poll=poll)
+ # verify if vote's result has to be displayed
+ response_dct['hide_vote'] = poll.hide_choices
+ if poll.hide_choices:
+ if u'display_result' in request.GET:
+ request.session['knowned_vote_' + poll.base_url] = 1
+ if 'knowned_vote_' + poll.base_url in request.session:
+ response_dct['hide_vote'] = False
+ response_dct['form_comment'] = CommentForm()
+ return render_to_response('vote.html', response_dct)