Merge lp:~ronnie.vd.c/ubuntu-nl-website/forms-views into lp:~registry/ubuntu-nl-website/voting
- forms-views
- Merge into voting
Proposed by
Ronnie
Status: | Merged |
---|---|
Merged at revision: | 6 |
Proposed branch: | lp:~ronnie.vd.c/ubuntu-nl-website/forms-views |
Merge into: | lp:~registry/ubuntu-nl-website/voting |
Diff against target: |
389 lines (+257/-37) 9 files modified
elections/forms.py (+104/-0) elections/models.py (+21/-1) elections/urls.py (+4/-4) elections/views.py (+87/-26) settings.py (+1/-1) templates/election_new.html (+10/-0) templates/election_results.html (+19/-0) templates/registration/login.html (+5/-0) urls.py (+6/-5) |
To merge this branch: | bzr merge lp:~ronnie.vd.c/ubuntu-nl-website/forms-views |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Registry Administrators | Pending | ||
Review via email: mp+49581@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'elections/forms.py' |
2 | --- elections/forms.py 1970-01-01 00:00:00 +0000 |
3 | +++ elections/forms.py 2011-02-14 00:39:53 +0000 |
4 | @@ -0,0 +1,104 @@ |
5 | +from django import forms |
6 | + |
7 | +from elections.models import Election, Candidate, Voter |
8 | + |
9 | + |
10 | +class CreateElectionForm(forms.ModelForm): |
11 | + class Meta: |
12 | + model = Election |
13 | + |
14 | + def clean(self, *args, **kwargs): |
15 | + super(CreateElectionForm, self).clean(*args, **kwargs) |
16 | + cleaned_data = self.cleaned_data |
17 | + |
18 | + candidacy_start = cleaned_data.get('candidacy_start') |
19 | + candidacy_end = cleaned_data.get('candidacy_end') |
20 | + election_start = cleaned_data.get('election_start') |
21 | + election_end = cleaned_data.get('election_end') |
22 | + |
23 | + if candidacy_start > candidacy_end: |
24 | + self._errors['candidacy_end'] = self.error_class(['Eind-datum is eerder dan begin-datum']) |
25 | + |
26 | + if candidacy_end > election_start: |
27 | + self._errors['election_start'] = self.error_class(['De kandidaten moeten gekozen worden voordat de verkiezing begint']) |
28 | + |
29 | + if election_start > election_end: |
30 | + self._errors['election_end'] = self.error_class(['Eind-datum is eerder dan begin-datum']) |
31 | + |
32 | + # Always return the full collection of cleaned data. |
33 | + return cleaned_data |
34 | + |
35 | + |
36 | +class ApplyForElectionForm(forms.ModelForm): |
37 | + class Meta: |
38 | + model = Candidate |
39 | + fields = ('chair_desired', 'council_desired') |
40 | + |
41 | + |
42 | +class VoteForm(forms.Form): |
43 | + chair = forms.ModelChoiceField(queryset=Candidate.objects.all()) |
44 | + council1 = forms.ModelChoiceField(queryset=Candidate.objects.all()) |
45 | + council2 = forms.ModelChoiceField(queryset=Candidate.objects.all()) |
46 | + council3 = forms.ModelChoiceField(queryset=Candidate.objects.all()) |
47 | + council4 = forms.ModelChoiceField(queryset=Candidate.objects.all()) |
48 | + council5 = forms.ModelChoiceField(queryset=Candidate.objects.all()) |
49 | + council6 = forms.ModelChoiceField(queryset=Candidate.objects.all()) |
50 | + |
51 | + def __init__(self, election, *args, **kwargs): |
52 | + super(VoteForm, self).__init__(*args, **kwargs) |
53 | + chair_choices = Candidate.objects.filter(election=election, chair_desired=True) |
54 | + council_choices = Candidate.objects.filter(election=election, council_desired=True) |
55 | + self.fields['chair'].queryset = chair_choices |
56 | + self.fields['council1'].queryset = council_choices |
57 | + self.fields['council2'].queryset = council_choices |
58 | + self.fields['council3'].queryset = council_choices |
59 | + self.fields['council4'].queryset = council_choices |
60 | + self.fields['council5'].queryset = council_choices |
61 | + self.fields['council6'].queryset = council_choices |
62 | + |
63 | + def clean(self, *args, **kwargs): |
64 | + cleaned_data = self.cleaned_data |
65 | + |
66 | + council = [cleaned_data.get('council1'), |
67 | + cleaned_data.get('council2'), |
68 | + cleaned_data.get('council3'), |
69 | + cleaned_data.get('council4'), |
70 | + cleaned_data.get('council5'), |
71 | + cleaned_data.get('council6')] |
72 | + council.reverse() |
73 | + |
74 | + for candidate in council: |
75 | + if council.count(candidate) > 1: |
76 | + for same in range(council.count(candidate) - 1): |
77 | + index = council.index(candidate) |
78 | + self._errors['council%d' % (len(council)-index)] = self.error_class(['Je hebt deze persoon al gekozen']) |
79 | + council.pop(index) |
80 | + |
81 | + # Always return the full collection of cleaned data. |
82 | + return cleaned_data |
83 | + |
84 | + |
85 | + def save(self, *args, **kwargs): |
86 | + cleaned_data = self.cleaned_data |
87 | + |
88 | + votes = [cleaned_data.get('council1'), |
89 | + cleaned_data.get('council2'), |
90 | + cleaned_data.get('council3'), |
91 | + cleaned_data.get('council4'), |
92 | + cleaned_data.get('council5'), |
93 | + cleaned_data.get('council6')] |
94 | + |
95 | + for c in range(len(votes)): |
96 | + candidate = votes[c] |
97 | + candidate.council_votes += len(votes) - c |
98 | + candidate.save() |
99 | + |
100 | + chair = cleaned_data.get('chair') |
101 | + if chair in votes: # If the chair is in the votes, the new version is not stored in cleaned_data, so use the database version |
102 | + chair = Candidate.objects.get(pk=cleaned_data.get('chair').pk) |
103 | + |
104 | + chair.chair_votes += 1 |
105 | + chair.save() |
106 | + |
107 | + return True |
108 | + |
109 | |
110 | === modified file 'elections/models.py' |
111 | --- elections/models.py 2011-02-11 23:43:01 +0000 |
112 | +++ elections/models.py 2011-02-14 00:39:53 +0000 |
113 | @@ -1,16 +1,20 @@ |
114 | from django.db import models |
115 | +from django.contrib import admin |
116 | from django.contrib.auth.models import User |
117 | |
118 | import datetime |
119 | |
120 | |
121 | class Election(models.Model): |
122 | - name = models.CharField(max_length=128, default="Verkiezing") |
123 | + name = models.CharField(max_length=128, default='Verkiezing') |
124 | candidacy_start = models.DateTimeField(default=datetime.datetime.now()) |
125 | candidacy_end = models.DateTimeField(default=datetime.datetime.now()+datetime.timedelta(days=28)) |
126 | election_start = models.DateTimeField(default=datetime.datetime.now()+datetime.timedelta(days=28)) |
127 | election_end = models.DateTimeField(default=datetime.datetime.now()+datetime.timedelta(days=35)) |
128 | live_results = models.BooleanField(default=False) # If true, the votes can be seen immediatly, else the results are shown after the election |
129 | + |
130 | + def __unicode__(self): |
131 | + return self.name |
132 | |
133 | |
134 | class Candidate(models.Model): |
135 | @@ -21,6 +25,22 @@ |
136 | chair_votes = models.IntegerField(default=0) |
137 | council_votes = models.IntegerField(default=0) # 6 for first choice, 1 for 6th choice |
138 | |
139 | + class Meta: |
140 | + unique_together = ('user', 'election') |
141 | + |
142 | + def __unicode__(self): |
143 | + return '%s (%s)' % (self.user.username, self.election.name) |
144 | + |
145 | + |
146 | class Voter(models.Model): |
147 | election = models.ForeignKey(Election) |
148 | voter = models.CharField(max_length=12) # StoreOneWayHashOfOpenIDHere |
149 | + |
150 | + class Meta: |
151 | + unique_together = ('voter', 'election') |
152 | + |
153 | + |
154 | +admin.site.register(Election) |
155 | +admin.site.register(Candidate) |
156 | +admin.site.register(Voter) |
157 | + |
158 | |
159 | === modified file 'elections/urls.py' |
160 | --- elections/urls.py 2011-02-11 23:45:49 +0000 |
161 | +++ elections/urls.py 2011-02-14 00:39:53 +0000 |
162 | @@ -2,9 +2,9 @@ |
163 | |
164 | |
165 | urlpatterns = patterns('', |
166 | - url('^nieuw/$', 'elections.views.election_new', name='election-new'), # Create a new election (check permissions trough admin group) |
167 | + url(r'^nieuw/$', 'elections.views.election_new', name='election-new'), # Create a new election (check permissions trough admin group) |
168 | # Kunnen deze 3 pagina's ook 1 url worden, afhankelijk van de datum (voor,tijdens,na verkiezing?) |
169 | - url('^(?P<electionId>\d+)/(?P<electionName>\w+)/verkiesbaar$', 'elections.views.election_apply', name='election-apply'), # Check/Uncheck (before election starts) if you apply for this |
170 | - url('^(?P<electionId>\d+)/(?P<electionName>\w+)/stem$', 'elections.views.election_vote', name='election-vote'), # Voting happens here (or view / edit? your vote, if already voted) |
171 | - url('^(?P<electionId>\d+)/(?P<electionName>\w+)/uitslag/$', 'elections.views.election_results', name='election-results'), # The link where the results are shown (maybe with some nice effects) |
172 | + url(r'^(?P<electionId>\d+)/(?P<electionName>\w+)/verkiesbaar$', 'elections.views.election_apply', name='election-apply'), # Check/Uncheck (before election starts) if you apply for this |
173 | + url(r'^(?P<electionId>\d+)/(?P<electionName>\w+)/stem$', 'elections.views.election_vote', name='election-vote'), # Voting happens here (or view / edit? your vote, if already voted) |
174 | + url(r'^(?P<electionId>\d+)/(?P<electionName>\w+)/uitslag/$', 'elections.views.election_results', name='election-results'), # The link where the results are shown (maybe with some nice effects) |
175 | ) |
176 | |
177 | === modified file 'elections/views.py' |
178 | --- elections/views.py 2011-02-11 23:45:49 +0000 |
179 | +++ elections/views.py 2011-02-14 00:39:53 +0000 |
180 | @@ -1,29 +1,90 @@ |
181 | +from django.contrib.auth.decorators import login_required |
182 | +from django.db import IntegrityError |
183 | from django.views.generic.simple import direct_to_template |
184 | +from django.shortcuts import redirect |
185 | from django.template import RequestContext |
186 | - |
187 | -from elections.forms import CreateElectionForm, VoteForm |
188 | - |
189 | +from django.http import Http404 |
190 | + |
191 | +from elections.models import Election, Candidate, Voter |
192 | +from elections.forms import CreateElectionForm, ApplyForElectionForm, VoteForm |
193 | + |
194 | +import datetime |
195 | + |
196 | +def account(request): |
197 | + # TODO: Write this view |
198 | + return direct_to_template(request, 'election_new.html', {}) |
199 | + |
200 | +@login_required |
201 | def election_new(request): |
202 | - form = None |
203 | - if request.POST: |
204 | - pass |
205 | - form = CreateElectionForm(request.POST) |
206 | - |
207 | - if not form: |
208 | - pass |
209 | - form = CreateElectionForm() |
210 | - |
211 | - context = { |
212 | - 'form': form, |
213 | - } |
214 | - |
215 | - return direct_to_template(request, 'election_new.html', context) |
216 | - |
217 | -def election_apply(request): |
218 | - form = ApplyForElectionForm(request.POST) |
219 | - |
220 | -def election_vote(request): |
221 | - pass |
222 | - |
223 | -def election_results(request): |
224 | - pass |
225 | + # TODO: limit only to admins, to prevent other users to create different elections (for now) |
226 | + form = CreateElectionForm(data=request.POST or None) |
227 | + |
228 | + if request.POST and form.is_valid(): |
229 | + election = form.save() |
230 | + # TODO: Link to a succes page |
231 | + return redirect(election_apply, electionId=election.pk, electionName=election.name) |
232 | + |
233 | + return direct_to_template(request, 'election_new.html', {'form': form}) |
234 | + |
235 | +@login_required |
236 | +def election_apply(request, electionId, electionName): |
237 | + election = Election.objects.get(pk=electionId) |
238 | + if datetime.datetime.now() < election.candidacy_start: |
239 | + raise Http404('De kandidaatstelling is nog niet begonnen') |
240 | + if datetime.datetime.now() > election.candidacy_end: |
241 | + raise Http404('De kandidaatstelling is al afgelopen') |
242 | + |
243 | + # TODO: check if user has rights |
244 | + candidate, created = Candidate.objects.get_or_create(election=election, user=request.user) |
245 | + form = ApplyForElectionForm(data=request.POST or None, instance=candidate) |
246 | + |
247 | + if request.POST and form.is_valid(): |
248 | + form.save() |
249 | + # TODO: Link to a success page and overview page of candidates |
250 | + return redirect(election_vote, electionId=election.pk, electionName=election.name) |
251 | + |
252 | + return direct_to_template(request, 'election_new.html', {'form': form}) |
253 | + |
254 | +@login_required |
255 | +def election_vote(request, electionId, electionName): |
256 | + election = Election.objects.get(pk=electionId) |
257 | + if datetime.datetime.now() < election.election_start: |
258 | + raise Http404('De verkiezing is nog niet begonnen') |
259 | + if datetime.datetime.now() > election.election_end: |
260 | + raise Http404('De verkiezing is al afgelopen') |
261 | + |
262 | + # TODO: check if user has rights to vote |
263 | + user_hash = hash(request.user) # TODO: Do a better hash, and not of the user, but the openid |
264 | + try: # If the voter already exists, it has already vote, raise an exception! |
265 | + Voter.objects.get(election=election, voter=user_hash) |
266 | + except Voter.DoesNotExist: |
267 | + pass |
268 | + else: |
269 | + raise Http404('Je heb al gestemd voor deze verkiezing') |
270 | + |
271 | + form = VoteForm(data=request.POST or None, election=election) |
272 | + |
273 | + if request.POST and form.is_valid(): |
274 | + form.save() |
275 | + Voter.objects.create(election=election, voter=user_hash) |
276 | + return redirect(election_results, electionId=election.pk, electionName=election.name) |
277 | + |
278 | + return direct_to_template(request, 'election_new.html', {'form': form}) |
279 | + |
280 | + |
281 | +def election_results(request, electionId, electionName): |
282 | + election = Election.objects.get(pk=electionId) |
283 | + if datetime.datetime.now() < election.election_start: |
284 | + if election.live_results: |
285 | + raise Http404('Het stemming is nog niet begonnen, de eerste resultaten zijn pas zichtbaar op: %s' % election.election_start) |
286 | + else: |
287 | + raise Http404('Het stemming is nog niet begonnen, de eerste resultaten zijn pas zichtbaar op: %s' % election.election_end) |
288 | + if datetime.datetime.now() < election.election_end and not election.live_results: |
289 | + raise Http404('De resultaten zijn pas zichtbaar na de stemming (%s)' % election.election_end) |
290 | + |
291 | + top_chairs = Candidate.objects.filter(chair_desired=True).order_by('-chair_votes') |
292 | + top_council = Candidate.objects.filter(council_desired=True).order_by('-council_votes') |
293 | + return direct_to_template(request, 'election_results.html', {'chairs': top_chairs, |
294 | + 'council': top_council}) |
295 | + |
296 | + |
297 | |
298 | === modified file 'settings.py' |
299 | --- settings.py 2011-02-11 23:45:49 +0000 |
300 | +++ settings.py 2011-02-14 00:39:53 +0000 |
301 | @@ -95,7 +95,7 @@ |
302 | 'django.contrib.sites', |
303 | 'django.contrib.messages', |
304 | # Uncomment the next line to enable the admin: |
305 | - # 'django.contrib.admin', |
306 | + 'django.contrib.admin', |
307 | # Uncomment the next line to enable admin documentation: |
308 | # 'django.contrib.admindocs', |
309 | |
310 | |
311 | === added directory 'templates' |
312 | === added file 'templates/election_new.html' |
313 | --- templates/election_new.html 1970-01-01 00:00:00 +0000 |
314 | +++ templates/election_new.html 2011-02-14 00:39:53 +0000 |
315 | @@ -0,0 +1,10 @@ |
316 | +<html> |
317 | +<body> |
318 | +<form method="POST" action=""> |
319 | +{% csrf_token %} |
320 | +{{ form.as_ul }} |
321 | +<input type="submit" value="create" /> |
322 | +</form> |
323 | +</body> |
324 | +</html> |
325 | + |
326 | |
327 | === added file 'templates/election_results.html' |
328 | --- templates/election_results.html 1970-01-01 00:00:00 +0000 |
329 | +++ templates/election_results.html 2011-02-14 00:39:53 +0000 |
330 | @@ -0,0 +1,19 @@ |
331 | +<html> |
332 | +<body> |
333 | + |
334 | +<h3>=== Chairs ===</h3> |
335 | +<ul> |
336 | +{% for candidate in chairs %} |
337 | + <li>{{ candidate.user.username }} ({{ candidate.chair_votes }} votes)</li> |
338 | +{% endfor %} |
339 | +</ul> |
340 | + |
341 | +<h3>=== Council ===</h3> |
342 | +<ul> |
343 | +{% for candidate in council %} |
344 | + <li>{{ candidate.user.username }} ({{ candidate.council_votes }} votes)</li> |
345 | +{% endfor %} |
346 | +</ul> |
347 | + |
348 | +</body> |
349 | +</html> |
350 | |
351 | === added directory 'templates/registration' |
352 | === added file 'templates/registration/login.html' |
353 | --- templates/registration/login.html 1970-01-01 00:00:00 +0000 |
354 | +++ templates/registration/login.html 2011-02-14 00:39:53 +0000 |
355 | @@ -0,0 +1,5 @@ |
356 | +<form method="POST"> |
357 | + {% csrf_token %} |
358 | + {{ form.as_ul }} |
359 | + <input type="submit" name="Inloggen" /> |
360 | +</form> |
361 | |
362 | === modified file 'urls.py' |
363 | --- urls.py 2011-02-11 23:45:49 +0000 |
364 | +++ urls.py 2011-02-14 00:39:53 +0000 |
365 | @@ -1,12 +1,13 @@ |
366 | from django.conf.urls.defaults import * |
367 | |
368 | # Uncomment the next two lines to enable the admin: |
369 | -# from django.contrib import admin |
370 | -# admin.autodiscover() |
371 | +from django.contrib import admin |
372 | +admin.autodiscover() |
373 | |
374 | urlpatterns = patterns('', |
375 | - url('^account/$', 'elections.views.account'), # to fill in your Full name, because the LP name is not always the right |
376 | - url('^verkiezing/$', include('ubuntu_nl_voting.elections.urls')), |
377 | + url(r'^account/$', 'elections.views.account', name='account'), # to fill in your Full name, because the LP name is not always the right |
378 | + url(r'^accounts/login/$', 'django.contrib.auth.views.login', name='login'), |
379 | + url(r'^verkiezing/', include('ubuntu_nl_voting.elections.urls')), |
380 | # Example: |
381 | # (r'^ubuntu_nl_voting/', include('ubuntu_nl_voting.foo.urls')), |
382 | |
383 | @@ -14,5 +15,5 @@ |
384 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), |
385 | |
386 | # Uncomment the next line to enable the admin: |
387 | - # (r'^admin/', include(admin.site.urls)), |
388 | + (r'^admin/', include(admin.site.urls)), |
389 | ) |