diff options
Diffstat (limited to 'papillon/polls')
| -rw-r--r-- | papillon/polls/admin.py | 3 | ||||
| -rw-r--r-- | papillon/polls/feeds.py | 13 | ||||
| -rw-r--r-- | papillon/polls/forms.py | 30 | ||||
| -rw-r--r-- | papillon/polls/models.py | 105 | ||||
| -rw-r--r-- | papillon/polls/views.py | 80 |
5 files changed, 139 insertions, 92 deletions
diff --git a/papillon/polls/admin.py b/papillon/polls/admin.py index 2207c60..a5021b6 100644 --- a/papillon/polls/admin.py +++ b/papillon/polls/admin.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2008 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2008-2016 É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 @@ -24,6 +24,7 @@ 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') diff --git a/papillon/polls/feeds.py b/papillon/polls/feeds.py index b062590..d5fc515 100644 --- a/papillon/polls/feeds.py +++ b/papillon/polls/feeds.py @@ -24,8 +24,9 @@ from django.core.exceptions import ObjectDoesNotExist from django.contrib.syndication.views import Feed from django.utils.translation import gettext_lazy as _ from django.utils.safestring import mark_safe +from django.contrib.syndication.views import FeedDoesNotExist -from papillon.polls.models import Poll, Vote, Voter +from papillon.polls.models import Poll, Voter class PollLatestEntries(Feed): @@ -49,14 +50,14 @@ class PollLatestEntries(Feed): return mark_safe(obj.description) def item_link(self, voter): - url = reverse('poll', args=[voter.poll.base_url]) + url = reverse('poll', args=[voter.poll.base_url]) url = self.request.build_absolute_uri(reverse('poll', - args=[voter.poll.base_url])) - url = "%s_%d" % (url[:-1], # dirty... + args=[voter.poll.base_url])) + url = "%s_%d" % (url[:-1], # dirty... time.mktime(voter.modification_date.timetuple())) return url def items(self, obj): - voters = Voter.objects.filter(poll=obj - ).order_by('-modification_date')[:10] + voters = Voter.objects.filter( + poll=obj).order_by('-modification_date')[:10] return voters diff --git a/papillon/polls/forms.py b/papillon/polls/forms.py index 2e9b75d..359410d 100644 --- a/papillon/polls/forms.py +++ b/papillon/polls/forms.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2009 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2009-2016 É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 @@ -30,42 +30,50 @@ from django.utils.translation import gettext_lazy as _ from papillon.polls.models import Poll, Category, Choice, Comment from django.conf 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,] + "%stextareas.js" % settings.STATIC_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'] + 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',] + 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 @@ -75,19 +83,23 @@ class AdminPollForm(PollForm): exclude.append('category') if not settings.ALLOW_FRONTPAGE_POLL: exclude.append('public') - enddate = SplitDateTimeJSField(widget=adminwidgets.AdminSplitDateTime(), + enddate = SplitDateTimeJSField( + widget=adminwidgets.AdminSplitDateTime(), required=False, label=Poll._meta.get_field('enddate').verbose_name, - help_text=Poll._meta.get_field('enddate').help_text) + 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) @@ -110,7 +122,7 @@ class DatedChoiceForm(ChoiceForm): if not time: time = '00:00:00' date = "%s %s" % (datas[1], time) - datetime.strptime(date, '%Y-%m-%d %H:%M:%S') + datetime.strptime(date, '%Y-%m-%d %H:%M:%S') data = date except (ValueError, AssertionError): raise forms.ValidationError(_('Invalid date format: \ @@ -120,7 +132,7 @@ YYYY-MM-DD HH:MM:SS')) def clean_limit(self): """ data = eval(self.cleaned_data['name']) - + new_limit = int(request.POST[key]) sum = choice.getSum() if new_limit < sum: diff --git a/papillon/polls/models.py b/papillon/polls/models.py index 411e75f..506ad79 100644 --- a/papillon/polls/models.py +++ b/papillon/polls/models.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2008-2011 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2008-2016 É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 @@ -28,39 +28,45 @@ 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 \ + 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_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")) + 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: + 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 @@ -68,23 +74,27 @@ modify the current poll")) - "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")) + 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 \ + 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): @@ -122,8 +132,8 @@ the poll/check this option to reopen it")) if not self.dated_choices: return choices = self.getChoices() - sort_fct = lambda x:datetime.datetime.strptime(x.name, - '%Y-%m-%d %H:%M:%S') + 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 @@ -131,45 +141,57 @@ the poll/check this option to reopen it")) 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, related_name='comments') 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} + 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])]) + 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'] @@ -180,9 +202,9 @@ class Choice(models.Model): 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') + # 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): @@ -191,7 +213,7 @@ class Choice(models.Model): for vote in Vote.objects.filter(choice=self, value__isnull=False): sum += vote.value if balanced_poll: - return sum/2 + return sum / 2 return sum def changeOrder(self, idx=1): @@ -208,20 +230,21 @@ class Choice(models.Model): if choice == self: continue if idx < 0 and choice.order < self.order \ - and choice.order >= new_order: - choice.order += 1 - choice.save() + 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() + 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'))), + VOTE = ((1, (_('Yes'), _('Yes'))), (0, (_('No'), _('Maybe')), ), (-1, (_('No'), _('No'))),) value = models.IntegerField(choices=VOTE, blank=True, null=True) diff --git a/papillon/polls/views.py b/papillon/polls/views.py index 43ff37f..54920b7 100644 --- a/papillon/polls/views.py +++ b/papillon/polls/views.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2008 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2008-2016 É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 @@ -24,18 +24,19 @@ Views management from random import choice as random_choice import string import time -from datetime import datetime -from django.shortcuts import render_to_response -from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse from django.conf import settings +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.template import RequestContext from django.utils.translation import gettext_lazy as _ -from django.core.urlresolvers import reverse from papillon.polls.models import Poll, PollUser, Choice, Voter, Vote, \ - Category, Comment + Category, Comment from papillon.polls.forms import CreatePollForm, AdminPollForm, ChoiceForm, \ - DatedChoiceForm, CommentForm + DatedChoiceForm, CommentForm + def getBaseResponse(request): """Manage basic fields for the template @@ -44,14 +45,15 @@ def getBaseResponse(request): # setting the current language and available languages if 'language' in request.GET: if request.GET['language'] in \ - [language[0] for language in settings.LANGUAGES]: + [language[0] for language in settings.LANGUAGES]: request.session['django_language'] = request.GET['language'] return None, HttpResponseRedirect(request.path) languages = [] for language_code, language_label in settings.LANGUAGES: languages.append((language_code, language_label)) - return {'media_url':settings.MEDIA_URL, 'languages':languages, - 'admin_url':settings.ADMIN_MEDIA_PREFIX,}, None + return {'languages': languages, + 'admin_url': settings.ADMIN_MEDIA_PREFIX}, None + def index(request): "Main page" @@ -62,10 +64,11 @@ def index(request): if response_dct['public']: 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) + return render_to_response('main.html', response_dct, + context_instance=RequestContext(request)) + def category(request, category_id): "Page for a category" @@ -77,6 +80,7 @@ def category(request, category_id): 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. ''' @@ -84,7 +88,7 @@ def create(request): "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(): + Poll.objects.filter(admin_url=url).count(): url = '' chars = string.letters + string.digits for i in xrange(6): @@ -110,6 +114,7 @@ def create(request): response_dct['form'] = form return render_to_response('create.html', response_dct) + def edit(request, admin_url): '''Edition of a poll. ''' @@ -133,14 +138,15 @@ def edit(request, admin_url): form = Form(instance=poll) response_dct['form'] = form response_dct['poll'] = poll - response_dct['base_url'] = request.build_absolute_uri(reverse('poll', - args=[poll.base_url])) - response_dct['edit_url'] = request.build_absolute_uri(reverse('edit', - args=[poll.admin_url])) - response_dct['choices_url'] = request.build_absolute_uri(reverse( - 'edit_choices_admin', args=[poll.admin_url])) + response_dct['base_url'] = request.build_absolute_uri( + reverse('poll', args=[poll.base_url])) + response_dct['edit_url'] = request.build_absolute_uri( + reverse('edit', args=[poll.admin_url])) + response_dct['choices_url'] = request.build_absolute_uri( + reverse('edit_choices_admin', args=[poll.admin_url])) return render_to_response('edit.html', response_dct) + def editChoicesAdmin(request, admin_url): response_dct, redirect = getBaseResponse(request) if redirect: @@ -153,6 +159,7 @@ def editChoicesAdmin(request, admin_url): response_dct['poll'] = poll return editChoices(request, response_dct, admin=True) + def editChoicesUser(request, poll_url): response_dct, redirect = getBaseResponse(request) if redirect: @@ -167,6 +174,7 @@ def editChoicesUser(request, poll_url): response_dct['poll'] = poll return editChoices(request, response_dct) + def editChoices(request, response_dct, admin=False): '''Edition of choices. ''' @@ -182,7 +190,7 @@ def editChoices(request, response_dct, admin=False): order += 1 except IndexError: order = 0 - form = Form(initial={'poll':poll.id, 'order':str(order)}) + form = Form(initial={'poll': poll.id, 'order': str(order)}) if request.method == 'POST': # if a new choice is submitted @@ -210,7 +218,8 @@ def editChoices(request, response_dct, admin=False): for key in request.POST: if key.startswith('delete_') and request.POST[key]: try: - choice = Choice.objects.get(id=int(key[len('delete_'):])) + choice = Choice.objects.get( + id=int(key[len('delete_'):])) if choice.poll != poll: raise ValueError Vote.objects.filter(choice=choice).delete() @@ -221,10 +230,9 @@ def editChoices(request, response_dct, admin=False): if admin and request.method == 'GET': for key in request.GET: try: - current_url = reverse('edit_choices_admin', - args=[poll.admin_url]) if admin else \ - reverse('edit_choices_user', - args=[poll.poll_url]) + current_url = reverse( + 'edit_choices_admin', args=[poll.admin_url]) if admin else\ + reverse('edit_choices_user', args=[poll.poll_url]) if 'up_choice' in key: choice = Choice.objects.get(id=int(request.GET[key])) if choice.poll != poll: @@ -252,18 +260,18 @@ def editChoices(request, response_dct, admin=False): 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 + 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] + 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 @@ -339,6 +347,7 @@ def poll(request, poll_url): # a new choice v = Vote(voter=voter, choice=choice, value=0) v.save() + def newComment(request, poll): "Comment the poll" if poll.comments.count() >= settings.MAX_COMMENT_NB: @@ -444,8 +453,8 @@ def poll(request, poll_url): except ValueError: pass - response_dct.update({'poll':poll, - 'VOTE':Vote.VOTE,}) + response_dct.update({'poll': poll, + 'VOTE': Vote.VOTE}) response_dct['base_url'] = "/".join(request.path.split('/')[:-2]) \ + '/%s/' % poll.base_url @@ -455,7 +464,7 @@ def poll(request, poll_url): for voter in voters: # highlight a voter if time.mktime(voter.modification_date.timetuple()) \ - == highlight_vote_date: + == highlight_vote_date: voter.highlight = True voter.votes = voter.getVotes(choice_ids) # initialize undefined vote @@ -464,7 +473,8 @@ def poll(request, poll_url): if choice.id not in choice_vote_ids: vote = Vote(voter=voter, choice=choice, value=None) vote.save() - voter.votes.sort(lambda x, y : cmp(choices.index(x.choice), choices.index(y.choice))) + voter.votes.sort(lambda x, y: cmp(choices.index(x.choice), + choices.index(y.choice))) sums = [choice.getSum(poll.type == 'B') for choice in choices] vote_max = max(sums) c_idx = 0 @@ -479,7 +489,7 @@ def poll(request, poll_url): response_dct['limit_set'] = None for choice in choices: if choice.limit: - response_dct['limit_set'] = True + response_dct['limit_set'] = True if choice.limit and sums[choices.index(choice)] >= choice.limit: choice.available = False else: |
