Commits

Rafael A committed 820d0ae

project started

  • Participants

Comments (0)

Files changed (14)

poll/__init__.py

Empty file added.
+from django.contrib import admin
+from poll.models import PollType, Poll, Item, Queue, Vote, Choice
+from django.utils.translation import gettext as _
+
+class PollItemInline(admin.TabularInline):
+    model = Item
+    extra = 5
+    max_num = 10
+
+class PollAdmin(admin.ModelAdmin):
+    list_display = ('publish', 'startdate', 'polltype', 'title', 'vote_count')
+    
+    inlines = [
+        PollItemInline,
+    ]
+
+    fieldsets = (
+                 (None, {'fields': ('title',)}),
+                 (_('Options'), {'fields': ('publish', 'polltype', 'queue', 'startdate',)}),
+                 )
+
+class VoteChoiceItemInline(admin.TabularInline):
+    model = Choice
+    extra = 5
+    max_num = 10
+    readonly_fields = ('item', 'uservalue')
+
+class VoteAdmin(admin.ModelAdmin):
+    list_display = ('poll', 'ip', 'user', 'datetime')
+    list_filter = ('poll', 'datetime')
+    
+    inlines = [
+        VoteChoiceItemInline,
+    ]
+
+admin.site.register(Poll, PollAdmin)
+admin.site.register(PollType, admin.ModelAdmin)
+admin.site.register(Queue, admin.ModelAdmin)
+admin.site.register(Vote, VoteAdmin)
+
+#admin.site.register(Item, admin.ModelAdmin)
+#admin.site.register(Choice, admin.ModelAdmin)
+from django.http import HttpResponse
+from poll.models import Poll, Item, Vote, Choice
+from django.db import transaction
+from django.contrib.auth.models import AnonymousUser
+from django.utils import simplejson
+from utils import set_cookie
+
+def authpassQueue(user, queue):
+    if queue != None:
+        if queue.auth and not user.has_perm('Poll.can_vote'):
+            return False
+    return True
+
+#TODO: Need to optimize
+@transaction.commit_on_success
+def poll_ajax_vote(request, poll_pk):
+    if request.is_ajax():
+        
+        queue = Poll.publish_manager.get(pk=poll_pk).queue
+        if not authpassQueue(request.user, queue):    
+            return HttpResponse(status=400)
+        
+        try:
+            chosen_items = simplejson.loads(request.GET['chosen_items'])
+        except:
+            return HttpResponse(status=400)
+        
+        poll = Poll.objects.get(pk=poll_pk)
+        
+        if isinstance(request.user, AnonymousUser):
+            user = None
+        else:
+            user = request.user 
+        
+        vote = Vote.objects.create(poll=poll,
+                                   ip=request.META['REMOTE_ADDR'],
+                                   user=user)
+        
+        for item_pk, value in chosen_items.items():
+            item = Item.objects.get(pk=item_pk)
+            
+            if item.userbox:
+                Choice.objects.create(vote=vote, item=item, uservalue=value)
+            else:
+                Choice.objects.create(vote=vote, item=item)
+        
+        response = HttpResponse(status=200)
+        set_cookie(response, poll.get_cookie_name(), poll_pk)
+        
+        return response
+    return HttpResponse(status=400)
+
+def poll_ajax_result(request, poll_pk):
+    if request.is_ajax():
+        poll = Poll.objects.get(pk=poll_pk)        
+        #Send data for results
+        data = {}
+        
+        for item in Item.objects.filter(poll=poll):
+            subdata = {
+                       'index': item.index,
+                       'title': item.value,
+                       'count': Choice.objects.filter(item=item).count(),
+                       }
+            
+            data[item.pk] = subdata
+            
+        data['total'] = Vote.objects.filter(poll=poll).count()
+        
+        return HttpResponse(simplejson.dumps(data))
+    return HttpResponse(status=400)

poll/fixtures/initial_data.json

