From 95729368a48257b110ea33dfb005785f8ed6734f Mon Sep 17 00:00:00 2001 From: etienne Date: Sat, 27 Mar 2010 17:06:03 +0000 Subject: Rearrange directories - include documentation --- __init__.py | 0 docs/generate | 5 + docs/style.css | 67 ++++ locale/fr/LC_MESSAGES/django.po | 384 ------------------- manage.py | 11 - papillon/__init__.py | 0 papillon/locale/fr/LC_MESSAGES/django.po | 384 +++++++++++++++++++ papillon/manage.py | 11 + papillon/poll_cleaning.py | 23 ++ papillon/polls/__init__.py | 0 papillon/polls/admin.py | 34 ++ papillon/polls/feeds.py | 55 +++ papillon/polls/forms.py | 132 +++++++ papillon/polls/models.py | 227 ++++++++++++ papillon/polls/templatetags/__init__.py | 0 papillon/polls/templatetags/get_range.py | 25 ++ papillon/polls/views.py | 491 +++++++++++++++++++++++++ papillon/settings.py | 102 +++++ papillon/static/bg.jpg | Bin 0 -> 3473 bytes papillon/static/styles.css | 357 ++++++++++++++++++ papillon/static/textareas.js | 27 ++ papillon/templates/base.html | 27 ++ papillon/templates/category.html | 16 + papillon/templates/create.html | 33 ++ papillon/templates/edit.html | 62 ++++ papillon/templates/editChoices.html | 19 + papillon/templates/editChoicesAdmin.html | 44 +++ papillon/templates/editChoicesUser.html | 27 ++ papillon/templates/feeds/poll_description.html | 8 + papillon/templates/main.html | 22 ++ papillon/templates/vote.html | 148 ++++++++ papillon/urls.py | 53 +++ poll_cleaning.py | 23 -- polls/__init__.py | 0 polls/admin.py | 34 -- polls/feeds.py | 55 --- polls/forms.py | 132 ------- polls/models.py | 227 ------------ polls/templatetags/__init__.py | 0 polls/templatetags/get_range.py | 25 -- polls/views.py | 491 ------------------------- scripts/poll_cleaning.sh | 8 + settings.py | 102 ----- static/bg.jpg | Bin 3473 -> 0 bytes static/styles.css | 357 ------------------ static/textareas.js | 27 -- templates/base.html | 27 -- templates/category.html | 16 - templates/create.html | 33 -- templates/edit.html | 62 ---- templates/editChoices.html | 19 - templates/editChoicesAdmin.html | 44 --- templates/editChoicesUser.html | 27 -- templates/feeds/poll_description.html | 8 - templates/main.html | 22 -- templates/vote.html | 148 -------- urls.py | 53 --- 57 files changed, 2407 insertions(+), 2327 deletions(-) delete mode 100644 __init__.py create mode 100755 docs/generate create mode 100644 docs/style.css delete mode 100644 locale/fr/LC_MESSAGES/django.po delete mode 100755 manage.py create mode 100644 papillon/__init__.py create mode 100644 papillon/locale/fr/LC_MESSAGES/django.po create mode 100755 papillon/manage.py create mode 100755 papillon/poll_cleaning.py create mode 100644 papillon/polls/__init__.py create mode 100644 papillon/polls/admin.py create mode 100644 papillon/polls/feeds.py create mode 100644 papillon/polls/forms.py create mode 100644 papillon/polls/models.py create mode 100644 papillon/polls/templatetags/__init__.py create mode 100644 papillon/polls/templatetags/get_range.py create mode 100644 papillon/polls/views.py create mode 100644 papillon/settings.py create mode 100644 papillon/static/bg.jpg create mode 100644 papillon/static/styles.css create mode 100644 papillon/static/textareas.js create mode 100644 papillon/templates/base.html create mode 100644 papillon/templates/category.html create mode 100644 papillon/templates/create.html create mode 100644 papillon/templates/edit.html create mode 100644 papillon/templates/editChoices.html create mode 100644 papillon/templates/editChoicesAdmin.html create mode 100644 papillon/templates/editChoicesUser.html create mode 100644 papillon/templates/feeds/poll_description.html create mode 100644 papillon/templates/main.html create mode 100644 papillon/templates/vote.html create mode 100644 papillon/urls.py delete mode 100755 poll_cleaning.py delete mode 100644 polls/__init__.py delete mode 100644 polls/admin.py delete mode 100644 polls/feeds.py delete mode 100644 polls/forms.py delete mode 100644 polls/models.py delete mode 100644 polls/templatetags/__init__.py delete mode 100644 polls/templatetags/get_range.py delete mode 100644 polls/views.py create mode 100755 scripts/poll_cleaning.sh delete mode 100644 settings.py delete mode 100644 static/bg.jpg delete mode 100644 static/styles.css delete mode 100644 static/textareas.js delete mode 100644 templates/base.html delete mode 100644 templates/category.html delete mode 100644 templates/create.html delete mode 100644 templates/edit.html delete mode 100644 templates/editChoices.html delete mode 100644 templates/editChoicesAdmin.html delete mode 100644 templates/editChoicesUser.html delete mode 100644 templates/feeds/poll_description.html delete mode 100644 templates/main.html delete mode 100644 templates/vote.html delete mode 100644 urls.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/docs/generate b/docs/generate new file mode 100755 index 0000000..936ba03 --- /dev/null +++ b/docs/generate @@ -0,0 +1,5 @@ +#!/bin/sh +txt2tags --encoding utf-8 --css-sugar --style style.css --toc -t xhtml -o INSTALL.html INSTALL.t2t +txt2tags --encoding utf-8 --toc -t txt -o INSTALL INSTALL.t2t +awk '{gsub("\t"," ");print}' INSTALL > ../INSTALL +rm INSTALL diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..5ba1a2f --- /dev/null +++ b/docs/style.css @@ -0,0 +1,67 @@ +html{ +background-color:#dfcbff; +font-family:arial; +font-size:80%; +} + +body{ +padding:20px; +background-color:white; +} + +a{ +color:#b400ff; +} + +h1{ +font-size:22px; +margin-left:auto; +margin-right:auto; +margin-bottom:10px; +padding-left:20px; +color:white; +background-color:#b488ff; +} + +h2{ +font-size:18px; +padding-left:20px; +color:#b400ff; +} + +h3{ +padding-left:20px; +font-weight:normal; +font-style:italic; +color:#b400ff; +} + +pre{ +background-color:#dfcbff; +padding:10px 6px; +} + +table{ +border-spacing:0px; +border:1px solid; +border-right:None; +} + +td, th, tr{ +border:None; +margin:0; +padding:0; +border-spacing:0px; +border-right:1px solid; +} + +td{ +padding:4px 8px; +} + +th{ +background-color:#dfcbff; +padding:8px; +border-bottom:1px solid; +} + diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po deleted file mode 100644 index 4d09e84..0000000 --- a/locale/fr/LC_MESSAGES/django.po +++ /dev/null @@ -1,384 +0,0 @@ -# Papillon -# Copyright (C) 2008 -# This file is distributed under the same license as the papillon package. -# Étienne Loks , 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 ,\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/manage.py b/manage.py deleted file mode 100755 index bcdd55e..0000000 --- a/manage.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/__init__.py b/papillon/__init__.py new file mode 100644 index 0000000..e69de29 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 , 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 ,\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 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 + +# 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 . + +# 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 + +# 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 . + +# 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 + +# 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 . + +# 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 + +# 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 . + +# 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 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): + +
    {% for i in 3|get_range %} +
  • {{ i }}. Do something
  • +{% endfor %}
