From 420e05bcbe66a7a8cb99a29a1c36d48494d0af0d Mon Sep 17 00:00:00 2001
From: Ben Adida <ben@adida.net>
Date: Thu, 30 Dec 2010 12:11:19 -0800
Subject: [PATCH] tweaked migration, started adding quarantined cast votes

---
 ...0002_v3_1_new_election_and_voter_fields.py |  3 ++-
 helios/models.py                              | 19 +++++++++++++++++++
 helios/tests.py                               | 13 +++++++++++++
 3 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/helios/migrations/0002_v3_1_new_election_and_voter_fields.py b/helios/migrations/0002_v3_1_new_election_and_voter_fields.py
index dd41fc6..e75643d 100644
--- a/helios/migrations/0002_v3_1_new_election_and_voter_fields.py
+++ b/helios/migrations/0002_v3_1_new_election_and_voter_fields.py
@@ -24,7 +24,8 @@ class Migration(SchemaMigration):
         db.add_column('helios_voter', 'voter_email', self.gf('django.db.models.fields.CharField')(max_length=250, null=True), keep_default=False)
 
         # Adding field 'Election.datatype'
-        db.add_column('helios_election', 'datatype', self.gf('django.db.models.fields.CharField')(default='2011/01/election', max_length=250), keep_default=False)
+        # manually tweaked default value to ensure proper datatype for older elections
+        db.add_column('helios_election', 'datatype', self.gf('django.db.models.fields.CharField')(default='legacy/election', max_length=250), keep_default=False)
 
         # Adding field 'Election.election_type'
         db.add_column('helios_election', 'election_type', self.gf('django.db.models.fields.CharField')(default='election', max_length=250), keep_default=False)
diff --git a/helios/models.py b/helios/models.py
index ba0e3a1..1150564 100644
--- a/helios/models.py
+++ b/helios/models.py
@@ -87,6 +87,13 @@ class Election(models.Model, electionalgs.Election):
   registration_starts_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
   voting_starts_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
   voting_ends_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
+
+  # if this is non-null, then a complaint period, where people can cast a quarantined ballot.
+  # we do NOT call this a "provisional" ballot, since provisional implies that the voter has not
+  # been qualified. We may eventually add this, but it can't be in the same CastVote table, which
+  # is tied to a voter.
+  complaint_period_ends_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
+
   tallying_starts_at = models.DateTimeField(auto_now_add=False, default=None, null=True)
   
   # dates when things were forced to be performed
@@ -730,6 +737,10 @@ class CastVote(models.Model, electionalgs.CastVote):
 
   cast_at = models.DateTimeField(auto_now_add=True)
 
+  # some ballots can be quarantined (this is not the same thing as provisional)
+  quarantined_p = modelsBooleanField(default=False, null=False)
+  released_from_quarantine_at = models.DateTimeField(auto_now_add=False, null=True)
+
   # when is the vote verified?
   verified_at = models.DateTimeField(null=True)
   invalidated_at = models.DateTimeField(null=True)
@@ -742,6 +753,10 @@ class CastVote(models.Model, electionalgs.CastVote):
   def voter_hash(self):
     return self.voter.hash
 
+  @property
+  def is_quarantined(self):
+    return self.quarantined_p and not self.released_from_quarantine_at
+
   def set_tinyhash(self):
     """
     find a tiny version of the hash for a URL slug.
@@ -774,6 +789,10 @@ class CastVote(models.Model, electionalgs.CastVote):
     return cls.objects.filter(voter = voter).order_by('-cast_at')
 
   def verify_and_store(self):
+    # if it's quarantined, don't let this go through
+    if self.is_quarantined:
+      raise Exception("cast vote is quarantined, verification and storage is delayed.")
+
     result = self.vote.verify(self.voter.election)
 
     if result:
diff --git a/helios/tests.py b/helios/tests.py
index 17d123f..078b914 100644
--- a/helios/tests.py
+++ b/helios/tests.py
@@ -200,6 +200,19 @@ class VoterModelTests(TestCase):
         # check that you can get at the voter user structure
         self.assertEquals(v.user.user_id, v.voter_email)
 
+
+class CastVoteModelTests(TestCase):
+    fixtures = ['users.json', 'election.json']
+
+    def setUp(self):
+        self.election = models.Election.objects.get(short_name='test')
+        self.user = auth_models.User.objects.get(user_id='ben@adida.net', user_type='google')
+
+        # register the voter
+        self.voter = models.Voter.register_user_in_election(self.user, self.election)
+
+    def test_cast_vote(self):
+        assert False
 ##
 ## Black box tests
 ##
-- 
GitLab