+[{"pk": 2, "model": "poll.polltype", "fields": {"index": 1, "title": "Multiple"}}, {"pk": 1, "model": "poll.polltype", "fields": {"index": 0, "title": "Single"}}, {"pk": 1, "model": "poll.poll", "fields": {"startdate": "2011-11-06", "votes": [], "title": "Who will be a next Russian President?", "publish": true, "polltype": 1, "queue": 1}}, {"pk": 1, "model": "poll.queue", "fields": {"auth": false, "title": "Public"}}, {"pk": 2, "model": "poll.queue", "fields": {"auth": true, "title": "Private"}}, {"pk": 1, "model": "poll.item", "fields": {"index": 0, "poll": 1, "value": "Mr. Medvedev", "userbox": false}}, {"pk": 2, "model": "poll.item", "fields": {"index": 1, "poll": 1, "value": "Mr. Putin", "userbox": false}}, {"pk": 3, "model": "poll.item", "fields": {"index": 2, "poll": 1, "value": "Mr. Freeman", "userbox": false}}, {"pk": 4, "model": "poll.item", "fields": {"index": 3, "poll": 1, "value": "KGB", "userbox": false}}, {"pk": 5, "model": "poll.item", "fields": {"index": 4, "poll": 1, "value": "or...", "userbox": true}}, {"pk": 1, "model": "poll.vote", "fields": {"ip": "127.0.0.1", "poll": 1, "user": 1, "datetime": "2011-11-06 09:04:01"}}, {"pk": 1, "model": "poll.choice", "fields": {"vote": 1, "item": 4, "uservalue": null}}]

poll/locale/ru/LC_MESSAGES/django.mo

Binary file added.

poll/locale/ru/LC_MESSAGES/django.po

