diff --git a/.project b/.project
new file mode 100644
index 0000000..b1c4236
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ django-polls
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..3d64837
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,8 @@
+
+
+
+/${PROJECT_DIR_NAME}
+
+python 2.7
+django-polls
+
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..385eb37
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+encoding//polls/migrations/0001_initial.py=utf-8
+encoding//polls/south_migrations/0001_initial.py=utf-8
+encoding//polls/south_migrations/0002_auto__add_vote__add_field_poll_description.py=utf-8
+encoding//polls/south_migrations/0003_auto__add_unique_vote_poll_user.py=utf-8
+encoding//polls/south_migrations/0004_auto__add_field_vote_comment__add_field_vote_datetime__add_field_vote_.py=utf-8
+encoding//polls/south_migrations/0005_auto__chg_field_vote_user__del_unique_vote_user_poll.py=utf-8
+encoding//polls/south_migrations/0006_auto__del_field_vote_datetime__add_field_vote_created.py=utf-8
+encoding//polls/south_migrations/0007_auto__add_unique_vote_user_poll_choice__chg_field_poll_reference__add_.py=utf-8
+encoding//polls/south_migrations/0008_auto__add_field_choice_code.py=utf-8
+encoding//polls/south_migrations/0009_auto__add_unique_choice_poll_code.py=utf-8
diff --git a/polls/__init__.py b/polls/__init__.py
index b794fd4..1843c5a 100644
--- a/polls/__init__.py
+++ b/polls/__init__.py
@@ -1 +1 @@
-__version__ = '0.1.0'
+__version__ = '0.2.5dev'
diff --git a/polls/admin.py b/polls/admin.py
index 69ef318..ea1df5f 100644
--- a/polls/admin.py
+++ b/polls/admin.py
@@ -16,7 +16,8 @@ class PollAdmin(admin.ModelAdmin):
class VoteAdmin(admin.ModelAdmin):
model = Vote
- list_display = ('choice', 'user', 'poll')
+ list_display = ('choice', 'user', 'poll', 'created')
+ readonly_fields = ('created',)
admin.site.register(Poll, PollAdmin)
admin.site.register(Vote, VoteAdmin)
diff --git a/polls/api.py b/polls/api.py
new file mode 100644
index 0000000..58c572f
--- /dev/null
+++ b/polls/api.py
@@ -0,0 +1,216 @@
+'''
+ Api("v1/poll")
+ POST /poll/ -- create a new poll, shall allow to post choices in the same API call
+ POST /choice/ -- add a choice to an existing poll
+ POST /vote/ -- vote on poll with pk
+ PUT /choice/ -- update choice data
+ PUT /poll/ -- update poll data
+ GET /poll/ -- retrieve the poll information, including choice details
+ GET /result/ -- retrieve the statistics on the poll.
+ This shall return a JSON formatted like so. Note the actual statistics calculation shall be implemented
+ in poll.service.stats (later on, this will be externalized into a batch job).
+'''
+
+from exceptions import PollClosed, PollNotOpen, PollNotAnonymous, PollNotMultiple
+import json
+
+from django.conf.urls import url
+from django.contrib.auth import get_user_model
+from django.core.urlresolvers import resolve
+from django.forms.models import model_to_dict
+from tastypie import fields
+from tastypie import http
+from tastypie.authentication import MultiAuthentication, BasicAuthentication, SessionAuthentication, \
+ Authentication
+from tastypie.authorization import Authorization, \
+ DjangoAuthorization
+from tastypie.exceptions import ImmediateHttpResponse
+from tastypie.resources import ALL, NamespacedModelResource
+
+from polls.exceptions import PollInvalidChoice
+from polls.models import Poll, Choice, Vote
+from polls.util import ReasonableDjangoAuthorization, IPAuthentication
+
+
+class UserResource(NamespacedModelResource):
+
+ class Meta:
+ queryset = get_user_model().objects.all()
+ allowed_methods = ['get']
+ resource_name = 'user'
+ always_return_data = True
+ authentication = MultiAuthentication(
+ BasicAuthentication(), SessionAuthentication())
+ authorization = ReasonableDjangoAuthorization(read_detail='')
+ excludes = ['date_joined', 'password', 'is_superuser',
+ 'is_staff', 'is_active', 'last_login', 'first_name', 'last_name']
+ filtering = {
+ 'username': ALL,
+ }
+
+ def limit_list_by_user(self, request, object_list):
+ """
+ limit the request object list to its own profile, except
+ for superusers. Superusers get a list of all users
+
+ note that for POST requests tastypie internally
+ queries get_object_list, and we should return a valid
+ list
+ """
+ view, args, kwargs = resolve(request.path)
+ if request.method == 'GET' and not 'pk' in kwargs and not request.user.is_superuser:
+ return object_list.filter(pk=request.user.pk)
+ return object_list
+
+ def get_object_list(self, request):
+ object_list = super(UserResource, self).get_object_list(request)
+ object_list = self.limit_list_by_user(request, object_list)
+ return object_list
+
+
+class PollResource(NamespacedModelResource):
+ # POST, GET, PUT
+ # user = fields.ForeignKey(UserResource, 'user')
+
+ class Meta:
+ queryset = Poll.objects.all()
+ allowed_methods = ['get', 'post', 'put']
+ resource_name = 'poll'
+ always_return_data = True
+ # anyone can list and get polls, otherwise Django auth kicks in
+ authentication = MultiAuthentication(
+ BasicAuthentication(), SessionAuthentication(), Authentication())
+ authorization = ReasonableDjangoAuthorization(read_list='',
+ read_detail='')
+ filtering = {
+ 'reference': 'exact',
+ }
+
+ def obj_create(self, bundle, **kwargs):
+ return super(PollResource, self).obj_create(bundle, user=bundle.request.user)
+
+ def dehydrate(self, bundle):
+ choices = Choice.objects.filter(poll=bundle.data['id'])
+ bundle.data['choices'] = [model_to_dict(choice) for choice in choices]
+ return bundle
+
+ def alter_detail_data_to_serialize(self, request, data):
+ data.data['already_voted'] = Poll.objects.get(
+ pk=data.data.get('id')).already_voted(user=request.user)
+ return data
+
+ def prepend_urls(self):
+ """ match by pk or reference """
+ return [
+ url(r"^(?P%s)/(?P[0-9]+)/$" % self._meta.resource_name,
+ self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
+ url(r"^(?P%s)/(?P[\w-]+)/$" % self._meta.resource_name,
+ self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
+ ]
+
+
+class ChoiceResource(NamespacedModelResource):
+ poll = fields.ToOneField(PollResource, 'poll')
+
+ class Meta:
+ queryset = Choice.objects.all()
+ allowed_methods = ['post', 'put']
+ authentication = MultiAuthentication(
+ BasicAuthentication(), SessionAuthentication())
+ authorization = DjangoAuthorization()
+ resource_name = 'choice'
+ always_return_data = True
+
+
+class VoteResource(NamespacedModelResource):
+ user = fields.ToOneField(
+ UserResource, 'user', blank=True, null=True, readonly=True)
+ choice = fields.ToOneField(ChoiceResource, 'choice', readonly=True)
+ poll = fields.ToOneField(PollResource, 'poll', readonly=True)
+
+ class Meta:
+ queryset = Vote.objects.all()
+ allowed_methods = ['post', 'put']
+ # by default require authentication but regress for anonymous votes
+ authentication = IPAuthentication(BasicAuthentication(),
+ SessionAuthentication(),
+ Authentication())
+ # anyone can vote
+ authorization = Authorization()
+ resource_name = 'vote'
+ always_return_data = True
+
+ def obj_create(self, bundle, **kwargs):
+ poll = PollResource().get_via_uri(bundle.data.get('poll'))
+ if not poll.already_voted(bundle.request.user):
+ try:
+ choices = bundle.data.get('choice')
+ # convert single-choice into list
+ if isinstance(choices, basestring):
+ choices = [choices]
+ votes = poll.vote(choices=choices,
+ data=bundle.data.get('data'),
+ user=bundle.request.user,
+ comment=bundle.data.get('comment'))
+ except (PollClosed, PollNotOpen, PollNotAnonymous, PollNotMultiple):
+ raise ImmediateHttpResponse(
+ response=http.HttpForbidden('not allowed'))
+ except PollInvalidChoice:
+ raise ImmediateHttpResponse(
+ response=http.HttpBadRequest('invalid data'))
+ else:
+ bundle.obj = votes[0]
+ else:
+ raise ImmediateHttpResponse(
+ response=http.HttpForbidden('already voted'))
+ return bundle
+
+ def obj_update(self, bundle, **kwargs):
+ poll = PollResource().get_via_uri(bundle.data.get('poll'))
+ # non anonymous votes by the same user can be modified
+ if not poll.is_anonymous and bundle.obj.user == bundle.request.user:
+ bundle.obj.change_vote(choices=bundle.data.get('choice'),
+ data=bundle.data.get('data'),
+ user=bundle.request.user)
+ else:
+ raise ImmediateHttpResponse(
+ response=http.HttpForbidden('already voted'))
+
+ def dehydrate(self, bundle):
+ # convert JSON Field
+ bundle = super(VoteResource, self).dehydrate(bundle)
+ bundle.data['data'] = json.dumps(bundle.obj.data)
+ # represent values as strings
+ bundle.data['poll'] = self.get_resource_uri(bundle.obj.poll)
+ bundle.data['resource_uri'] = self.get_resource_uri(bundle.obj)
+ bundle.data['choice'] = bundle.obj.choice.code
+ return bundle
+
+
+class ResultResource(NamespacedModelResource):
+
+ class Meta:
+ queryset = Poll.objects.all()
+ allowed_methods = ['get']
+ # anyone can get results
+ authentication = MultiAuthentication(
+ BasicAuthentication(), SessionAuthentication(), Authentication())
+ authorization = Authorization()
+ resource_name = 'result'
+ always_return_data = True
+ excludes = ['description', 'start_votes', 'end_votes',
+ 'is_anonymous', 'is_multiple', 'is_closed', 'reference']
+
+ def prepend_urls(self):
+ """ match by pk or reference """
+ return [
+ url(r"^(?P%s)/(?P[0-9]+)/$" % self._meta.resource_name,
+ self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
+ url(r"^(?P%s)/(?P[\w-]+)/$" % self._meta.resource_name,
+ self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
+ ]
+
+ def dehydrate(self, bundle):
+ poll = bundle.obj
+ bundle.data['stats'] = poll.get_stats()
+ return bundle
diff --git a/polls/exceptions.py b/polls/exceptions.py
new file mode 100644
index 0000000..74c2aa3
--- /dev/null
+++ b/polls/exceptions.py
@@ -0,0 +1,6 @@
+class PollNotOpen(Exception): pass
+class PollClosed(Exception): pass
+class PollNotAnonymous(Exception): pass
+class PollNotMultiple(Exception): pass
+class PollChoiceRequired(Exception): pass
+class PollInvalidChoice(Exception): pass
diff --git a/polls/migrations/0001_initial.py b/polls/migrations/0001_initial.py
index ef2824f..559b5aa 100644
--- a/polls/migrations/0001_initial.py
+++ b/polls/migrations/0001_initial.py
@@ -1,49 +1,79 @@
# -*- coding: utf-8 -*-
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
+from __future__ import unicode_literals
+from django.db import models, migrations
+import polls.models
+import django.utils.timezone
+from django.conf import settings
+import django_extensions.db.fields.json
+import uuid
-class Migration(SchemaMigration):
- def forwards(self, orm):
- # Adding model 'Poll'
- db.create_table('polls_poll', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('question', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ))
- db.send_create_signal('polls', ['Poll'])
+class Migration(migrations.Migration):
- # Adding model 'Choice'
- db.create_table('polls_choice', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('poll', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['polls.Poll'])),
- ('choice', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ))
- db.send_create_signal('polls', ['Choice'])
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
-
- def backwards(self, orm):
- # Deleting model 'Poll'
- db.delete_table('polls_poll')
-
- # Deleting model 'Choice'
- db.delete_table('polls_choice')
-
-
- models = {
- 'polls.choice': {
- 'Meta': {'object_name': 'Choice'},
- 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['polls.Poll']"})
- },
- 'polls.poll': {
- 'Meta': {'object_name': 'Poll'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- }
- }
-
- complete_apps = ['polls']
\ No newline at end of file
+ operations = [
+ migrations.CreateModel(
+ name='Choice',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('choice', models.CharField(max_length=255)),
+ ('code', models.CharField(default=b'', max_length=36, blank=True)),
+ ],
+ options={
+ 'ordering': ['poll', 'choice'],
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Poll',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('question', models.CharField(max_length=255)),
+ ('description', models.TextField(blank=True)),
+ ('reference', models.CharField(default=uuid.uuid4, unique=True, max_length=36)),
+ ('is_anonymous', models.BooleanField(default=False, help_text='Allow to vote for anonymous user')),
+ ('is_multiple', models.BooleanField(default=False, help_text='Allow to make multiple choices')),
+ ('is_closed', models.BooleanField(default=False, help_text='Do not accept votes')),
+ ('start_votes', models.DateTimeField(default=django.utils.timezone.now, help_text='The earliest time votes get accepted')),
+ ('end_votes', models.DateTimeField(default=polls.models.vote_endtime, help_text='The latest time votes get accepted')),
+ ],
+ options={
+ 'ordering': ['-start_votes'],
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Vote',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('comment', models.TextField(max_length=144, null=True, blank=True)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('data', django_extensions.db.fields.json.JSONField(null=True, blank=True)),
+ ('choice', models.ForeignKey(to='polls.Choice')),
+ ('poll', models.ForeignKey(to='polls.Poll')),
+ ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)),
+ ],
+ options={
+ 'ordering': ['poll', 'choice'],
+ },
+ bases=(models.Model,),
+ ),
+ migrations.AlterUniqueTogether(
+ name='vote',
+ unique_together=set([('user', 'poll', 'choice')]),
+ ),
+ migrations.AddField(
+ model_name='choice',
+ name='poll',
+ field=models.ForeignKey(to='polls.Poll'),
+ preserve_default=True,
+ ),
+ migrations.AlterUniqueTogether(
+ name='choice',
+ unique_together=set([('poll', 'code')]),
+ ),
+ ]
diff --git a/polls/migrations/0002_poll_allow_multi_votes.py b/polls/migrations/0002_poll_allow_multi_votes.py
new file mode 100644
index 0000000..af01bd0
--- /dev/null
+++ b/polls/migrations/0002_poll_allow_multi_votes.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('polls', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='poll',
+ name='allow_multi_votes',
+ field=models.BooleanField(default=False, help_text='Allow multiple votes by same user'),
+ preserve_default=True,
+ ),
+ ]
diff --git a/polls/migrations/0003_auto_20160424_1140.py b/polls/migrations/0003_auto_20160424_1140.py
new file mode 100644
index 0000000..d0c4f1b
--- /dev/null
+++ b/polls/migrations/0003_auto_20160424_1140.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('polls', '0002_poll_allow_multi_votes'),
+ ]
+
+ operations = [
+ migrations.AlterUniqueTogether(
+ name='vote',
+ unique_together=set([]),
+ ),
+ ]
diff --git a/polls/models.py b/polls/models.py
index 3f6d9b9..bdecbed 100644
--- a/polls/models.py
+++ b/polls/models.py
@@ -1,30 +1,155 @@
-from django.db import models
+from datetime import timedelta
+from exceptions import PollClosed, PollNotOpen, PollNotAnonymous, PollNotMultiple
+from uuid import uuid4
+
from django.contrib.auth.models import User
+from django.db import models
+from django.utils import timezone
+from django.utils.text import slugify
+from django.utils.translation import ugettext_lazy as _
+from django_extensions.db.fields.json import JSONField
+
+from polls.exceptions import PollChoiceRequired, PollInvalidChoice
+
+
+def vote_endtime():
+ return timezone.now() + timedelta(days=5)
class Poll(models.Model):
question = models.CharField(max_length=255)
description = models.TextField(blank=True)
+ reference = models.CharField(max_length=36, default=uuid4, unique=True)
+ is_anonymous = models.BooleanField(
+ default=False, help_text=_('Allow to vote for anonymous user'))
+ is_multiple = models.BooleanField(
+ default=False, help_text=_('Allow to make multiple choices'))
+ is_closed = models.BooleanField(
+ default=False, help_text=_('Do not accept votes'))
+ allow_multi_votes = models.BooleanField(
+ default=False, help_text=_('Allow multiple votes by same user'))
+ start_votes = models.DateTimeField(
+ default=timezone.now, help_text=_('The earliest time votes get accepted'))
+ end_votes = models.DateTimeField(default=vote_endtime,
+ help_text=_('The latest time votes get accepted'))
+
+ def vote(self, choices, user=None, data=None, comment=None):
+ current_time = timezone.now()
+ if self.is_closed:
+ raise PollClosed
+ if current_time < self.start_votes or current_time > self.end_votes:
+ raise PollNotOpen
+ if user is None and not self.is_anonymous:
+ raise PollNotAnonymous
+ if not choices:
+ raise PollInvalidChoice
+ if len(choices) > 1 and not self.is_multiple:
+ raise PollNotMultiple
+ if len(choices) == 0:
+ raise PollChoiceRequired
+ # if self.is_anonymous: user = None # pass None, even though user is
+ # authenticated
+ votes = []
+ for choice_id in choices:
+ if isinstance(choice_id, int) or choice_id.isdigit():
+ query = dict(pk=choice_id)
+ else:
+ query = dict(poll=self, code=choice_id)
+ try:
+ choice = Choice.objects.get(**query)
+ except:
+ raise PollInvalidChoice
+ # we always track the technical user at least by ip or clientid
+ # to make sure we don't get multiple votes
+ #if self.is_anonymous:
+ # user = None
+ vote = Vote.objects.create(poll=self, user=user,
+ choice=choice, data=data,
+ comment=comment)
+ votes.append(vote)
+ return votes
+
+ def change_vote(self, choices, user=None, data=None):
+ """
+ this deletes all previous votes of the user and revotes with
+ new choices.
+ """
+ votes = self.vote_set.filter(user=user).delete()
+ self.vote(choices, user=user, data=data)
+ return votes
def count_choices(self):
return self.choice_set.count()
- def count_total_votes(self):
- result = 0
+ def count_percentage(self, as_code=False):
+ """
+ return a dict of choices and percentages
+ {
+ : percentage
+ (...)
+ }
+ """
+ total_votes = self.count_total_votes()
+ stats = {}
for choice in self.choice_set.all():
- result += choice.count_votes()
- return result
+ key = choice.code if as_code else choice
+ stats[key] = float(choice.count_votes()) / total_votes
+ return stats
+
+ def count_total_votes(self):
+ votes = sum((choice.count_votes() for choice in self.choice_set.all()))
+ return votes
+
+ def get_stats(self):
+ """
+ return a statistics object
- def can_vote(self, user):
- return not self.vote_set.filter(user=user).exists()
+ returns a dict of
+ {
+ labels : [choice, ...],
+ codes : [code, ...],
+ percentage : [%, ...],
+ }
+ """
+ # get statistics as dict of { choice : pct }
+ stats = self.count_percentage()
+ # convert to same indexed labels, codes, percentages
+ labels = []
+ codes = []
+ percentage = []
+ for c, p in stats.iteritems():
+ labels.append(c.choice)
+ codes.append(c.code)
+ percentage.append(p)
+ count = self.count_total_votes()
+ stats = dict(values=percentage, codes=codes,
+ labels=labels, votes=count)
+ return stats
+
+ def already_voted(self, user):
+ if not self.is_anonymous:
+ if user.is_anonymous():
+ raise PollNotAnonymous
+ if self.allow_multi_votes:
+ # if we allow multiple votes, we don't care how many
+ # votes this user has already vote
+ return False
+ return self.vote_set.filter(user=user).exists()
def __unicode__(self):
return self.question
+ class Meta:
+ ordering = ['-start_votes']
+
class Choice(models.Model):
+ #: poll reference
poll = models.ForeignKey(Poll)
+ #: label field
choice = models.CharField(max_length=255)
+ #: code as an alternative to id
+ code = models.CharField(max_length=36, default='', blank=True)
def count_votes(self):
return self.vote_set.count()
@@ -32,17 +157,26 @@ def count_votes(self):
def __unicode__(self):
return self.choice
+ def save(self, *args, **kwargs):
+ if not self.code:
+ self.code = slugify(unicode(self.choice))
+ super(Choice, self).save(*args, **kwargs)
+
class Meta:
- ordering = ['choice']
+ unique_together = (('poll', 'code'),)
+ ordering = ['poll', 'choice']
class Vote(models.Model):
- user = models.ForeignKey(User)
+ user = models.ForeignKey(User, blank=True, null=True)
poll = models.ForeignKey(Poll)
choice = models.ForeignKey(Choice)
+ comment = models.TextField(max_length=144, blank=True, null=True)
+ created = models.DateTimeField(auto_now_add=True)
+ data = JSONField(blank=True, null=True)
def __unicode__(self):
- return u'Vote for %s' % (self.choice)
+ return u'Vote for %s' % self.choice
class Meta:
- unique_together = (('user', 'poll'))
+ ordering = ['poll', 'choice']
diff --git a/polls/south_migrations/0001_initial.py b/polls/south_migrations/0001_initial.py
new file mode 100644
index 0000000..ef2824f
--- /dev/null
+++ b/polls/south_migrations/0001_initial.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Poll'
+ db.create_table('polls_poll', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('question', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ))
+ db.send_create_signal('polls', ['Poll'])
+
+ # Adding model 'Choice'
+ db.create_table('polls_choice', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('poll', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['polls.Poll'])),
+ ('choice', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ))
+ db.send_create_signal('polls', ['Choice'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'Poll'
+ db.delete_table('polls_poll')
+
+ # Deleting model 'Choice'
+ db.delete_table('polls_choice')
+
+
+ models = {
+ 'polls.choice': {
+ 'Meta': {'object_name': 'Choice'},
+ 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['polls.Poll']"})
+ },
+ 'polls.poll': {
+ 'Meta': {'object_name': 'Poll'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ }
+ }
+
+ complete_apps = ['polls']
\ No newline at end of file
diff --git a/polls/migrations/0002_auto__add_vote__add_field_poll_description.py b/polls/south_migrations/0002_auto__add_vote__add_field_poll_description.py
similarity index 100%
rename from polls/migrations/0002_auto__add_vote__add_field_poll_description.py
rename to polls/south_migrations/0002_auto__add_vote__add_field_poll_description.py
diff --git a/polls/migrations/0003_auto__add_unique_vote_poll_user.py b/polls/south_migrations/0003_auto__add_unique_vote_poll_user.py
similarity index 100%
rename from polls/migrations/0003_auto__add_unique_vote_poll_user.py
rename to polls/south_migrations/0003_auto__add_unique_vote_poll_user.py
diff --git a/polls/south_migrations/0004_auto__add_field_vote_comment__add_field_vote_datetime__add_field_vote_.py b/polls/south_migrations/0004_auto__add_field_vote_comment__add_field_vote_datetime__add_field_vote_.py
new file mode 100644
index 0000000..8cd1b1d
--- /dev/null
+++ b/polls/south_migrations/0004_auto__add_field_vote_comment__add_field_vote_datetime__add_field_vote_.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Vote.comment'
+ db.add_column(u'polls_vote', 'comment',
+ self.gf('django.db.models.fields.TextField')(max_length=144, null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'Vote.datetime'
+ db.add_column(u'polls_vote', 'datetime',
+ self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default=datetime.datetime(2015, 2, 19, 0, 0), blank=True),
+ keep_default=False)
+
+ # Adding field 'Vote.data'
+ db.add_column(u'polls_vote', 'data',
+ self.gf('django.db.models.fields.TextField')(default='{}', null=True, blank=True),
+ keep_default=False)
+
+ # Adding field 'Poll.is_closed'
+ db.add_column(u'polls_poll', 'is_closed',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'Poll.reference'
+ db.add_column(u'polls_poll', 'reference',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=36, blank=True, unique=True),
+ keep_default=False)
+ # mysql error:
+ # Duplicate index 'polls_poll_reference_4cacbf22888a7509_uniq' defined on the table 'polls.polls_poll
+ #db.create_unique(u'polls_poll', ['reference'])
+
+ # Adding field 'Poll.is_multiple'
+ db.add_column(u'polls_poll', 'is_multiple',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'Poll.is_anonymous'
+ db.add_column(u'polls_poll', 'is_anonymous',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ # Adding field 'Poll.start_votes'
+ db.add_column(u'polls_poll', 'start_votes',
+ self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2015, 2, 19, 0, 0)),
+ keep_default=False)
+
+ # Adding field 'Poll.end_votes'
+ db.add_column(u'polls_poll', 'end_votes',
+ self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2015, 2, 24, 0, 0)),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Vote.comment'
+ db.delete_column(u'polls_vote', 'comment')
+
+ # Deleting field 'Vote.datetime'
+ db.delete_column(u'polls_vote', 'datetime')
+
+ # Deleting field 'Vote.data'
+ db.delete_column(u'polls_vote', 'data')
+
+ # Deleting field 'Poll.is_closed'
+ db.delete_column(u'polls_poll', 'is_closed')
+
+ # Deleting field 'Poll.reference'
+ db.delete_column(u'polls_poll', 'reference')
+ #db.delete_unique(u'polls_poll', ['reference'])
+
+ # Deleting field 'Poll.is_multiple'
+ db.delete_column(u'polls_poll', 'is_multiple')
+
+ # Deleting field 'Poll.is_anonymous'
+ db.delete_column(u'polls_poll', 'is_anonymous')
+
+ # Deleting field 'Poll.start_votes'
+ db.delete_column(u'polls_poll', 'start_votes')
+
+ # Deleting field 'Poll.end_votes'
+ db.delete_column(u'polls_poll', 'end_votes')
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'polls.choice': {
+ 'Meta': {'ordering': "['choice']", 'object_name': 'Choice'},
+ 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"})
+ },
+ u'polls.poll': {
+ 'Meta': {'ordering': "['-start_votes']", 'object_name': 'Poll'},
+ 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'end_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 2, 24, 0, 0)'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '36', 'blank': 'True', 'unique': 'True'}),
+ 'start_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 2, 19, 0, 0)'})
+ },
+ u'polls.vote': {
+ 'Meta': {'unique_together': "(('user', 'poll'),)", 'object_name': 'Vote'},
+ 'choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Choice']"}),
+ 'comment': ('django.db.models.fields.TextField', [], {'max_length': '144', 'null': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'null': 'True', 'blank': 'True'}),
+ 'datetime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+ }
+ }
+
+ complete_apps = ['polls']
\ No newline at end of file
diff --git a/polls/south_migrations/0005_auto__chg_field_vote_user__del_unique_vote_user_poll.py b/polls/south_migrations/0005_auto__chg_field_vote_user__del_unique_vote_user_poll.py
new file mode 100644
index 0000000..4c8c9cc
--- /dev/null
+++ b/polls/south_migrations/0005_auto__chg_field_vote_user__del_unique_vote_user_poll.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Removing unique constraint on 'Vote', fields ['user', 'poll']
+ db.delete_unique(u'polls_vote', ['user_id', 'poll_id'])
+
+
+ # Changing field 'Vote.user'
+ db.alter_column(u'polls_vote', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True))
+
+ def backwards(self, orm):
+
+ # User chose to not deal with backwards NULL issues for 'Vote.user'
+ raise RuntimeError("Cannot reverse this migration. 'Vote.user' and its values cannot be restored.")
+
+ # The following code is provided here to aid in writing a correct migration
+ # Changing field 'Vote.user'
+ db.alter_column(u'polls_vote', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User']))
+ # Adding unique constraint on 'Vote', fields ['user', 'poll']
+ db.create_unique(u'polls_vote', ['user_id', 'poll_id'])
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'polls.choice': {
+ 'Meta': {'ordering': "['choice']", 'object_name': 'Choice'},
+ 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"})
+ },
+ u'polls.poll': {
+ 'Meta': {'ordering': "['-start_votes']", 'object_name': 'Poll'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'end_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 2, 25, 0, 0)'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+ 'start_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 2, 20, 0, 0)'})
+ },
+ u'polls.vote': {
+ 'Meta': {'object_name': 'Vote'},
+ 'choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Choice']"}),
+ 'comment': ('django.db.models.fields.TextField', [], {'max_length': '144', 'null': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'null': 'True', 'blank': 'True'}),
+ 'datetime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['polls']
\ No newline at end of file
diff --git a/polls/south_migrations/0006_auto__del_field_vote_datetime__add_field_vote_created.py b/polls/south_migrations/0006_auto__del_field_vote_datetime__add_field_vote_created.py
new file mode 100644
index 0000000..1e11ab3
--- /dev/null
+++ b/polls/south_migrations/0006_auto__del_field_vote_datetime__add_field_vote_created.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ db.rename_column(u'polls_vote', 'datetime', 'created')
+
+ def backwards(self, orm):
+ db.rename_column(u'polls_vote', 'created', 'datetime')
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'polls.choice': {
+ 'Meta': {'object_name': 'Choice'},
+ 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"})
+ },
+ u'polls.poll': {
+ 'Meta': {'ordering': "['-start_votes']", 'object_name': 'Poll'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'end_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 3, 1, 0, 0)'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'reference': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+ 'start_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 2, 24, 0, 0)'})
+ },
+ u'polls.vote': {
+ 'Meta': {'object_name': 'Vote'},
+ 'choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Choice']"}),
+ 'comment': ('django.db.models.fields.TextField', [], {'max_length': '144', 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['polls']
\ No newline at end of file
diff --git a/polls/south_migrations/0007_auto__add_unique_vote_user_poll_choice__chg_field_poll_reference__add_.py b/polls/south_migrations/0007_auto__add_unique_vote_user_poll_choice__chg_field_poll_reference__add_.py
new file mode 100644
index 0000000..4ac9ee3
--- /dev/null
+++ b/polls/south_migrations/0007_auto__add_unique_vote_user_poll_choice__chg_field_poll_reference__add_.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding unique constraint on 'Vote', fields ['user', 'poll', 'choice']
+ db.create_unique(u'polls_vote', ['user_id', 'poll_id', 'choice_id'])
+
+
+ # Changing field 'Poll.reference'
+ db.alter_column(u'polls_poll', 'reference', self.gf('django.db.models.fields.CharField')(unique=True, max_length=36))
+ # Adding unique constraint on 'Poll', fields ['reference']
+ # mysql error:
+ # Duplicate index 'polls_poll_reference_4cacbf22888a7509_uniq' defined on the table 'polls.polls_poll
+ # db.create_unique(u'polls_poll', ['reference'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'Poll', fields ['reference']
+ #db.delete_unique(u'polls_poll', ['reference'])
+
+ # Removing unique constraint on 'Vote', fields ['user', 'poll', 'choice']
+ db.delete_unique(u'polls_vote', ['user_id', 'poll_id', 'choice_id'])
+
+
+ # Changing field 'Poll.reference'
+ db.alter_column(u'polls_poll', 'reference', self.gf('django.db.models.fields.CharField')(max_length=20))
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'polls.choice': {
+ 'Meta': {'object_name': 'Choice'},
+ 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"})
+ },
+ u'polls.poll': {
+ 'Meta': {'ordering': "['-start_votes']", 'object_name': 'Poll'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'end_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 3, 8, 0, 0)'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'reference': ('django.db.models.fields.CharField', [], {'default': "'fcfa941c-9127-4a96-aff8-437d3cd35fea'", 'unique': 'True', 'max_length': '36'}),
+ 'start_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 3, 3, 0, 0)'})
+ },
+ u'polls.vote': {
+ 'Meta': {'unique_together': "(('user', 'poll', 'choice'),)", 'object_name': 'Vote'},
+ 'choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Choice']"}),
+ 'comment': ('django.db.models.fields.TextField', [], {'max_length': '144', 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['polls']
\ No newline at end of file
diff --git a/polls/south_migrations/0008_auto__add_field_choice_code.py b/polls/south_migrations/0008_auto__add_field_choice_code.py
new file mode 100644
index 0000000..bbd834d
--- /dev/null
+++ b/polls/south_migrations/0008_auto__add_field_choice_code.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+from uuid import UUID
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Choice.code'
+ db.add_column(u'polls_choice', 'code',
+ self.gf('django.db.models.fields.CharField')(default='', max_length=36, blank=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'Choice.code'
+ db.delete_column(u'polls_choice', 'code')
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'polls.choice': {
+ 'Meta': {'object_name': 'Choice'},
+ 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'code': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '36', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"})
+ },
+ u'polls.poll': {
+ 'Meta': {'ordering': "['-start_votes']", 'object_name': 'Poll'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'end_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 3, 21, 0, 0)'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'reference': ('django.db.models.fields.CharField', [], {'default': "UUID('5e0cdc46-d6fe-43fd-9849-b66ec15ecdb6')", 'unique': 'True', 'max_length': '36'}),
+ 'start_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
+ },
+ u'polls.vote': {
+ 'Meta': {'unique_together': "(('user', 'poll', 'choice'),)", 'object_name': 'Vote'},
+ 'choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Choice']"}),
+ 'comment': ('django.db.models.fields.TextField', [], {'max_length': '144', 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['polls']
\ No newline at end of file
diff --git a/polls/south_migrations/0009_auto__add_unique_choice_poll_code.py b/polls/south_migrations/0009_auto__add_unique_choice_poll_code.py
new file mode 100644
index 0000000..bb484bd
--- /dev/null
+++ b/polls/south_migrations/0009_auto__add_unique_choice_poll_code.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+from uuid import UUID
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding unique constraint on 'Choice', fields ['poll', 'code']
+ db.create_unique(u'polls_choice', ['poll_id', 'code'])
+
+
+ def backwards(self, orm):
+ # Removing unique constraint on 'Choice', fields ['poll', 'code']
+ db.delete_unique(u'polls_choice', ['poll_id', 'code'])
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'polls.choice': {
+ 'Meta': {'unique_together': "(('poll', 'code'),)", 'object_name': 'Choice'},
+ 'choice': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'code': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '36', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"})
+ },
+ u'polls.poll': {
+ 'Meta': {'ordering': "['-start_votes']", 'object_name': 'Poll'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'end_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 3, 23, 0, 0)'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'question': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'reference': ('django.db.models.fields.CharField', [], {'default': "UUID('bdc7cb0d-e3a6-4e0e-8211-bdda001c68ba')", 'unique': 'True', 'max_length': '36'}),
+ 'start_votes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
+ },
+ u'polls.vote': {
+ 'Meta': {'unique_together': "(('user', 'poll', 'choice'),)", 'object_name': 'Vote'},
+ 'choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Choice']"}),
+ 'comment': ('django.db.models.fields.TextField', [], {'max_length': '144', 'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'data': ('django.db.models.fields.TextField', [], {'default': "'{}'", 'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'poll': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['polls.Poll']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['polls']
\ No newline at end of file
diff --git a/polls/south_migrations/__init__.py b/polls/south_migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/polls/templates/polls/poll_detail.html b/polls/templates/polls/poll_detail.html
index de49e57..18ae114 100644
--- a/polls/templates/polls/poll_detail.html
+++ b/polls/templates/polls/poll_detail.html
@@ -1,12 +1,11 @@
{% extends "base.html" %}
{% load i18n %}
-{% load polls %}
{% block content %}
{{poll.question}}
{% if poll.votable %}
-