diff options
Diffstat (limited to 'papillon/polls')
-rw-r--r-- | papillon/polls/__init__.py | 0 | ||||
-rw-r--r-- | papillon/polls/admin.py | 34 | ||||
-rw-r--r-- | papillon/polls/feeds.py | 55 | ||||
-rw-r--r-- | papillon/polls/forms.py | 132 | ||||
-rw-r--r-- | papillon/polls/models.py | 227 | ||||
-rw-r--r-- | papillon/polls/templatetags/__init__.py | 0 | ||||
-rw-r--r-- | papillon/polls/templatetags/get_range.py | 25 | ||||
-rw-r--r-- | papillon/polls/views.py | 491 |
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) |