+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-11-06 17:05+0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+#: admin.py:19
+msgid "Options"
+msgstr "Опции"
+
+#: models.py:8
+msgid "Type title"
+msgstr "Введите заголовок"
+
+#: models.py:8
+msgid "It will be used as header of the poll"
+msgstr "Будет использован как заголовок опроса"
+
+#: models.py:9
+msgid "Index"
+msgstr "Индекс"
+
+#: models.py:9 models.py:27
+msgid "Must be unique"
+msgstr "Должен быть уникальным"
+
+#: models.py:16
+msgid "PollType"
+msgstr "Тип опроса"
+
+#: models.py:17
+msgid "PollTypes"
+msgstr "Типы опроса"
+
+#: models.py:24
+msgid "Title"
+msgstr "Заголовок"
+
+#: models.py:24
+msgid "The parameter is used as question to user"
+msgstr "Параметр используется как заголовок вопроса к пользователю"
+
+#: models.py:25 models.py:75
+msgid "Queue"
+msgstr "Очередь"
+
+#: models.py:25
+msgid ""
+"Do you want to use the poll as a stand alone poll or insert it into the "
+"queue?"
+msgstr ""
+"Вы хотите использовать опрос как отдельностоящий опрос или вставить его в "
+"очередь?"
+
+#: models.py:26
+msgid "Poll type"
+msgstr "Тип опроса"
+
+#: models.py:26
+msgid "Choose the poll type"
+msgstr "Выберете тип опроса"
+
+#: models.py:27
+msgid "Start date"
+msgstr "Дата старта"
+
+#: models.py:28
+msgid "Publish"
+msgstr "Публиковать"
+
+#: models.py:29 models.py:104
+msgid "Votes"
+msgstr "Голоса"
+
+#: models.py:29
+msgid "Choose variants of answers"
+msgstr "Выберете варианты ответов"
+
+#: models.py:35
+msgid "The \"Start date\" param must be unique"
+msgstr "Поле \"Start date\" должно быть уникальным"
+
+#: models.py:57 models.py:91 templates/polls.html:20
+msgid "Poll"
+msgstr "Опрос"
+
+#: models.py:58
+msgid "Polls"
+msgstr "Опросы"
+
+#: models.py:59
+msgid "User can vote"
+msgstr "Пользователь может голосовать"
+
+#: models.py:62
+msgid "Queue name"
+msgstr "Название очереди"
+
+#: models.py:62
+msgid "It must be unique"
+msgstr "Должен быть уникальным"
+
+#: models.py:63
+msgid "Need auth?"
+msgstr "Нужна аутентификация?"
+
+#: models.py:63
+msgid ""
+"Do the poll queue is for authenticated users only or not? (If yes, users "
+"must have \"can_vote\" permission to vote)"
+msgstr ""
+"Очередь опросов только для аутентифицированных пользователей или нет? (Иначе "
+"у пользователя должен быть доступ \"can_vote\")"
+
+#: models.py:67
+msgid "With auth"
+msgstr "С авторизацией"
+
+#: models.py:69
+msgid "Without auth"
+msgstr "Без авторизации"
+
+#: models.py:76
+msgid "Queues"
+msgstr "Очереди"
+
+#: models.py:80
+msgid "Its userbox?"
+msgstr "Это пользовательский ввод?"
+
+#: models.py:80
+msgid "Set it, if you want user to type its own answer. (show as textbox)"
+msgstr ""
+"Выберете это, если вы хотите что бы пользователь сам напечатал свой ответ "
+"(вывод textbox)"
+
+#: models.py:81
+msgid "Value"
+msgstr "Значение"
+
+#: models.py:81
+msgid "Its a title of item"
+msgstr "Заголовок item"
+
+#: models.py:82
+msgid "Position"
+msgstr "Позиция"
+
+#: models.py:82
+msgid "Its for positioning only"
+msgstr "Только для позиционирования"
+
+#: models.py:92
+msgid "User's IP"
+msgstr "IP пользователя"
+
+#: models.py:93 templates/polls.html:23
+msgid "User"
+msgstr "Пользователь"
+
+#: models.py:95
+msgid "Voited items"
+msgstr "Голосовавшие items"
+
+#: models.py:103 templates/polls.html:32
+msgid "Vote"
+msgstr "Голос"
+
+#: templates/polls.html:12
+msgid "Total Voters"
+msgstr "Всего"
+
+#: templates/polls.html:13
+msgid "Please, choose anything to vote"
+msgstr "Пожалуйста выберете что-нибудь чтобы проголосовать"
+
+#: templates/polls.html:28
+msgid "You already voted"
+msgstr "Вы уже проголосовали"
+
+#: templates/polls.html:29
+msgid "Show results"
+msgstr "Показать результаты"
+from django.db import models
+from django.utils.translation import gettext as _
+from django.contrib.auth.models import User
+from django.db.models.manager import Manager
+from django.core.exceptions import ValidationError
+
+class PollType(models.Model):
+    title = models.CharField(max_length=150, verbose_name=_('Type title'), help_text=_('It will be used as header of the poll'))
+    index = models.SmallIntegerField(unique=True, verbose_name=_('Index'), help_text=_('Must be unique'))
+    
+    def __unicode__(self):
+        return '%s (%d)' % (self.title, self.index)
+    
+    class Meta:
+        ordering = ['-index']
+        verbose_name = _('PollType')
+        verbose_name_plural = _('PollTypes')
+        
+class PublishManager(Manager):
+    def get_query_set(self):
+        return super(PublishManager, self).get_query_set().filter(publish=True)
+
+class Poll(models.Model):
+    title = models.CharField(max_length=250, verbose_name=_('Title'), help_text=_('The parameter is used as question to user'))
+    queue = models.ForeignKey('Queue', blank=True, null=True, verbose_name=_('Queue'), help_text=_('Do you want to use the poll as a stand alone poll or insert it into the queue?'))
+    polltype = models.ForeignKey(PollType, verbose_name=_('Poll type'), help_text=_('Choose the poll type'))
+    startdate = models.DateField(verbose_name=_('Start date'), help_text=_('Must be unique'))
+    publish = models.BooleanField(default=True, verbose_name=_('Publish'))
+    votes = models.ManyToManyField('Vote', related_name='%(app_label)s_%(class)s_related', blank=True, verbose_name=_('Votes'), help_text=_('Choose variants of answers'))
+    
+    objects = models.Manager()
+    publish_manager = PublishManager()
+    
+    def clean(self):
+        err_msg = _('The "Start date" param must be unique')
+        objs = Poll.objects.filter(startdate=self.startdate)
+        count = objs.count()
+        
+        if count > 1:
+            raise ValidationError(err_msg)
+        elif count == 1:
+            if objs[0] != self:
+                raise ValidationError(err_msg)
+    
+    def get_vote_count(self):
+        return Vote.objects.filter(poll=self).count()
+    vote_count = property(fget=get_vote_count)
+    
+    def get_cookie_name(self):
+        return str('poll_%s' % (self.pk)) 
+    
+    def __unicode__(self):
+        return self.title
+    
+    class Meta:
+        ordering = ['-startdate']
+        verbose_name = _('Poll')
+        verbose_name_plural = _('Polls')
+        permissions = (("can_vote", _("User can vote")),)
+        
+class Queue(models.Model):
+    title = models.CharField(unique=True, max_length=250, verbose_name=_('Queue name'), help_text=_('It must be unique'))
+    auth = models.BooleanField(verbose_name=_('Need auth?'), help_text=_('Do the poll queue is for authenticated users only or not? (If yes, users must have "can_vote" permission to vote)'))
+    
+    def __unicode__(self):
+        if self.auth:
+            auth = _('With auth')
+        else:
+            auth = _('Without auth')
+        
+        return '%s (%s)' % (self.title, auth)
+    
+    class Meta:
+        ordering = ['-title']
+        verbose_name = _('Queue')
+        verbose_name_plural = _('Queues')
+
+class Item(models.Model):
+    poll = models.ForeignKey(Poll)
+    userbox = models.BooleanField(verbose_name=_('Its userbox?'), help_text=_('Set it, if you want user to type its own answer. (show as textbox)'))
+    value = models.CharField(max_length=250, verbose_name=_('Value'), help_text=_('Its a title of item'))
+    index = models.SmallIntegerField(default='0', verbose_name=_('Position'), help_text=_('Its for positioning only'))
+    
+    def __unicode__(self):
+        return '%s (%d)' % (self.value, self.index)
+    
+    class Meta:
+        ordering = ['index']
+
+class Vote(models.Model):
+    poll = models.ForeignKey(Poll, verbose_name=_('Poll'))
+    ip = models.IPAddressField(verbose_name=_('User\'s IP'))
+    user = models.ForeignKey(User, blank=True, null=True, verbose_name=_('User'))
+    datetime = models.DateTimeField(auto_now_add=True)
+    choices = models.ManyToManyField(Item, through='Choice', verbose_name=_('Voited items'))
+    
+    def __unicode__(self):
+        if isinstance(self.user, User):
+            return self.user.username
+        return self.ip
+    
+    class Meta:
+        verbose_name = _('Vote')
+        verbose_name_plural = _('Votes')
+        
+class Choice(models.Model):
+    vote = models.ForeignKey(Vote)
+    item = models.ForeignKey(Item)
+    uservalue = models.CharField(max_length=250, blank=True, null=True)

