summaryrefslogtreecommitdiff
path: root/postgresqleu/elections/forms.py
blob: da111f90a6499d23dd26dd539bd434126d3cb659 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from django import forms
from django.forms.utils import ErrorList
from django.db import transaction
from django.utils import timezone

from .models import Vote
from postgresqleu.membership.models import MemberLog


class VoteForm(forms.Form):
    def __init__(self, election, member, *args, **kwargs):
        super(VoteForm, self).__init__(*args, **kwargs)

        self.saved_and_modified = False

        self.election = election
        self.member = member

        self.candidates = election.candidate_set.all().order_by('name')
        self.votes = Vote.objects.filter(election=election, voter=member)

        votemap = {}
        for vote in self.votes:
            votemap[vote.candidate_id] = vote.score

        dropdown = [(x, self._votestring(x)) for x in range(1, len(self.candidates) + 1)]
        dropdown.insert(0, (-1, '** Please rate this candidate'))

        # Dynamically add a dropdown field for each candidate
        for candidate in self.candidates:
            self.fields['cand%i' % candidate.id] = forms.ChoiceField(choices=dropdown,
                                                                     label=candidate.name,
                                                                     required=True,
                                                                     help_text=candidate.id,
                                                                     initial=votemap.get(candidate.id, -1))

    def _votestring(self, x):
        if x == 1:
            return "1 - Least favourite"
        if x == len(self.candidates):
            return "%s - Favourite" % len(self.candidates)
        return "%s" % x

    def clean(self):
        # First, make sure all existing fields are actually filled out
        for (k, v) in list(self.cleaned_data.items()):
            if k.startswith('cand'):
                if v == "-1":
                    self._errors[k] = ErrorList(["You need to select a score for this candidate!"])
            else:
                raise Exception("Invalid field name found: %s" % k)

        # Second, make sure the fields match the candidates
        fields = self.cleaned_data.copy()
        for candidate in self.candidates:
            k = "cand%i" % candidate.id
            if k in fields:
                del fields[k]
            else:
                raise Exception("Data for candidate %i is missing" % candidate.id)

        if len(fields) > 0:
            raise Exception("Data for candidate not standing for election found!")

        # Finally, verify that all options have been found, and none have been duplicated
        options = list(range(1, len(self.candidates) + 1))
        for k, v in list(self.cleaned_data.items()):
            if int(v) in options:
                # First use is ok. Take it out of the list, so next attempt generates error
                del options[options.index(int(v))]
            else:
                # Not in the list means it was already used! Bad user!
                if k not in self._errors:
                    # Only add this error in case the other error hasn't already fired
                    self._errors[k] = ErrorList(["This score has already been given to another candidate"])

        if len(options) != 0:
            raise forms.ValidationError("One or more scores was not properly assigned!")

        return self.cleaned_data

    @transaction.atomic
    def save(self):
        # Let's see if the old votes are here
        if len(self.votes) == 0:
            # This is completely new, let's create votes for him
            for k, v in list(self.cleaned_data.items()):
                id = int(k[4:])
                Vote(election=self.election, voter=self.member, candidate_id=id, score=v).save()
            self.votes = Vote.objects.filter(election=self.election, voter=self.member)
            MemberLog(member=self.member, timestamp=timezone.now(),
                      message="Voted in election '%s'" % self.election.name).save()
            self.saved_and_modified = True
        elif len(self.votes) == len(self.candidates):
            # Ok, we have one vote for each candidate already, so modify them as necessary
            changedany = False
            for vote in self.votes:
                score = int(self.cleaned_data['cand%i' % vote.candidate_id])
                if vote.score != score:
                    vote.score = score
                    vote.save()
                    changedany = True

            if changedany:
                MemberLog(member=self.member, timestamp=timezone.now(),
                          message="Changed votes in election '%s'" % self.election.name).save()
                self.saved_and_modified = True
        else:
            raise Exception("Invalid number of records found in database, unable to update vote.")