+ +Results with the HTML: +
    +
  • 0. Do something
  • +
  • 1. Do something
  • +
  • 2. Do something
  • +
+ +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 + +# 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 . + +# 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 new file mode 100644 index 0000000..7653c63 Binary files /dev/null and b/papillon/static/bg.jpg differ 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 + +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 . + +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 + +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 . + +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 @@ + + + + + {% block title %}Papillon{% endblock %} + {% block fullscript %}{% endblock %} + + + +
+ + +
+{% block content %}{% endblock %} +
+ +
+ + + 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 %} +

{{category.name}}

+

{{category.description}}

+ +{% if polls %}

{%trans "Polls"%}

{%endif%} +{% for poll in polls %} +
+

{{poll.name}}

+

{{poll.description|safe}}

+
+{% 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 %} +

{% trans "New poll" %}

+
+ + {% for field in form %} + {% if field.is_hidden %} + {{field}} + {% else %} + + + + + + + {% endif %} + {% endfor %} + + + + +
{{field.errors}}
{{field.label_tag}}{{field}}{{field.help_text|markdown}}
+
+ +{% 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 %} + + + +{{ form.media }} +{% endblock %} + +{% block content %} +

{% trans "Edit poll" %}

+
+ + + + + + + + + + + + + + + + + {% for field in form %} + {% if field.is_hidden %} + {{field}} + {% else %} + + + + + {% if field.help_text %}{%endif%} + + {% endif %} + {% endfor %} + + + + +
+{{root_url}}poll/{{poll.base_url}} +