poll/templates/polls.html

+{% load i18n %}
+{% load polls_tags %}
+
+{% if poll %}
+<link rel="stylesheet" href="{{ STATIC_URL }}polls/css/polls.css" />
+<script type="text/javascript">
+	vote_url = '{% url poll_ajax_vote poll.pk %}';
+	result_url = '{% url poll_ajax_result poll.pk %}';
+	poll_pk = '{{ poll.pk }}';
+	poll_type = {% if poll.polltype.index == 0 %}'single'{% else %}'multiple'{% endif %};
+	show_points = false;
+	trans_total_voters = '{% trans "Total Voters" %}';
+	trans_select_something = '{% trans "Please, choose anything to vote" %}';
+</script>
+<script type="text/javascript" src="{{ STATIC_URL }}polls/js/common.js"></script>
+
+<div id="poll_content">
+	<form method="post">
+		{% csrf_token %}
+		<h1><span id="poll">{% trans "Poll" %}:</span> {{ poll.title }}</h1>
+		{% if user.is_authenticated %}
+		<div id="poll_userspace">
+			{% trans "User" %}: {{ user }}
+		</div>
+		{% endif %} 
+		<div id="poll_body">
+			{% if poll.get_cookie_name in request.COOKIES %}
+				<p>{% trans "You already voted" %}.</p>
+				<p><a id="showresults" href="javascript:void(0);">{% trans "Show results" %}</a></p>
+			{% else %}
+				{% render_items poll items %}
+				<p><input id="sendvote" type="button" value="{% trans "Vote" %}" /></p>
+			{% endif %}
+		</div>
+	</form>
+</div>
+{% endif %}

