diff options
Diffstat (limited to 'papillon')
27 files changed, 2327 insertions, 0 deletions
diff --git a/papillon/__init__.py b/papillon/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/papillon/__init__.py diff --git a/papillon/locale/fr/LC_MESSAGES/django.po b/papillon/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..4d09e84 --- /dev/null +++ b/papillon/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,384 @@ +# Papillon +# Copyright (C) 2008 +# This file is distributed under the same license as the papillon package. +# Étienne Loks <etienne.loks@peacefrogs.net>, 2008. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-12-04 23:49+0100\n" +"PO-Revision-Date: 2008-08-20 00:22+0200\n" +"Last-Translator: Étienne Loks <etienne.loks@peacefrogs.net>,\n" +"Language-Team: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: polls/feeds.py:37 +msgid "Papillon - poll : " +msgstr "Papillon - sondage : " + +#: polls/forms.py:99 +msgid "Invalid poll" +msgstr "Sondage non valide" + +#: polls/forms.py:114 +msgid "Invalid date format: YYYY-MM-DD HH:MM:SS" +msgstr "Format de date invalide AAAA-MM-JJ HH:MM:SS" + +#: polls/models.py:44 templates/edit.html:22 +msgid "" +"Copy this address and send it to voters who want to participate to this poll" +msgstr "Copiez cette adresse et envoyez là aux participants à ce sondage." + +#: polls/models.py:46 templates/edit.html:31 +msgid "Address to modify the current poll" +msgstr "Adresse de modification de ce sondage" + +#: polls/models.py:48 templates/vote.html:133 +msgid "Author name" +msgstr "Nom de l'auteur" + +#: polls/models.py:49 +msgid "Name, firstname or nickname of the author" +msgstr "Nom, prénom ou surnom de l'auteur" + +#: polls/models.py:51 +msgid "Poll name" +msgstr "Nom du sondage" + +#: polls/models.py:52 +msgid "Global name to present the poll" +msgstr "Nom général pour présenter le sondage" + +#: polls/models.py:54 +msgid "Poll description" +msgstr "Description du sondage" + +#: polls/models.py:55 +msgid "Precise description of the poll" +msgstr "Description précise du sondage" + +#: polls/models.py:57 +msgid "Yes/No poll" +msgstr "Oui/Non" + +#: polls/models.py:58 +msgid "Yes/No/Maybe poll" +msgstr "Oui/Non/Peut-être" + +#: polls/models.py:59 +msgid "One choice poll" +msgstr "Sondage à choix unique" + +#: polls/models.py:60 +msgid "Valuable choice poll" +msgstr "Sondage pondéré" + +#: polls/models.py:62 +msgid "Type of the poll" +msgstr "Type du sondage" + +#: polls/models.py:63 +msgid "" +"Type of the poll:\n" +"\n" +" - \"Yes/No poll\" is the appropriate type for a simple multi-choice poll\n" +" - \"Yes/No/Maybe poll\" allows voters to stay undecided\n" +" - \"One choice poll\" gives only one option to choose from\n" +" - \"Valuable choice poll\" permit users to give a note between 0 to 9 to " +"different choices\n" +msgstr "" +"Type du sondage :\n" +"\n" +" - \"Oui/Non\" est un sondage simple permettant de choisir entre plusieurs " +"options\n" +" - \"Oui/Non/Peut-être\" permet de laisser une option d'indécision aux " +"votants\n" +" - \"Sondage à choix unique\" ne permet que de choisir un choix parmi ceux " +"proposés\n" +" - \"Sondage pondéré\" permet aux utilisateurs de donner une note entre 0 et " +"9 pour chaque choix\n" + +#: polls/models.py:71 +msgid "Choices are dates" +msgstr "Les choix sont des dates" + +#: polls/models.py:72 +msgid "Check this option to choose between dates" +msgstr "Cocher cette option pour choisir entre des dates" + +#: polls/models.py:74 +msgid "Closing date" +msgstr "Date de fermeture" + +#: polls/models.py:74 +msgid "Closing date for participating to the poll" +msgstr "Date de fermeture au vote du sondage" + +#: polls/models.py:78 +msgid "Display the poll on main page" +msgstr "Afficher le sondage sur la page principale" + +#: polls/models.py:78 +msgid "Check this option to make the poll public" +msgstr "Cocher cette option pour que le sondage soit publique" + +#: polls/models.py:81 +msgid "Allow users to add choices" +msgstr "Permettre aux votants d'ajouter des choix" + +#: polls/models.py:81 +msgid "Check this option to open the poll to new choices submitted by users" +msgstr "" +"Cocher cette option pour permettre aux utilisateurs d'enrichir le sondage " +"avec des nouveaux choix" + +#: polls/models.py:84 +msgid "Hide votes to new voters" +msgstr "Cacher les résultats aux nouveaux votants" + +#: polls/models.py:84 +msgid "Check this option to hide poll results to new users" +msgstr "" +"Cocher cette option pour cacher, dans un premier temps, les résultats d'un " +"sondage" + +#: polls/models.py:87 +msgid "State of the poll" +msgstr "État du sondage" + +#: polls/models.py:87 +msgid "Uncheck this option to close the poll/check this option to reopen it" +msgstr "" +"Décocher cette option pour fermet le sondage aux votes/cocher cette option " +"pour l'ouvrir de nouveau" + +#: polls/models.py:156 +#, python-format +msgid "Vote from %(user)s" +msgstr "Vote de %(user)s" + +#: polls/models.py:224 +msgid "Yes" +msgstr "Oui" + +#: polls/models.py:225 polls/models.py:226 +msgid "No" +msgstr "Non" + +#: polls/models.py:225 +msgid "Maybe" +msgstr "Peut-être" + +#: polls/views.py:65 +msgid "The poll requested don't exist (anymore?)" +msgstr "Le sondage que vous avez demandé n'existe pas (n'existe plus ?)" + +#: templates/category.html:8 +msgid "Polls" +msgstr "Sondage" + +#: templates/create.html:11 +msgid "New poll" +msgstr "Nouveau sondage" + +#: templates/create.html:28 +msgid "Create" +msgstr "Créer" + +#: templates/edit.html:13 +msgid "Edit poll" +msgstr "Éditer un sondage" + +#: templates/edit.html:17 +msgid "Poll url" +msgstr "Adresse du sondage" + +#: templates/edit.html:26 +msgid "Administration url" +msgstr "Adresse d'administration" + +#: templates/edit.html:35 +msgid "Choices administration url" +msgstr "Adresse d'administration des choix" + +#: templates/edit.html:40 +msgid "Address to modify choices of the current poll." +msgstr "Adresse de modification des choix disponibles pour ce sondage" + +#: templates/edit.html:57 templates/editChoicesAdmin.html:38 +#: templates/vote.html:59 templates/vote.html.py:115 +msgid "Edit" +msgstr "Modifier" + +#: templates/editChoices.html:4 +msgid "New choice" +msgstr "Nouveau choix" + +#: templates/editChoices.html:11 +msgid "" +"Setting a new choice. Optionally you can set a limit of vote for this " +"choice. This limit is usefull for limited resources allocation." +msgstr "" +"Ajouter un nouveau choix. Optionnellement vous pouvez ajouter une limite de " +"vote pour ce choix. Cette limite est utile dans le cas d'attribution de " +"ressources limitées." + +#: templates/editChoices.html:15 templates/editChoicesAdmin.html:35 +#: templates/editChoicesUser.html:22 +msgid "Limited to" +msgstr "Limité à" + +#: templates/editChoices.html:15 templates/editChoicesAdmin.html:35 +#: templates/editChoicesUser.html:22 +msgid "vote(s)" +msgstr "vote(s)" + +#: templates/editChoices.html:16 +msgid "Add" +msgstr "Ajouter" + +#: templates/editChoicesAdmin.html:14 +msgid "As long as no options were added to the poll, it will not be available." +msgstr "" +"Tant qu'aucune option ne sera ajouté au sondage, il ne sera pas disponible." + +#: templates/editChoicesAdmin.html:16 +msgid "Complete/Finalise the poll" +msgstr "Complète/Finalise le sondage" + +#: templates/editChoicesAdmin.html:17 +msgid "Next" +msgstr "Suivant" + +#: templates/editChoicesAdmin.html:21 +msgid "Available choices" +msgstr "Choix disponibles" + +#: templates/editChoicesAdmin.html:24 +msgid "Up/down" +msgstr "Haut/bas" + +#: templates/editChoicesAdmin.html:25 templates/editChoicesUser.html:17 +msgid "Label" +msgstr "Libellé" + +#: templates/editChoicesAdmin.html:26 templates/editChoicesUser.html:18 +msgid "Limit" +msgstr "Limite" + +#: templates/editChoicesAdmin.html:27 +msgid "Delete?" +msgstr "Supprimer ?" + +#: templates/editChoicesUser.html:13 +msgid "Return to the poll" +msgstr "Retourner au sondage" + +#: templates/editChoicesUser.html:14 +msgid "Choices" +msgstr "Choix" + +#: templates/editChoicesUser.html:21 templates/vote.html:23 +#: templates/vote.html.py:144 +msgid "DATETIME_FORMAT" +msgstr "" + +#: templates/main.html:6 +msgid "Create a poll" +msgstr "Créer un sondage" + +#: templates/main.html:7 +msgid "" +"Create a new sondage for take a decision, find a date for a meeting, etc." +msgstr "" +"Créer un nouveau sondage pour prendre une décision, trouver une date pour " +"une réunion, etc." + +#: templates/main.html:7 +msgid "It's here!" +msgstr "C'est ici !" + +#: templates/main.html:9 +msgid "Public polls" +msgstr "Sondages publics" + +#: templates/main.html:17 +msgid "Categories" +msgstr "Catégories" + +#: templates/vote.html:15 +msgid "The current poll is closed." +msgstr "Le sondage actuel est fermé" + +#: templates/vote.html:23 +msgid "max" +msgstr "max" + +#: templates/vote.html:54 templates/vote.html.py:103 +msgid "Limit reached" +msgstr "Limite atteinte" + +#: templates/vote.html:109 +msgid "Sum" +msgstr "Somme" + +#: templates/vote.html:115 +msgid "Participate" +msgstr "Participer" + +#: templates/vote.html:122 +msgid "Add a new choice to this poll?" +msgstr "Ajouter un nouveau choix à ce sondage ?" + +#: templates/vote.html:124 +msgid "" +"You have already vote? You are enough wise not to be influenced by other " +"votes? You can display result by clicking" +msgstr "" +"Vous avez déjà voté ? Vous pensez être suffisament sage pour ne pas être " +"influencé par les autres votes ? Vous pouvez afficher le résultat en cliquant" + +#: templates/vote.html:124 +msgid "here" +msgstr "ici" + +#: templates/vote.html:125 +msgid "Remain informed of poll evolution:" +msgstr "Restez informé de l'évolution du sondage" + +#: templates/vote.html:125 +msgid "syndication" +msgstr "syndication" + +#: templates/vote.html:128 +msgid "Comments" +msgstr "Commentaires" + +#: templates/vote.html:137 +msgid "Comment" +msgstr "Commentaire" + +#: templates/vote.html:140 +msgid "Send" +msgstr "Envoyer" + +#: templates/feeds/poll_description.html:2 +#, python-format +msgid "%(voter_name)s has added/modified a vote." +msgstr "%(voter_name)s a ajouté ou modifié un vote." + +#: templates/feeds/poll_description.html:3 +msgid "Current results:" +msgstr "Résultats actuels :" + +#: templates/feeds/poll_description.html:6 +#, python-format +msgid ": %(sum)s vote" +msgid_plural ": %(sum)s votes" +msgstr[0] " : %(sum)s vote" +msgstr[1] " : %(sum)s votes" + diff --git a/papillon/manage.py b/papillon/manage.py new file mode 100755 index 0000000..bcdd55e --- /dev/null +++ b/papillon/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/papillon/poll_cleaning.py b/papillon/poll_cleaning.py new file mode 100755 index 0000000..993a5b4 --- /dev/null +++ b/papillon/poll_cleaning.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Clean the old polls +''' + +import os +import sys + +# django settings path +os.environ['DJANGO_SETTINGS_MODULE'] = 'papillon.settings' + +# add the parent path to sys.path +curdir = os.path.abspath(os.curdir) +sep = os.path.sep +sys.path.append(sep.join(curdir.split(sep)[:-1])) + + +from papillon.polls.models import Poll + +for poll in Poll.objects.all(): + poll.checkForErasement() 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) diff --git a/papillon/settings.py b/papillon/settings.py new file mode 100644 index 0000000..85ae8c8 --- /dev/null +++ b/papillon/settings.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Django settings for papillon project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ROOT_PATH = '/var/local/django/papillon/' +SERVER_URL = 'http://localhost:8000/' +EXTRA_URL = 'papillon/' +BASE_SITE = SERVER_URL + EXTRA_URL + +TINYMCE_URL = 'http://localhost/tinymce/' +# time to live in days +DAYS_TO_LIVE = 30 + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. +DATABASE_NAME = ROOT_PATH + 'papillon.db' # Or path to database file if using sqlite3. +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +# although not all variations may be possible on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'Europe/Paris' + +# Language code for this installation. All choices can be found here: +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +# http://blogs.law.harvard.edu/tech/stories/storyReader$15 +LANGUAGE_CODE = 'fr-fr' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = ROOT_PATH + 'static/' + +# URL that handles the media served from MEDIA_ROOT. +# Example: "http://media.lawrence.com" +MEDIA_URL = BASE_SITE + 'static/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = BASE_SITE + 'media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'replace_this_with_something_else' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.doc.XViewMiddleware', +) + +ROOT_URLCONF = 'papillon.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + ROOT_PATH + 'templates', +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.admin', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.markup', + 'papillon.polls', +) + +LANGUAGES = ( + ('fr', 'Français'), + ('en', 'English'), +) diff --git a/papillon/static/bg.jpg b/papillon/static/bg.jpg Binary files differnew file mode 100644 index 0000000..7653c63 --- /dev/null +++ b/papillon/static/bg.jpg diff --git a/papillon/static/styles.css b/papillon/static/styles.css new file mode 100644 index 0000000..c329905 --- /dev/null +++ b/papillon/static/styles.css @@ -0,0 +1,357 @@ +/* +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. +*/ + +body{ +margin:0; +font-size:12px; +font-family: Arial, Verdana, Geneva, "Bitstream Vera Sans", Helvetica, sans-serif; +background-color:#ced3e1; +} + +pre{ +font-size:12px; +font-family: Arial, Verdana, Geneva, "Bitstream Vera Sans", Helvetica, sans-serif; +} + +a{ +color:#6f819d; +} + +h1, h2, h3, h4, h5 { +font-family: Georgia, "Bitstream Vera Serif", "New York", Palatino, serif; +font-weight:normal; +} + +h1, h1 a{ +margin:0; +margin-top:2px; +border:1px solid black; +border-right:None; +border-left:None; +background-color:#6f819d; +color:white; +padding-left:10px; +font-size:32px; +text-decoration:None; +} + +h2{ +font-style:italic; +border-bottom:1px dashed black; +margin-left:10px; +padding:0; +font-size:24px; +} + +h2 a{ +color:black; +text-decoration:None; +} + +h2 a:hover{ +color:#808080; +} + +h3{ +margin:10px; +font-size:20px; +} + +h3 a{ +color:black; +text-decoration:None; +} + +h3 a:hover{ +color:#808080; +} + +p{ +padding:6px; +margin:6px; +max-width:600px; +} + +td{ +font-size:12px; +padding:4px; +} + +th{ +font-size:12px; +text-align:left; +} + +label{ +font-family: Georgia, "Bitstream Vera Serif", "New York", Palatino, serif; +font-weight:normal; +font-style:italic; +} + +hr.spacer{ +clear:both; +height:0; +border:0; +} + +#main{ +background-color:white; +border:1px solid; +margin:20px; +background-image: url(bg.jpg); +background-repeat:no-repeat; +background-position:top right; +} + +#header{ +text-align:right; +font-size:11px; +color:#808080; +} + +#header #languages a{ +color:#808080; +padding-right:6px; +text-decoration:none; +} + +#footer{ +text-align:center; +font-size:11px; +color:#808080; +margin:6px; +} + +#footer a{ +color:#808080; +text-decoration:none; +} + +.alert, .error, .errorlist{ +color:blue; +} + +.new_poll{ +width:600px; +} + +.new_poll input{ +width:160px; +} + +.new_poll .submit{ +width:auto; +} + +.new_poll input.limit{ +width:20px; +} + +.new_poll textarea{ +width:160px; +height:100px; +} + +.datetime a img{ +border:0; +} + +.datetime input.vTimeField{ +width:68px; +} + +.datetime input.vDateField{ +width:75px; +} + +.form_description{ +background-color:#6f819d; +color:white; +font-size:11px; +width:200px; +} + +a.arrow{ +text-decoration:None; +font-weight:bold; +} + +#content{ +margin:5px; +} + +#poll_table{ +overflow:auto; +overflow-y:visible; +text-align:center; +width:100%; +padding-bottom:16px; +display:block; +float:left; +} + +#poll{ +text-align:center; +} + +#poll a{ +color:black; +} + +#poll td{ +border:1px solid black; +padding:0; +} + +#poll td.simple{ +border:None; +background-color:#FFF; +} + +#poll th{ +background-color:#ced3e1; +border:1px solid black; +padding:5px; +} + +#poll input{ +width:100px; +} + +#poll .OK{ +background-color:#9ec5d5; +} + +#poll .OKO{ +background-color:#b689d5; +} + +#poll .KO{ +background-color:#b9b3bd; +} + +#sum th{ +background-color:white; +border:None; +text-align:center; +} + +#sum td{ +border:None; +} + +.highlight{ +font-weight:bold; +background-color:#ced3e1; +} + +tr.highlighted_voter td{ +background-color:#808080; +color:white; +} + +.footnote{ +font-size:10px; +padding:10px; +} + +.footnote p{ +padding:0; +margin:2px; +} + +.poll-description{ +margin:4px; +padding:4px; +border:1px solid #d3d3d3; +} + +.poll-description p{ +margin:0; +padding:2px; +} + + +.comments ul{ +list-style-type:None; +margin:4px; +padding:0; +} + +.comments li{ +margin:4px; +padding:4px; +border:1px solid #d3d3d3; +} + +.comments .author{ +margin:0; +color:#6f819d; +padding:0; +} + +.comments input{ +width:160px; +} + +.comments textarea{ +width:160px; +height:100px; +} + +.comments #tdsubmit{ +text-align:center; +} + +.comments .submit{ +width:auto; +} + + +/* CALENDARS & CLOCKS IMPORTED FROM ADMIN */ + +.calendarbox, .clockbox { margin: 5px auto; font-size: 11px; width: 16em; text-align: center; background: white; position: relative; border: 1px solid #444; } +.clockbox { width: auto; } +.clockbox h2 { margin: 0; font-size: 13px; border-bottom: 1px solid #222; padding: 3px; background-color: #EEE; } + +.calendar { margin: 0; padding: 0; } +.calendar table { margin: 0; padding: 0; border-collapse: collapse; background: white; width: 100%; } +.calendar caption, .calendarbox h2 { margin: 0; font-size: 12px; text-align: center; border-top: none; background-color: #EEE; } +.calendar caption { height: 18px; font-weight: bold; } +.calendar th { font-size: 10px; color: #666; padding: 2px 3px; text-align: center; background: #e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-bottom: 1px solid #ddd; } +.calendar td { font-size: 11px; text-align: center; padding: 0; border-top: 1px solid #eee; border-bottom: none; } +.calendar td.selected a { background: #C9DBED; } +.calendar td.nonday { background: #efefef; } +.calendar td.today a { background: #ffc; } +.calendar td a, .timelist a { display: block; font-weight: bold; padding: 4px; text-decoration: none; color: #444; } +.calendar td a:hover, .timelist a:hover { background: #4A0010; color: white; } +.calendar td a:active, .timelist a:active { background: #CCC; color: white; } +.calendarnav { font-size: 10px; text-align: center; color: #ccc; margin: 0; padding: 1px 3px; } +.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { color: #999; } +.calendar-shortcuts { background: white; font-size: 10px; line-height: 11px; border-top: 1px solid #eee; padding: 3px 0 4px; color: #ccc; } +.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { display: block; position: absolute; font-weight: bold; font-size: 12px; background: #AAA url(../img/admin/default-bg.gif) bottom left repeat-x; padding: 1px 4px 2px 4px; color: white; } +.calendarnav-previous:hover, .calendarnav-next:hover { background: #4A0010; } +.calendarnav-previous { top: 0; left: 0; } +.calendarnav-next { top: 0; right: 0; } +.calendar-cancel { margin: 0 !important; padding: 0; font-size: 10px; background: #e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; border-top: 1px solid #ddd; } +.calendar-cancel a { padding: 2px; color: #999; } + +ul.timelist, .timelist li { list-style-type: none; margin: 0; padding: 0; } +.timelist a { padding: 2px; } + diff --git a/papillon/static/textareas.js b/papillon/static/textareas.js new file mode 100644 index 0000000..fec83b8 --- /dev/null +++ b/papillon/static/textareas.js @@ -0,0 +1,27 @@ +/* base function shared by some pages */ +/* 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. +*/ + +tinyMCE.init({ + mode : "textareas", + theme : "advanced", + relative_urls : false, + theme_advanced_buttons1 : "bold,italic,underline,strikethrough,separator,bullist,numlist,separator,hr,separator,link", + theme_advanced_buttons2 : "", + theme_advanced_buttons3 : "" +}); diff --git a/papillon/templates/base.html b/papillon/templates/base.html new file mode 100644 index 0000000..b673f24 --- /dev/null +++ b/papillon/templates/base.html @@ -0,0 +1,27 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <link rel="stylesheet" href="{{root_url}}static/styles.css" /> + <title>{% block title %}Papillon{% endblock %}</title> + {% block fullscript %}{% endblock %} +</head> + +<body> +<div id="main"> +<div id="header"> +<span id='languages'>{% for language in languages%}<a href='?language={{language.0}}'>{{language.1}}</a>{% endfor %}</span> +</div> +<div id="top"> + <h1><a href='{{root_url}}'>Papillon</a></h1> +</div> +<div id="content"> +{% block content %}{% endblock %} +</div> +<div id="footer"> +<a href='http://blog.peacefrogs.net/nim/papillon/'>Papillon</a> - <a href='http://www.gnu.org/licenses/gpl.html'>Copyright</a> © 2008 <a href='http://redmine.peacefrogs.net/projects/show/papillon'>Papillon project</a> +</div> +</div> +</body> +</html> + diff --git a/papillon/templates/category.html b/papillon/templates/category.html new file mode 100644 index 0000000..55aa084 --- /dev/null +++ b/papillon/templates/category.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +<h2>{{category.name}}</h2> +<p>{{category.description}}</p> + +{% if polls %}<h2>{%trans "Polls"%}</h2>{%endif%} +{% for poll in polls %} +<div class='poll-description'> + <p><a href='{{root_url}}poll/{{poll.base_url}}'>{{poll.name}}</a></p> + <p>{{poll.description|safe}}</p> +</div> +{% endfor %} + +{% endblock %} diff --git a/papillon/templates/create.html b/papillon/templates/create.html new file mode 100644 index 0000000..7c3d2b3 --- /dev/null +++ b/papillon/templates/create.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% load i18n %} +{% load markup %} + +{% block fullscript %} + {{ form.media }} +{% endblock %} + + +{% block content %} + <h2>{% trans "New poll" %}</h2> +<form action="" method="post"> +<table class='new_poll'> + {% for field in form %} + {% if field.is_hidden %} + {{field}} + {% else %} + <tr><td colspan='3'>{{field.errors}}</td></tr> + <tr> + <td>{{field.label_tag}}</td> + <td>{{field}}</td> + <td class='form_description'>{{field.help_text|markdown}}</td> + </tr> + {% endif %} + {% endfor %} + <tr> + <td></td> + <td><input type='submit' value='{% trans "Create" %}' class='submit'/></td> + </tr> +</table> +</form> + +{% endblock %} diff --git a/papillon/templates/edit.html b/papillon/templates/edit.html new file mode 100644 index 0000000..7ef4f14 --- /dev/null +++ b/papillon/templates/edit.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} +{% load markup %} +{% load i18n %} + +{% block fullscript %} +<script type="text/javascript" src="{{root_url}}admin/jsi18n/"></script> +<script type="text/javascript" src="{{root_url}}media/js/core.js"></script> +<script type="text/javascript" src="{{root_url}}media/js/admin/RelatedObjectLookups.js"></script> +{{ form.media }} +{% endblock %} + +{% block content %} + <h2>{% trans "Edit poll" %}</h2> +<form action="" method="post"> +<table class='new_poll'> + <tr> + <td><label>{% trans "Poll url" %}</label></td> + <td> +<a href='{{root_url}}poll/{{poll.base_url}}'>{{root_url}}poll/{{poll.base_url}}</a> + </td> + <td class='form_description'><p> +{% trans "Copy this address and send it to voters who want to participate to this poll" %} + </p></td> + </tr> + <tr> + <td><label>{% trans "Administration url" %}</label></td> + <td> +<a href='{{root_url}}edit/{{poll.admin_url}}'>{{root_url}}edit/{{poll.admin_url}}</a> + </td> + <td class='form_description'><p> + {% trans "Address to modify the current poll" %} + </p></td> + </tr> + <tr> + <td><label>{% trans "Choices administration url" %}</label></td> + <td> +<a href='{{root_url}}editChoicesAdmin/{{poll.admin_url}}'>{{root_url}}editChoicesAdmin/{{poll.admin_url}}</a> + </td> + <td class='form_description'><p> + {% trans "Address to modify choices of the current poll." %} + </p></td> + </tr> + {% for field in form %} + {% if field.is_hidden %} + {{field}} + {% else %} + <tr><td colspan='3'>{{field.errors}}</td></tr> + <tr> + <td>{{field.label_tag}}</td> + <td>{{field}}</td> + {% if field.help_text %}<td class='form_description'>{{field.help_text|markdown}}</td>{%endif%} + </tr> + {% endif %} + {% endfor %} + <tr> + <td></td> + <td><input type='submit' value='{% trans "Edit" %}' class='submit'/></td> + </tr> +</table> +</form> + +{% endblock %} diff --git a/papillon/templates/editChoices.html b/papillon/templates/editChoices.html new file mode 100644 index 0000000..1082d30 --- /dev/null +++ b/papillon/templates/editChoices.html @@ -0,0 +1,19 @@ +{% load markup %} +{% load i18n %} + +<h2>{% trans "New choice" %}</h2> +{%if form_new_choice.errors %} <p class='error'>{{form_new_choice.errors}}</p>{%endif%} +<form action="{{admin_url}}" method="post"> +{{form_new_choice.poll}} +{{form_new_choice.order}} +<table class='new_poll'> + <tr> + <td class='form_description' colspan='3'><p>{% trans "Setting a new choice. Optionally you can set a limit of vote for this choice. This limit is usefull for limited resources allocation." %}</p></td> + </tr> + <tr> + <td>{{form_new_choice.name}}</td> + <td>{%trans "Limited to"%} {{form_new_choice.limit}} {%trans "vote(s)"%}</td> + <td><input type='hidden' name='add' value='1'/> <input type='submit' value='{% trans "Add" %}' class='submit'/></td> + </tr> +</table> +</form> diff --git a/papillon/templates/editChoicesAdmin.html b/papillon/templates/editChoicesAdmin.html new file mode 100644 index 0000000..a7798bf --- /dev/null +++ b/papillon/templates/editChoicesAdmin.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} +{% load markup %} +{% load i18n %} + +{% block fullscript %} +<script type="text/javascript" src="{{root_url}}admin/jsi18n/"></script> +<script type="text/javascript" src="{{root_url}}media/js/core.js"></script> +<script type="text/javascript" src="{{root_url}}media/js/admin/RelatedObjectLookups.js"></script> +{{ form_new_choice.media }} +{% endblock %} + +{% block content %} +{% if not choices %}<p class='error'> +{% blocktrans %}As long as no options were added to the poll, it will not be available.{% endblocktrans %} +</p>{% else %} +<h2>{% trans "Complete/Finalise the poll" %}</h2> +<p><a href='{{root_url}}edit/{{poll.admin_url}}'><button>{% trans "Next"%}</button></p> +{% endif %} +{% include 'editChoices.html' %} +{% if choices %} +<h2>{% trans "Available choices" %}</h2> +<table class='new_poll'> + <tr> + {%if not poll.dated_choices%}<th>{% trans "Up/down" %}</th>{%endif%} + <th>{% trans "Label" %}</th> + <th>{% trans "Limit" %}</th> + <th>{% trans "Delete?"%}</th> + </tr> + {% for choice in choices %} + <form action="" method="post"> + {{choice.form.poll}}{{choice.form.order}}<tr> + {%if not poll.dated_choices%}<td><a href='?up_choice={{choice.id}}' class='arrow'>+</a> + / <a href='?down_choice={{choice.id}}' class='arrow'>-</a></td>{%endif%} + <td>{{choice.form.name}}</td> + <td>{% trans "Limited to"%} {{choice.form.limit}} {% trans "vote(s)" %}</td> + <td><input type='checkbox' name='delete_{{choice.id}}'/></td> + <td><input type='hidden' name='edit' value='{{choice.id}}'/></td> + <td><input type='submit' value='{% trans "Edit" %}' class='submit'/></td> + </tr> + </form>{% endfor %} +</table> +{% endif %} + +{% endblock %} diff --git a/papillon/templates/editChoicesUser.html b/papillon/templates/editChoicesUser.html new file mode 100644 index 0000000..0d2b2c1 --- /dev/null +++ b/papillon/templates/editChoicesUser.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} +{% load markup %} +{% load i18n %} + +{% block fullscript %} +<script type="text/javascript" src="{{root_url}}admin/jsi18n/"></script> +<script type="text/javascript" src="{{root_url}}media/js/core.js"></script> +<script type="text/javascript" src="{{root_url}}media/js/admin/RelatedObjectLookups.js"></script> +{{ form_new_choice.media }} +{% endblock %} + +{% block content %} + <p><a href="{{root_url}}poll/{{poll.base_url}}/">{%trans "Return to the poll"%}</a></p> +<h2>{% trans "Choices" %}</h2> +{% if choices %}<table class='new_poll'> + <tr> + <th>{% trans "Label" %}</th> + <th>{% trans "Limit" %}</th> + </tr> + {% for choice in choices %}<tr> + <td>{%if poll.dated_choices%}{{choice.date|date:_("DATETIME_FORMAT")}}{%else%}{{choice.name}}{%endif%}</td> + <td>{% if choice.limit %}{% trans "Limited to"%} {{choice.limit}} {% trans "vote(s)" %}{% endif %}</td> + </tr>{% endfor %} +</table> +{% endif %} +{% include 'editChoices.html' %} +{% endblock %} diff --git a/papillon/templates/feeds/poll_description.html b/papillon/templates/feeds/poll_description.html new file mode 100644 index 0000000..7522a5a --- /dev/null +++ b/papillon/templates/feeds/poll_description.html @@ -0,0 +1,8 @@ +{% load i18n %} +<p>{% blocktrans with obj.user.name as voter_name %}{{ voter_name }} has added/modified a vote.{%endblocktrans%}</p> +<p>{% trans "Current results:" %}</p> +<ul> +{% for choice in obj.poll.getChoices %} + <li><strong>{{choice.name}}</strong>{% blocktrans count choice.getSum as sum %}: {{sum}} vote{%plural%}: {{sum}} votes{%endblocktrans%}</li> +{% endfor %} +</ul>
\ No newline at end of file diff --git a/papillon/templates/main.html b/papillon/templates/main.html new file mode 100644 index 0000000..8a09830 --- /dev/null +++ b/papillon/templates/main.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} +{% if error %}<p class='error'>{{error}}</p>{%endif%} +<h2><a href='create'>{%trans "Create a poll"%}</a></h2> +<p>{% trans "Create a new sondage for take a decision, find a date for a meeting, etc." %} <a href='create'>{% trans "It's here!" %}</a></p> + +{% if polls %}<h2>{%trans "Public polls"%}</h2>{%endif%} +{% for poll in polls %} +<div class='poll-description'> + <p><a href='poll/{{poll.base_url}}'>{{poll.name}}</a></p> + <p>{{poll.description}}</p> +</div> +{% endfor %} + +{% if categories %}<h2>{%trans "Categories"%}</h2>{% endif %} +{% for category in categories %} +<h3><a href='category/{{category.id}}'>{{category.name}}</a></h3> +{% endfor %} + +{% endblock %} diff --git a/papillon/templates/vote.html b/papillon/templates/vote.html new file mode 100644 index 0000000..eb1ae21 --- /dev/null +++ b/papillon/templates/vote.html @@ -0,0 +1,148 @@ +{% extends "base.html" %} +{% load i18n %} +{% load get_range %} + +{% block fullscript %} +<script type="text/javascript" src="{{root_url}}admin/jsi18n/"></script> +<script type="text/javascript" src="{{root_url}}media/js/core.js"></script> +<script type="text/javascript" src="{{root_url}}media/js/admin/RelatedObjectLookups.js"></script> +{{ form_comment.media }} +{% endblock %} + +{% block content %} + <h2>{%if poll.category %}{{poll.category.name}} - {%endif%}{{poll.name}}</h2> +{% if error %}<p class='alert'>{{ error }}</p>{% endif %} +{% if not poll.open %}<p class='alert'>{% trans "The current poll is closed."%}</p>{% endif %} + <p>{{ poll.description|safe }}</p> + <form method='post' action='{{base_url}}'> + <div id='poll_table'> + <table id='poll'> + <tr> + <td class='simple'></td> + <td class='simple'></td> + {% for choice in choices %}<th>{%if poll.dated_choices%}{{choice.date|date:_("DATETIME_FORMAT")}}{%else%}{{choice.name}}{%endif%}{% if choice.limit %} ({% trans "max" %} {{choice.limit}}){%endif%}</th> + {% endfor %}</tr> + {% if not hide_vote %} + {% for voter in voters %}<tr{% if voter.highlight %} class='highlighted_voter'{% endif %}> +{% ifequal current_voter_id voter.id %} + <input type='hidden' name='voter' value='{{voter.id}}'/> + <td class='simple'></td> + <td><input type='text' name='author_name' value='{{voter.user.name}}'/></td> + {% for vote in voter.votes %}<td> + {% if vote.choice.available or vote.value %} + {% ifequal poll.type 'P' %} + <input type='checkbox' name='vote_{{vote.id}}'{%ifequal vote.value 1%} checked='checked'{%endifequal%}/> + {% endifequal %} + {% ifequal poll.type 'O' %} + <input type='radio' name='vote' value='{{vote.id}}' {%ifequal vote.value 1%} checked='checked'{%endifequal%}/> + {% endifequal %} + {% ifequal poll.type 'B' %} + <select name='vote_{{vote.id}}'> + {% for vote_choice in VOTE %} + <option value='{{vote_choice.0}}'{%ifequal vote.value vote_choice.0%} selected='selected'{%endifequal%}>{{vote_choice.1.1}}</option> + {% endfor %} + </select> + {% endifequal %} + {% ifequal poll.type 'V' %} + <select name='vote_{{vote.id}}'> + {% for vote_choice in 10|get_range %} + <option value='{{vote_choice}}'{%ifequal vote.value vote_choice%} selected='selected'{%endifequal%}>{{vote_choice}}</option> + {% endfor %} + </select> + {% endifequal %} + {% else %} + {% trans "Limit reached" %} + {% endif %} + </td> + {%endfor%} +{%else%} + <td class='simple'>{% if poll.open %}<a href='?voter={{voter.id}}'>{% trans "Edit" %}</a>{%else%} {%endif%}</td> + <td>{{voter.user.name}}</td> + {% for vote in voter.votes %} + {% ifequal poll.type 'V' %} + <td class='{%ifequal vote.value 9%}OK{%else%}{%ifequal vote.value 0%}KO{%else%}OKO{%endifequal%}{%endifequal%}'> + {%if vote.value%}{{vote.value}}{%else%}0{%endif%}</td> + {% else %} + <td class='{%ifequal vote.value 1%}OK{%else%}{%ifequal vote.value 0%}OKO{%else%}KO{%endifequal%}{%endifequal%}'> + {%ifequal poll.type 'B'%} + {%for VOT in VOTE%} + {%ifequal VOT.0 vote.value%}{{VOT.1.1}}{%endifequal%}{%endfor%} + {%else%} + {%for VOT in VOTE%} + {%ifequal VOT.0 vote.value%}{{VOT.1.0}}{%endifequal%}{%endfor%} + {%endifequal%} + </td> + {% endifequal %} + {%endfor%} + {%endifequal%} + </tr>{%endfor%} + {%endif%} + {%if not current_voter_id%}{% if poll.open %} + <tr> + <td class='simple'></td> + <td><input type='text' name='author_name'/></td> + {%for choice in choices%}<td> + {% if choice.available %} + {% ifequal poll.type 'P' %} + <input type='checkbox' name='choice_{{choice.id}}'/>{% endifequal %} + {% ifequal poll.type 'O' %} + <input type='radio' name='choice' value='{{choice.id}}'/>{% endifequal %} + {% ifequal poll.type 'B' %} + <select name='choice_{{choice.id}}'>{% for vote_choice in VOTE %} + <option value='{{vote_choice.0}}'{%ifequal vote_choice.0 0%} selected='selected'{%endifequal%}>{{vote_choice.1.1}}</option>{% endfor %} + </select> + {% endifequal %} + {% ifequal poll.type 'V' %} + <select name='choice_{{choice.id}}'> + {% for vote_choice in 10|get_range %} + <option value='{{vote_choice}}'>{{vote_choice}}</option> + {% endfor %} + </select> + {% endifequal %} + {% else %} + {% trans "Limit reached" %} + {% endif %} + </td>{%endfor%} + </tr> + {%endif%}{%endif%} + {% if not hide_vote %}<tr id='sum'> + <td class='simple'></td><th>{% trans "Sum" %}</th> + {% for choice in choices %}<td{%if choice.highlight %} class='highlight'{%endif%}>{{choice.getSum}}</td> + {% endfor %} + </tr>{%endif%} + {% if poll.open %} + <td class='simple'></td> + <td class='simple'><input type='submit' value='{%if current_voter_id%}{% trans "Edit" %}{%else%}{% trans "Participate" %}{%endif%}' class='submit'/></td> + {% endif %} + </table> + </div> + <hr class='spacer'/> + </form> + {%if poll.opened_admin%} + <p><a href="{{root_url}}editChoicesUser/{{poll.base_url}}/">{%trans "Add a new choice to this poll?"%}</a></p>{%endif%} + <div class='footnote'> + {%if hide_vote%}<p>{% trans "You have already vote? You are enough wise not to be influenced by other votes? You can display result by clicking" %} <a href='?display_result=1'>{% trans "here" %}</a>.</p>{%else%} + <p>{% trans "Remain informed of poll evolution:" %} <a href="{{root_url}}feeds/poll/{{poll.base_url}}/">{%trans "syndication"%}</a></p>{%endif%} + </div> +{%if not hide_vote%} +<h3>{%trans "Comments"%}</h3> +<div class='comments'> + {%if poll.open%}<form method='post' action='{{base_url}}'> + <table class='comment'> + <tr> + <td><label for='comment_author'>{% trans "Author name" %}</label></td> + <td><input type='text' id='comment_author' name='comment_author'/></td> + </tr> + <tr> + <td><label for='comment'>{% trans "Comment"%}</label></td> + <td><textarea id='comment' name='comment' cols='' rows=''></textarea></td> + </tr> + <tr><td colspan='2' id='tdsubmit'><input type='submit' class='submit' value='{% trans "Send" %}'/></td></tr> + </table> +</form>{%endif%} + <ul>{%for comment in comments%} + <li><p class='author'>{{comment.author_name}}, {{comment.date|date:_("DATETIME_FORMAT")}} :</p> + {{comment.text|safe}}</li>{%endfor%} + </ul> +</div>{%endif%} +{% endblock %} diff --git a/papillon/urls.py b/papillon/urls.py new file mode 100644 index 0000000..1e35a7b --- /dev/null +++ b/papillon/urls.py @@ -0,0 +1,53 @@ +#!/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. + +from django.conf.urls.defaults import * +from django.contrib import admin +admin.autodiscover() + +from polls.feeds import PollLatestEntries + +feeds = { + 'poll': PollLatestEntries, +} + +urlpatterns = patterns('', + (r'^papillon/admin/doc/', include('django.contrib.admindocs.urls')), + (r'^papillon/admin/jsi18n/$', 'django.views.i18n.javascript_catalog'), + (r'^papillon/admin/(.*)', admin.site.root), + (r'^papillon/$', 'papillon.polls.views.index'), + (r'^papillon/create$', 'papillon.polls.views.create'), + (r'^papillon/edit/(?P<admin_url>\w+)/$', + 'papillon.polls.views.edit'), + (r'^papillon/editChoicesAdmin/(?P<admin_url>\w+)/$', + 'papillon.polls.views.editChoicesAdmin'), + (r'^papillon/editChoicesUser/(?P<poll_url>\w+)/$', + 'papillon.polls.views.editChoicesUser'), + (r'^papillon/category/(?P<category_id>\w+)/$', + 'papillon.polls.views.category'), + (r'^papillon/poll/(?P<poll_url>\w+)/$', 'papillon.polls.views.poll'), + (r'^papillon/poll/(?P<poll_url>\w+)/vote$', 'papillon.polls.views.poll'), + (r'^papillon/feeds/(?P<url>.*)$', + 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), + (r'^papillon/static/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': 'static/'}), + (r'^papillon/media/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': 'media/'}), + (r'^papillon/tinymce/', include('tinymce.urls')), +) |