+{% trans "Copy this address and send it to voters who want to participate to this poll" %} +

+{{root_url}}edit/{{poll.admin_url}} +

+ {% trans "Address to modify the current poll" %} +

+{{root_url}}editChoicesAdmin/{{poll.admin_url}} +

+ {% trans "Address to modify choices of the current poll." %} +

{{field.errors}}
{{field.label_tag}}{{field}}{{field.help_text|markdown}}
+
+ +{% 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 %} + +

{% trans "New choice" %}

+{%if form_new_choice.errors %}

{{form_new_choice.errors}}

{%endif%} +
+{{form_new_choice.poll}} +{{form_new_choice.order}} + + + + + + + + + +

{% trans "Setting a new choice. Optionally you can set a limit of vote for this choice. This limit is usefull for limited resources allocation." %}

{{form_new_choice.name}}{%trans "Limited to"%} {{form_new_choice.limit}} {%trans "vote(s)"%}
+
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 %} + + + +{{ form_new_choice.media }} +{% endblock %} + +{% block content %} +{% if not choices %}

+{% blocktrans %}As long as no options were added to the poll, it will not be available.{% endblocktrans %} +

{% else %} +

{% trans "Complete/Finalise the poll" %}

+

+{% endif %} +{% include 'editChoices.html' %} +{% if choices %} +

{% trans "Available choices" %}

+ + + {%if not poll.dated_choices%}{%endif%} + + + + + {% for choice in choices %} + + {{choice.form.poll}}{{choice.form.order}} + {%if not poll.dated_choices%}{%endif%} + + + + + + + {% endfor %} +
{% trans "Up/down" %}{% trans "Label" %}{% trans "Limit" %}{% trans "Delete?"%}
+ + / -{{choice.form.name}}{% trans "Limited to"%} {{choice.form.limit}} {% trans "vote(s)" %}
+{% 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 %} + + + +{{ form_new_choice.media }} +{% endblock %} + +{% block content %} +

{%trans "Return to the poll"%}

+

{% trans "Choices" %}

+{% if choices %} + + + + + {% for choice in choices %} + + + {% endfor %} +
{% trans "Label" %}{% trans "Limit" %}
{%if poll.dated_choices%}{{choice.date|date:_("DATETIME_FORMAT")}}{%else%}{{choice.name}}{%endif%}{% if choice.limit %}{% trans "Limited to"%} {{choice.limit}} {% trans "vote(s)" %}{% endif %}
+{% 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 %} +

{% blocktrans with obj.user.name as voter_name %}{{ voter_name }} has added/modified a vote.{%endblocktrans%}

+

{% trans "Current results:" %}

+
    +{% for choice in obj.poll.getChoices %} +
  • {{choice.name}}{% blocktrans count choice.getSum as sum %}: {{sum}} vote{%plural%}: {{sum}} votes{%endblocktrans%}
  • +{% endfor %} +
\ 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 %}

{{error}}

{%endif%} +

{%trans "Create a poll"%}

+

{% trans "Create a new sondage for take a decision, find a date for a meeting, etc." %} {% trans "It's here!" %}

+ +{% if polls %}

{%trans "Public polls"%}

{%endif%} +{% for poll in polls %} +
+

{{poll.name}}

+

{{poll.description}}

+
+{% endfor %} + +{% if categories %}

{%trans "Categories"%}

{% endif %} +{% for category in categories %} +

{{category.name}}

+{% 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 %} + + + +{{ form_comment.media }} +{% endblock %} + +{% block content %} +

{%if poll.category %}{{poll.category.name}} - {%endif%}{{poll.name}}

+{% if error %}

{{ error }}

{% endif %} +{% if not poll.open %}

{% trans "The current poll is closed."%}

{% endif %} +

{{ poll.description|safe }}