poll/templatetags/__init__.py

Empty file added.

poll/templatetags/polls_tags.py

+from django import template
+from poll.models import Poll, Item, Queue, Vote
+from settings import STATIC_URL
+from django.utils.safestring import SafeUnicode
+from django.utils.datetime_safe import datetime
+from poll.ajax import authpassQueue
+
+register = template.Library()
+
+@register.inclusion_tag('polls.html', takes_context=True)
+def poll(context, poll):
+    return {'poll': poll, 'items': Item.objects.filter(poll=poll), 'user': context['user'], 'request': context['request'], 'STATIC_URL': STATIC_URL}
+
+@register.inclusion_tag('polls.html', takes_context=True)
+def poll_queue(context, queue):
+    if isinstance(queue, SafeUnicode):
+        tmp_queue = Queue.objects.get(title=queue)
+    else:
+        tmp_queue = Queue.objects.get(queue)
+    
+    if authpassQueue(context['user'], tmp_queue):
+        tmp_polls = Poll.publish_manager.filter(queue=tmp_queue, startdate__lte=datetime.now())
+        
+        if len(tmp_polls) > 0:
+            cur_poll = tmp_polls[0]
+        else:
+            cur_poll = None
+        
+        return poll(context, cur_poll)
+
+class RenderItemsClass(template.Node):
+    def __init__(self, poll, items):
+        self.poll=template.Variable(poll)
+        self.items=template.Variable(items)
+        
+    def render(self, context):
+        poll = self.poll.resolve(context)
+        items = self.items.resolve(context)
+        #'name' = item.pk
+        pattern1 = '{3}<br /><input name="poll_{0}" type="{1}" id="{2}" value="" /><br />'
+        pattern2 = '<input name="poll_{0}" type="{1}" id="{2}" /> {3}<br />'
+        result = ''
+        
+        #Choose an input type
+        for item in items:
+            if item.userbox:
+                input_type = 'textbox'
+                pattern = pattern1
+            else:
+                if poll.polltype.index == 0:
+                    input_type = 'radio'
+                elif poll.polltype.index == 1:
+                    input_type = 'checkbox'
+                pattern = pattern2
+                    
+            result += pattern.format(poll.pk, input_type, item.pk, item.value)
+            
+        return result
+
+@register.tag
+def render_items(parser, token):
+    tag, poll, items = token.split_contents()
+    return RenderItemsClass(poll, items)
+from django.conf.urls.defaults import patterns, url
+from poll.ajax import poll_ajax_vote, poll_ajax_result
+
+urlpatterns = patterns('',
+    url(r'^vote/(?P<poll_pk>\d)$', poll_ajax_vote, name='poll_ajax_vote'),
+    url(r'^result/(?P<poll_pk>\d)$', poll_ajax_result, name='poll_ajax_result'),
+)
+import datetime
+
+def set_cookie(response, key, value, days_expire = 90):
+    if days_expire is None:
+        max_age = 365 * 24 * 60 * 60    #one year
+    else:
+        max_age = days_expire * 24 * 60 * 60 
+    expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=max_age), "%a, %d-%b-%Y %H:%M:%S GMT")
+    response.set_cookie(key, value, max_age=max_age, expires=expires)

static/polls/css/polls.css