+
+
+ + + + + {% for choice in choices %} + {% endfor %} + {% if not hide_vote %} + {% for voter in voters %} +{% ifequal current_voter_id voter.id %} + + + + {% for vote in voter.votes %} + {%endfor%} +{%else%} + + + {% for vote in voter.votes %} + {% ifequal poll.type 'V' %} + + {% else %} + + {% endifequal %} + {%endfor%} + {%endifequal%} + {%endfor%} + {%endif%} + {%if not current_voter_id%}{% if poll.open %} + + + + {%for choice in choices%}{%endfor%} + + {%endif%}{%endif%} + {% if not hide_vote %} + + {% for choice in choices %}{{choice.getSum}} + {% endfor %} + {%endif%} + {% if poll.open %} + + + {% endif %} +
{%if poll.dated_choices%}{{choice.date|date:_("DATETIME_FORMAT")}}{%else%}{{choice.name}}{%endif%}{% if choice.limit %} ({% trans "max" %} {{choice.limit}}){%endif%}
+ {% if vote.choice.available or vote.value %} + {% ifequal poll.type 'P' %} + + {% endifequal %} + {% ifequal poll.type 'O' %} + + {% endifequal %} + {% ifequal poll.type 'B' %} + + {% endifequal %} + {% ifequal poll.type 'V' %} + + {% endifequal %} + {% else %} + {% trans "Limit reached" %} + {% endif %} + {% if poll.open %}{% trans "Edit" %}{%else%} {%endif%}{{voter.user.name}} + {%if vote.value%}{{vote.value}}{%else%}0{%endif%} + {%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%} +
+ {% if choice.available %} + {% ifequal poll.type 'P' %} + {% endifequal %} + {% ifequal poll.type 'O' %} + {% endifequal %} + {% ifequal poll.type 'B' %} + + {% endifequal %} + {% ifequal poll.type 'V' %} + + {% endifequal %} + {% else %} + {% trans "Limit reached" %} + {% endif %} +
{% trans "Sum" %}
+
+
+
+ {%if poll.opened_admin%} +

{%trans "Add a new choice to this poll?"%}

{%endif%} +
+ {%if hide_vote%}

{% trans "You have already vote? You are enough wise not to be influenced by other votes? You can display result by clicking" %} {% trans "here" %}.

{%else%} +

{% trans "Remain informed of poll evolution:" %} {%trans "syndication"%}

{%endif%} +
+{%if not hide_vote%} +

{%trans "Comments"%}

+
+ {%if poll.open%}
+ + + + + + + + + + +
+
{%endif%} +
    {%for comment in comments%} +
  • {{comment.author_name}}, {{comment.date|date:_("DATETIME_FORMAT")}} :

    + {{comment.text|safe}}
  • {%endfor%} +