+#poll_content {background-color: #0B9D0B; padding: 3px;}
+#poll_content h1 {font-variant: small-caps;}
+#poll_content #poll_userspace {float: left; width: 100%; text-align: right;}
+#poll_content #poll {color: #b10000; font-variant: small-caps; text-decoration: underline;}
+
+#poll_item_dl {clear: both; width: 100%;} 
+
+#poll_body {background-color: #00BC00; padding: 10px 10% 3px 20px; clear: both; border-bottom: 3px solid #006600; border-left: 1px solid #005500;}
+#poll_body #showresults {font-variant: small-caps;}
+#poll_body #sendvote {padding: 5px;}
+
+#poll_body #bar {display: block; padding: 5px; border-bottom: 2px solid #444; border-left: 1px solid #222;}
+#poll_body #bar.max_bar {background-color: red;}
+#poll_body #bar.normal_bar {background-color: white;}
+
+#poll_body #voters_count {text-align: right; border-top: 2px solid #333; margin: 25px; padding-top: 4px;}
+#poll_body #total {font-size: 1.5em;}

static/polls/js/common.js

+$(document).ready(function() {
+	var path_to_items = '#poll_content #poll_body input[name="poll_' + poll_pk + '"]';
+	var body = $('#poll_body');
+	
+	function showResults(data) {
+		var all_points = 0, total = 0, biggest = 0;
+		
+		body.hide('slow', complete=function() {
+			body.empty();
+			$.each(data, function(index, value) {
+				if(index != 'total') {
+					if(data[index]['count'] > biggest) {
+						biggest = data[index]['count'];
+					}
+					all_points += data[index]['count'];
+				}
+			});
+			
+			$.each(data, function(index, value) {
+				if(index == 'total') {
+					total = value;
+				} else {
+					var percentage = (data[index]['count'] * 100) / all_points;
+					var bar_class = 'normal_bar';
+					
+					if(data[index]['count'] == biggest) {
+						bar_class = 'max_bar';
+					}
+					
+					var points_txt = '';
+					if(show_points) {
+						points_txt = '<sup>(' + data[index]['count'] + ')</sup>'; 
+					}
+					
+					body.append('<dl id="poll_item_dl">');
+					body.append('<dt>' + data[index]['title'] + ': <strong>' + (Math.round(percentage*100)/100) + '%</strong>' + points_txt + '</dt>');
+					body.append('<dd><div id="bar" class="' + bar_class + '" style="width: ' + percentage + '%;"></div></dd>');
+					body.append('</dl>');
+				}
+			});
+			
+			var total_points_txt = '';
+			if(show_points) {
+				total_points_txt = '<sup>(' + all_points + ')</sup>'; 
+			}
+			
+			body.append('<div id="voters_count">' + trans_total_voters + ': <span id="total">' + total + '</span> ' + total_points_txt + '</div>');
+		}).show('slow');
+	}
+	
+	function RecognizeAndPrepare() {
+		var result = '';
+		
+		$(path_to_items).map(function() {
+			if((this.type == 'checkbox' || this.type == 'radio') && this.checked) {
+				result += '"' + this.id + '": ' + '"' + this.type + '",';
+			}
+			if(this.type == 'text' && this.value != '') {
+				result += '"' + this.id + '": ' + '"' + this.value + '",';
+			}
+		})
+		
+		if(result.length > 0)
+			if(result[result.length - 1] == ',')
+				result = result.substring(0, result.length - 1);
+		
+		return '{' + result + '}';
+	}
+	
+	$(path_to_items).bind('focusin click', function() {
+		if(poll_type == 'single') {
+			var selected = this;
+			
+			$(path_to_items).map(function() {
+				if(this != selected) {
+					if((this.type == 'checkbox' || this.type == 'radio') && this.checked) {
+						this.checked = false;
+					}
+					if(this.type == 'text' && this.value != '') {
+						this.value = '';
+					}
+				} 
+			});
+		}
+	});
+	
+	function doResults() {
+		$.get(result_url, function(data) {
+			showResults($.parseJSON(data));
+		});
+	}
+	
+	$("#sendvote").click(function() {
+		var result = RecognizeAndPrepare();
+		
+		if(result == '{}') {
+			alert(trans_select_something);
+		} else {
+			body.hide('slow', complete=function() {
+				$.get(vote_url, {'chosen_items': result}, function() {
+					doResults();
+				});
+			});
+		}
+	});
+	
+	$("#showresults").click(function() {
+		doResults();
+	});
+});