+
{%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 + +# 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 . + +# 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\w+)/$', + 'papillon.polls.views.edit'), + (r'^papillon/editChoicesAdmin/(?P\w+)/$', + 'papillon.polls.views.editChoicesAdmin'), + (r'^papillon/editChoicesUser/(?P\w+)/$', + 'papillon.polls.views.editChoicesUser'), + (r'^papillon/category/(?P\w+)/$', + 'papillon.polls.views.category'), + (r'^papillon/poll/(?P\w+)/$', 'papillon.polls.views.poll'), + (r'^papillon/poll/(?P\w+)/vote$', 'papillon.polls.views.poll'), + (r'^papillon/feeds/(?P.*)$', + 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), + (r'^papillon/static/(?P.*)$', 'django.views.static.serve', + {'document_root': 'static/'}), + (r'^papillon/media/(?P.*)$', 'django.views.static.serve', + {'document_root': 'media/'}), + (r'^papillon/tinymce/', include('tinymce.urls')), +) diff --git a/poll_cleaning.py b/poll_cleaning.py deleted file mode 100755 index 993a5b4..0000000 --- a/poll_cleaning.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/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/polls/__init__.py b/polls/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/polls/admin.py b/polls/admin.py deleted file mode 100644 index 2207c60..0000000 --- a/polls/admin.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Étienne Loks - -# 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 . - -# 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/polls/feeds.py b/polls/feeds.py deleted file mode 100644 index 2d52dc7..0000000 --- a/polls/feeds.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Étienne Loks - -# 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 . - -# 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/polls/forms.py b/polls/forms.py deleted file mode 100644 index 3a151aa..0000000 --- a/polls/forms.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2009 Étienne Loks - -# 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 . - -# 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/polls/models.py b/polls/models.py deleted file mode 100644 index f8b3b22..0000000 --- a/polls/models.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Étienne Loks - -# 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 . - -# 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/polls/templatetags/__init__.py b/polls/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/polls/templatetags/get_range.py b/polls/templatetags/get_range.py deleted file mode 100644 index b9d8328..0000000 --- a/polls/templatetags/get_range.py +++ /dev/null @@ -1,25 +0,0 @@ -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): - -
    {% for i in 3|get_range %} -
  • {{ i }}. Do something
  • -{% endfor %}
- -Results with the HTML: -
    -
  • 0. Do something
  • -
  • 1. Do something
  • -
  • 2. Do something
  • -
- -Instead of 3 one may use the variable set in the views - """ - return range(value) - diff --git a/polls/views.py b/polls/views.py deleted file mode 100644 index e9ef572..0000000 --- a/polls/views.py +++ /dev/null @@ -1,491 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Étienne Loks - -# 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 . - -# 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/scripts/poll_cleaning.sh b/scripts/poll_cleaning.sh new file mode 100755 index 0000000..0635007 --- /dev/null +++ b/scripts/poll_cleaning.sh @@ -0,0 +1,8 @@ +# Cron jobs + +export DJANGO_SETTINGS_MODULE=papillon.settings + +#0 3 * * * python /path/to/maintenance/script.py +./poll_cleaning.py + + diff --git a/settings.py b/settings.py deleted file mode 100644 index 85ae8c8..0000000 --- a/settings.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/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/static/bg.jpg b/static/bg.jpg deleted file mode 100644 index 7653c63..0000000 Binary files a/static/bg.jpg and /dev/null differ diff --git a/static/styles.css b/static/styles.css deleted file mode 100644 index c329905..0000000 --- a/static/styles.css +++ /dev/null @@ -1,357 +0,0 @@ -/* -Copyright (C) 2008 Étienne Loks - -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 . - -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/static/textareas.js b/static/textareas.js deleted file mode 100644 index fec83b8..0000000 --- a/static/textareas.js +++ /dev/null @@ -1,27 +0,0 @@ -/* base function shared by some pages */ -/* Copyright (C) 2009 Étienne Loks - -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 . - -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/templates/base.html b/templates/base.html deleted file mode 100644 index b673f24..0000000 --- a/templates/base.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - {% block title %}Papillon{% endblock %} - {% block fullscript %}{% endblock %} - - - -
- - -
-{% block content %}{% endblock %} -
- -
- - - diff --git a/templates/category.html b/templates/category.html deleted file mode 100644 index 55aa084..0000000 --- a/templates/category.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block content %} -

{{category.name}}

-

{{category.description}}

- -{% if polls %}

{%trans "Polls"%}

{%endif%} -{% for poll in polls %} -
-

{{poll.name}}

-

{{poll.description|safe}}

-
-{% endfor %} - -{% endblock %} diff --git a/templates/create.html b/templates/create.html deleted file mode 100644 index 7c3d2b3..0000000 --- a/templates/create.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} -{% load markup %} - -{% block fullscript %} - {{ form.media }} -{% endblock %} - - -{% block content %} -

{% trans "New poll" %}

-
- - {% for field in form %} - {% if field.is_hidden %} - {{field}} - {% else %} - - - - - - - {% endif %} - {% endfor %} - - - - -
{{field.errors}}
{{field.label_tag}}{{field}}{{field.help_text|markdown}}
-
- -{% endblock %} diff --git a/templates/edit.html b/templates/edit.html deleted file mode 100644 index 7ef4f14..0000000 --- a/templates/edit.html +++ /dev/null @@ -1,62 +0,0 @@ -{% extends "base.html" %} -{% load markup %} -{% load i18n %} - -{% block fullscript %} - - - -{{ form.media }} -{% endblock %} - -{% block content %} -

{% trans "Edit poll" %}

-
- - - - - - - - - - - - - - - - - {% for field in form %} - {% if field.is_hidden %} - {{field}} - {% else %} - - - - - {% if field.help_text %}{%endif%} - - {% endif %} - {% endfor %} - - - - -
-{{root_url}}poll/{{poll.base_url}} -

-{% trans "Copy this address and send it to voters who want to participate to this poll" %} -

-{{root_url}}edit/{{poll.admin_url}} -

- {% trans "Address to modify the current poll" %} -

-{{root_url}}editChoicesAdmin/{{poll.admin_url}} -

- {% trans "Address to modify choices of the current poll." %} -

{{field.errors}}
{{field.label_tag}}{{field}}{{field.help_text|markdown}}
-
- -{% endblock %} diff --git a/templates/editChoices.html b/templates/editChoices.html deleted file mode 100644 index 1082d30..0000000 --- a/templates/editChoices.html +++ /dev/null @@ -1,19 +0,0 @@ -{% load markup %} -{% load i18n %} - -

{% trans "New choice" %}

-{%if form_new_choice.errors %}

{{form_new_choice.errors}}

{%endif%} -
-{{form_new_choice.poll}} -{{form_new_choice.order}} - - - - - - - - - -

{% trans "Setting a new choice. Optionally you can set a limit of vote for this choice. This limit is usefull for limited resources allocation." %}

{{form_new_choice.name}}{%trans "Limited to"%} {{form_new_choice.limit}} {%trans "vote(s)"%}
-
diff --git a/templates/editChoicesAdmin.html b/templates/editChoicesAdmin.html deleted file mode 100644 index a7798bf..0000000 --- a/templates/editChoicesAdmin.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "base.html" %} -{% load markup %} -{% load i18n %} - -{% block fullscript %} - - - -{{ form_new_choice.media }} -{% endblock %} - -{% block content %} -{% if not choices %}

-{% blocktrans %}As long as no options were added to the poll, it will not be available.{% endblocktrans %} -

{% else %} -

{% trans "Complete/Finalise the poll" %}

-

-{% endif %} -{% include 'editChoices.html' %} -{% if choices %} -

{% trans "Available choices" %}

- - - {%if not poll.dated_choices%}{%endif%} - - - - - {% for choice in choices %} - - {{choice.form.poll}}{{choice.form.order}} - {%if not poll.dated_choices%}{%endif%} - - - - - - - {% endfor %} -
{% trans "Up/down" %}{% trans "Label" %}{% trans "Limit" %}{% trans "Delete?"%}
+ - / -{{choice.form.name}}{% trans "Limited to"%} {{choice.form.limit}} {% trans "vote(s)" %}
-{% endif %} - -{% endblock %} diff --git a/templates/editChoicesUser.html b/templates/editChoicesUser.html deleted file mode 100644 index 0d2b2c1..0000000 --- a/templates/editChoicesUser.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} -{% load markup %} -{% load i18n %} - -{% block fullscript %} - - - -{{ form_new_choice.media }} -{% endblock %} - -{% block content %} -

{%trans "Return to the poll"%}

-

{% trans "Choices" %}

-{% if choices %} - - - - - {% for choice in choices %} - - - {% endfor %} -
{% trans "Label" %}{% trans "Limit" %}
{%if poll.dated_choices%}{{choice.date|date:_("DATETIME_FORMAT")}}{%else%}{{choice.name}}{%endif%}{% if choice.limit %}{% trans "Limited to"%} {{choice.limit}} {% trans "vote(s)" %}{% endif %}
-{% endif %} -{% include 'editChoices.html' %} -{% endblock %} diff --git a/templates/feeds/poll_description.html b/templates/feeds/poll_description.html deleted file mode 100644 index 7522a5a..0000000 --- a/templates/feeds/poll_description.html +++ /dev/null @@ -1,8 +0,0 @@ -{% load i18n %} -

{% blocktrans with obj.user.name as voter_name %}{{ voter_name }} has added/modified a vote.{%endblocktrans%}

-

{% trans "Current results:" %}

-
    -{% for choice in obj.poll.getChoices %} -
  • {{choice.name}}{% blocktrans count choice.getSum as sum %}: {{sum}} vote{%plural%}: {{sum}} votes{%endblocktrans%}
  • -{% endfor %} -
\ No newline at end of file diff --git a/templates/main.html b/templates/main.html deleted file mode 100644 index 8a09830..0000000 --- a/templates/main.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block content %} -{% if error %}

{{error}}

{%endif%} -

{%trans "Create a poll"%}

-

{% trans "Create a new sondage for take a decision, find a date for a meeting, etc." %} {% trans "It's here!" %}

- -{% if polls %}

{%trans "Public polls"%}

{%endif%} -{% for poll in polls %} -
-

{{poll.name}}

-

{{poll.description}}

-
-{% endfor %} - -{% if categories %}

{%trans "Categories"%}

{% endif %} -{% for category in categories %} -

{{category.name}}

-{% endfor %} - -{% endblock %} diff --git a/templates/vote.html b/templates/vote.html deleted file mode 100644 index eb1ae21..0000000 --- a/templates/vote.html +++ /dev/null @@ -1,148 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} -{% load get_range %} - -{% block fullscript %} - - - -{{ form_comment.media }} -{% endblock %} - -{% block content %} -

{%if poll.category %}{{poll.category.name}} - {%endif%}{{poll.name}}

-{% if error %}

{{ error }}

{% endif %} -{% if not poll.open %}

{% trans "The current poll is closed."%}

{% endif %} -

{{ poll.description|safe }}

-
-
- - - - - {% for choice in choices %} - {% endfor %} - {% if not hide_vote %} - {% for voter in voters %} -{% ifequal current_voter_id voter.id %} - - - - {% for vote in voter.votes %} - {%endfor%} -{%else%} - - - {% for vote in voter.votes %} - {% ifequal poll.type 'V' %} - - {% else %} - - {% endifequal %} - {%endfor%} - {%endifequal%} - {%endfor%} - {%endif%} - {%if not current_voter_id%}{% if poll.open %} - - - - {%for choice in choices%}{%endfor%} - - {%endif%}{%endif%} - {% if not hide_vote %} - - {% for choice in choices %}{{choice.getSum}} - {% endfor %} - {%endif%} - {% if poll.open %} - - - {% endif %} -
{%if poll.dated_choices%}{{choice.date|date:_("DATETIME_FORMAT")}}{%else%}{{choice.name}}{%endif%}{% if choice.limit %} ({% trans "max" %} {{choice.limit}}){%endif%}
- {% if vote.choice.available or vote.value %} - {% ifequal poll.type 'P' %} - - {% endifequal %} - {% ifequal poll.type 'O' %} - - {% endifequal %} - {% ifequal poll.type 'B' %} - - {% endifequal %} - {% ifequal poll.type 'V' %} - - {% endifequal %} - {% else %} - {% trans "Limit reached" %} - {% endif %} - {% if poll.open %}{% trans "Edit" %}{%else%} {%endif%}{{voter.user.name}} - {%if vote.value%}{{vote.value}}{%else%}0{%endif%} - {%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%} -
- {% if choice.available %} - {% ifequal poll.type 'P' %} - {% endifequal %} - {% ifequal poll.type 'O' %} - {% endifequal %} - {% ifequal poll.type 'B' %} - - {% endifequal %} - {% ifequal poll.type 'V' %} - - {% endifequal %} - {% else %} - {% trans "Limit reached" %} - {% endif %} -
{% trans "Sum" %}
-
-
-
- {%if poll.opened_admin%} -

{%trans "Add a new choice to this poll?"%}

{%endif%} -
- {%if hide_vote%}

{% trans "You have already vote? You are enough wise not to be influenced by other votes? You can display result by clicking" %} {% trans "here" %}.

{%else%} -

{% trans "Remain informed of poll evolution:" %} {%trans "syndication"%}

{%endif%} -
-{%if not hide_vote%} -

{%trans "Comments"%}

-
- {%if poll.open%}
- - - - - - - - - - -
-
{%endif%} -
    {%for comment in comments%} -
  • {{comment.author_name}}, {{comment.date|date:_("DATETIME_FORMAT")}} :

    - {{comment.text|safe}}
  • {%endfor%} -
-
{%endif%} -{% endblock %} diff --git a/urls.py b/urls.py deleted file mode 100644 index 1e35a7b..0000000 --- a/urls.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (C) 2008 Étienne Loks - -# 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 . - -# 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\w+)/$', - 'papillon.polls.views.edit'), - (r'^papillon/editChoicesAdmin/(?P\w+)/$', - 'papillon.polls.views.editChoicesAdmin'), - (r'^papillon/editChoicesUser/(?P\w+)/$', - 'papillon.polls.views.editChoicesUser'), - (r'^papillon/category/(?P\w+)/$', - 'papillon.polls.views.category'), - (r'^papillon/poll/(?P\w+)/$', 'papillon.polls.views.poll'), - (r'^papillon/poll/(?P\w+)/vote$', 'papillon.polls.views.poll'), - (r'^papillon/feeds/(?P.*)$', - 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), - (r'^papillon/static/(?P.*)$', 'django.views.static.serve', - {'document_root': 'static/'}), - (r'^papillon/media/(?P.*)$', 'django.views.static.serve', - {'document_root': 'media/'}), - (r'^papillon/tinymce/', include('tinymce.urls')), -) -- cgit v1.2.3