From 8eb7d5e69cb9c05c9ba21c64b7b88481de54c9a0 Mon Sep 17 00:00:00 2001 From: vomax Date: Sat, 29 Aug 2015 09:07:48 -0700 Subject: [PATCH 1/8] Expand voting poll test by testing voting poll details page --- pylab/website/tests/test_voting.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/pylab/website/tests/test_voting.py b/pylab/website/tests/test_voting.py index 34c1eaf..8a0279f 100644 --- a/pylab/website/tests/test_voting.py +++ b/pylab/website/tests/test_voting.py @@ -9,8 +9,9 @@ class VotingTests(WebTest): - def test_voting_page(self): + def test_voting_and_voting_poll_details_page(self): u1 = User.objects.create(username='u1') + u2 = User.objects.create(username='u2') p1 = Project.objects.create(author=u1, title='Test title 1', description='Test description') p2 = Project.objects.create(author=u1, title='Test title 2', description='Test description') @@ -19,6 +20,8 @@ def test_voting_page(self): Vote.objects.create(voter=u1, voting_poll=vp, project=p1) Vote.objects.create(voter=u1, voting_poll=vp, project=p2) + Vote.objects.create(voter=u2, voting_poll=vp, project=p1) + Vote.objects.create(voter=u2, voting_poll=vp, project=p2) resp = self.app.get('/vote/test-voting-poll/', user='u1') self.assertEqual(resp.status_int, 200) @@ -32,18 +35,32 @@ def test_voting_page(self): time_after_vote = datetime.datetime.now() - self.assertEqual(list(Vote.objects.values_list('points', flat=True)), [3, 2]) + self.assertEqual(list(Vote.objects.filter(voter=u1).values_list('points', flat=True)), [3, 2]) - for v in Vote.objects.all(): + for v in Vote.objects.filter(voter=u1): self.assertLess(time_before_vote, v.voted) self.assertGreater(time_after_vote, v.voted) - resp = self.app.get('/vote/test-voting-poll/', user='u1') + resp = self.app.get('/vote/test-voting-poll/', user='u2') self.assertEqual(resp.status_int, 200) time_before_vote = datetime.datetime.now() - resp.form['form-0-points'].value = 30 # Vote points sum should be less or equal to 15 - resp.form['form-1-points'].value = 20 + resp.form['form-0-points'].value = 6 # Vote points sum should be less or equal to 15 + resp.form['form-1-points'].value = 5 resp = resp.form.submit() + self.assertEqual(resp.status_int, 302) + + time_after_vote = datetime.datetime.now() + + self.assertEqual(list(Vote.objects.filter(voter=u2).values_list('points', flat=True)), [6, 5]) + + for v in Vote.objects.filter(voter=u2): + self.assertLess(time_before_vote, v.voted) + self.assertGreater(time_after_vote, v.voted) + + resp = self.app.get('/voting-poll/test-voting-poll/', user='u1') self.assertEqual(resp.status_int, 200) + + self.assertTrue(b'9' in resp.content) + self.assertTrue(b'7' in resp.content) From ea21908e0358541dbc09e6c43a11dc36e65a3d4f Mon Sep 17 00:00:00 2001 From: vomax Date: Sat, 29 Aug 2015 09:15:48 -0700 Subject: [PATCH 2/8] Add projects field in voting-poll model --- .../migrations/0009_votingpoll_projects.py | 19 +++++++++++++++++++ pylab/core/models.py | 1 + 2 files changed, 20 insertions(+) create mode 100644 pylab/core/migrations/0009_votingpoll_projects.py diff --git a/pylab/core/migrations/0009_votingpoll_projects.py b/pylab/core/migrations/0009_votingpoll_projects.py new file mode 100644 index 0000000..b7ab089 --- /dev/null +++ b/pylab/core/migrations/0009_votingpoll_projects.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0008_auto_20150809_0654'), + ] + + operations = [ + migrations.AddField( + model_name='votingpoll', + name='projects', + field=models.ManyToManyField(to='core.Project'), + ), + ] diff --git a/pylab/core/models.py b/pylab/core/models.py index 589a94b..e0ddb7f 100644 --- a/pylab/core/models.py +++ b/pylab/core/models.py @@ -65,6 +65,7 @@ class VotingPoll(models.Model): slug = AutoSlugField(populate_from='title') title = models.CharField(_("Title"), max_length=255) description = models.TextField(_("Description"), blank=True) + projects = models.ManyToManyField(Project) def __str__(self): return self.title From 3d9dd137102ae9f914f27280213eba4f5c7b66bf Mon Sep 17 00:00:00 2001 From: vomax Date: Sat, 29 Aug 2015 12:15:17 -0700 Subject: [PATCH 3/8] Do not create empty Vote instances --- pylab/core/models.py | 6 ++++ pylab/website/forms.py | 32 ++++++++++++++----- pylab/website/static/css/main.scss | 2 +- .../templates/website/voting_page.html | 3 +- pylab/website/tests/test_voting.py | 7 ++-- pylab/website/views.py | 19 +++++++---- 6 files changed, 46 insertions(+), 23 deletions(-) diff --git a/pylab/core/models.py b/pylab/core/models.py index e0ddb7f..7edda70 100644 --- a/pylab/core/models.py +++ b/pylab/core/models.py @@ -70,6 +70,12 @@ class VotingPoll(models.Model): def __str__(self): return self.title + def get_absolute_url(self): + return reverse('voting-poll-details', args=[self.slug]) + + def get_voting_url(self): + return reverse('voting-page', args=[self.slug]) + class Vote(models.Model): created = CreationDateTimeField() diff --git a/pylab/website/forms.py b/pylab/website/forms.py index fb0b7f2..1d48ff5 100644 --- a/pylab/website/forms.py +++ b/pylab/website/forms.py @@ -1,6 +1,7 @@ import datetime from django import forms +from django.core.validators import MinValueValidator from django.forms.models import BaseModelFormSet from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ @@ -54,16 +55,31 @@ def clean(self): self.check_existing_events(title, starts) -class VotePointsForm(forms.ModelForm): +class ProjectPointsForm(forms.ModelForm): + points = forms.IntegerField(widget=forms.NumberInput(attrs={'max': 99, 'min': 0, 'class': 'vote-points-input'}), + validators=[MinValueValidator(0)]) + + def __init__(self, user, voting_poll, *args, **kwargs): + self.user = user + self.voting_poll = voting_poll + super(ProjectPointsForm, self).__init__(*args, **kwargs) + + vote_qs = Vote.objects.filter(voter=self.user, project__id=self.initial['id']) + if vote_qs.exists(): + vote = Vote.objects.get(voter=self.user, project__id=self.initial['id']) + self.fields['points'].initial = vote.points + class Meta: - model = Vote - fields = ('points',) - widgets = { - 'points': forms.NumberInput(attrs={'max': 99, 'class': 'vote-points-input'}), - } + model = Project + fields = tuple() def save(self, commit=True, *args, **kwargs): - vote = super(VotePointsForm, self).save(commit=False, *args, **kwargs) + vote, created = Vote.objects.get_or_create( + voter=self.user, + project=self.instance, + voting_poll=self.voting_poll + ) + vote.points = self.cleaned_data['points'] vote.voted = datetime.datetime.now() vote.save() @@ -74,7 +90,7 @@ def clean(self, *args, **kwargs): total_points = 0 for form in self.forms: - if form.cleaned_data['points']: + if form.cleaned_data.get('points'): total_points += form.cleaned_data['points'] if total_points < 0 or total_points > 15: diff --git a/pylab/website/static/css/main.scss b/pylab/website/static/css/main.scss index e920baa..c5d4c40 100644 --- a/pylab/website/static/css/main.scss +++ b/pylab/website/static/css/main.scss @@ -52,7 +52,7 @@ } .vote-points-input { - width: 38px; + width: 42px; text-align: right; font-weight: bold; } diff --git a/pylab/website/templates/website/voting_page.html b/pylab/website/templates/website/voting_page.html index 2967c91..22246de 100644 --- a/pylab/website/templates/website/voting_page.html +++ b/pylab/website/templates/website/voting_page.html @@ -20,7 +20,6 @@

{{ voting_poll.title }}

{{ formset.management_form }} {% for form in formset %}
- {{ form.errors }}

{% for hidden in form.hidden_fields %} {{ hidden }} @@ -28,7 +27,7 @@

{{ voting_poll.title }}

{% for field in form.visible_fields %}
{{ field.errors }} - {{ field }} {{ form.instance.project.title }} + {{ field }} {{ form.instance.title }}
{% endfor %}

diff --git a/pylab/website/tests/test_voting.py b/pylab/website/tests/test_voting.py index 8a0279f..c633796 100644 --- a/pylab/website/tests/test_voting.py +++ b/pylab/website/tests/test_voting.py @@ -17,11 +17,8 @@ def test_voting_and_voting_poll_details_page(self): p2 = Project.objects.create(author=u1, title='Test title 2', description='Test description') vp = VotingPoll.objects.create(author=u1, title='Test voting poll', description='Test description') - - Vote.objects.create(voter=u1, voting_poll=vp, project=p1) - Vote.objects.create(voter=u1, voting_poll=vp, project=p2) - Vote.objects.create(voter=u2, voting_poll=vp, project=p1) - Vote.objects.create(voter=u2, voting_poll=vp, project=p2) + vp.projects.add(p1) + vp.projects.add(p2) resp = self.app.get('/vote/test-voting-poll/', user='u1') self.assertEqual(resp.status_int, 200) diff --git a/pylab/website/views.py b/pylab/website/views.py index 50b1fff..b76a8a1 100644 --- a/pylab/website/views.py +++ b/pylab/website/views.py @@ -1,3 +1,5 @@ +from django.utils.functional import curry + from django.contrib import messages from django.contrib.auth.decorators import login_required from django.forms.models import modelformset_factory @@ -103,22 +105,25 @@ def create_weekly_event(request, year, month, day, slug): def voting_page(request, voting_poll_slug): voting_poll = get_object_or_404(VotingPoll, slug=voting_poll_slug) total_points = 15 - VotePointsFormSet = modelformset_factory( - Vote, - form=website_forms.VotePointsForm, + ProjectPointsFormSet = modelformset_factory( + Project, + form=website_forms.ProjectPointsForm, formset=website_forms.BaseTotalPointsFormset, extra=0, ) - vote_qs = Vote.objects.filter(voter=request.user, voting_poll__slug=voting_poll_slug) + ProjectPointsFormSet.form = staticmethod(curry(website_forms.ProjectPointsForm, + user=request.user, + voting_poll=voting_poll + )) if request.method == 'POST': - formset = VotePointsFormSet(request.POST, queryset=vote_qs) + formset = ProjectPointsFormSet(request.POST, queryset=voting_poll.projects.all()) if formset.is_valid(): formset.save() messages.success(request, ugettext("Vote for ā€ž%sā€œ voting poll was saved successfully." % voting_poll)) - return redirect('project-list') + return redirect(voting_poll) else: - formset = VotePointsFormSet(queryset=vote_qs) + formset = ProjectPointsFormSet(queryset=voting_poll.projects.all()) return render(request, 'website/voting_page.html', { 'voting_poll': voting_poll, From 48d2dbdccde37a9b790908adb7dc3d2cb0c6bc0d Mon Sep 17 00:00:00 2001 From: niekas Date: Tue, 8 Sep 2015 10:49:57 +0300 Subject: [PATCH 4/8] Fix pylint errors --- pylab/website/forms.py | 10 ++++++---- pylab/website/views.py | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pylab/website/forms.py b/pylab/website/forms.py index 1d48ff5..9d5874a 100644 --- a/pylab/website/forms.py +++ b/pylab/website/forms.py @@ -56,8 +56,10 @@ def clean(self): class ProjectPointsForm(forms.ModelForm): - points = forms.IntegerField(widget=forms.NumberInput(attrs={'max': 99, 'min': 0, 'class': 'vote-points-input'}), - validators=[MinValueValidator(0)]) + points = forms.IntegerField( + widget=forms.NumberInput(attrs={'max': 99, 'min': 0, 'class': 'vote-points-input'}), + validators=[MinValueValidator(0)], + ) def __init__(self, user, voting_poll, *args, **kwargs): self.user = user @@ -73,8 +75,8 @@ class Meta: model = Project fields = tuple() - def save(self, commit=True, *args, **kwargs): - vote, created = Vote.objects.get_or_create( + def save(self, commit=True, *args, **kwargs): # pylint: disable=unused-argument + vote, created = Vote.objects.get_or_create( # pylint: disable=unused-variable voter=self.user, project=self.instance, voting_poll=self.voting_poll diff --git a/pylab/website/views.py b/pylab/website/views.py index b76a8a1..b3718e7 100644 --- a/pylab/website/views.py +++ b/pylab/website/views.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext from django.db.models import Sum -from pylab.core.models import Project, Event, Vote, VotingPoll +from pylab.core.models import Project, Event, VotingPoll from pylab.website.helpers import formrenderer from pylab.website.helpers.decorators import superuser_required from pylab.website.services import weeklyevents @@ -111,10 +111,10 @@ def voting_page(request, voting_poll_slug): formset=website_forms.BaseTotalPointsFormset, extra=0, ) - ProjectPointsFormSet.form = staticmethod(curry(website_forms.ProjectPointsForm, + ProjectPointsFormSet.form = staticmethod(curry( + website_forms.ProjectPointsForm, user=request.user, - voting_poll=voting_poll - )) + voting_poll=voting_poll)) if request.method == 'POST': formset = ProjectPointsFormSet(request.POST, queryset=voting_poll.projects.all()) From dd517dad1899790733b3bba31c113045adea75d6 Mon Sep 17 00:00:00 2001 From: niekas Date: Mon, 14 Sep 2015 18:18:10 +0300 Subject: [PATCH 5/8] Replace MinValueValidator with min_value --- pylab/website/forms.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylab/website/forms.py b/pylab/website/forms.py index 9d5874a..f5bb520 100644 --- a/pylab/website/forms.py +++ b/pylab/website/forms.py @@ -1,7 +1,6 @@ import datetime from django import forms -from django.core.validators import MinValueValidator from django.forms.models import BaseModelFormSet from django.utils.translation import ugettext from django.utils.translation import ugettext_lazy as _ @@ -57,8 +56,8 @@ def clean(self): class ProjectPointsForm(forms.ModelForm): points = forms.IntegerField( + min_value=0, widget=forms.NumberInput(attrs={'max': 99, 'min': 0, 'class': 'vote-points-input'}), - validators=[MinValueValidator(0)], ) def __init__(self, user, voting_poll, *args, **kwargs): From fb373380a705827e5085d93ed2372f54b0cc7be9 Mon Sep 17 00:00:00 2001 From: niekas Date: Mon, 14 Sep 2015 18:34:46 +0300 Subject: [PATCH 6/8] Use try-except instead double query --- pylab/website/forms.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pylab/website/forms.py b/pylab/website/forms.py index f5bb520..5fb4aa9 100644 --- a/pylab/website/forms.py +++ b/pylab/website/forms.py @@ -65,10 +65,11 @@ def __init__(self, user, voting_poll, *args, **kwargs): self.voting_poll = voting_poll super(ProjectPointsForm, self).__init__(*args, **kwargs) - vote_qs = Vote.objects.filter(voter=self.user, project__id=self.initial['id']) - if vote_qs.exists(): + try: vote = Vote.objects.get(voter=self.user, project__id=self.initial['id']) self.fields['points'].initial = vote.points + except Vote.DoesNotExist: + pass class Meta: model = Project From 5b2a48b85355c977bf8b020374cf3e386807ff53 Mon Sep 17 00:00:00 2001 From: niekas Date: Mon, 14 Sep 2015 20:01:27 +0300 Subject: [PATCH 7/8] Replace database queries with factories in tests --- pylab/core/factories.py | 26 +++++++++++++++++++++++++- pylab/website/tests/test_voting.py | 19 ++++++++++--------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/pylab/core/factories.py b/pylab/core/factories.py index e106ff1..6f2ab7f 100644 --- a/pylab/core/factories.py +++ b/pylab/core/factories.py @@ -3,7 +3,7 @@ from django.contrib.auth.models import User -from pylab.core.models import Event +from pylab.core.models import Event, Project, VotingPoll from pylab.core.helpers.factories import fake, future @@ -41,3 +41,27 @@ class Meta: description = factory.LazyAttribute(fake.sentence()) starts = factory.LazyAttribute(future(days=1, hour=18, minute=0, second=0)) ends = factory.LazyAttribute(future(days=1, hour=20, minute=0, second=0)) + + +class ProjectFactory(factory.django.DjangoModelFactory): + + class Meta: + model = Project + django_get_or_create = ('slug',) + + author = factory.SubFactory(UserFactory) + title = factory.LazyAttribute(fake.company()) + slug = factory.LazyAttribute(fake.slug()) + description = factory.LazyAttribute(fake.sentence()) + + +class VotingPollFactory(factory.django.DjangoModelFactory): + + class Meta: + model = VotingPoll + django_get_or_create = ('slug',) + + author = factory.SubFactory(UserFactory) + title = factory.LazyAttribute(fake.company()) + slug = factory.LazyAttribute(fake.slug()) + description = factory.LazyAttribute(fake.sentence()) diff --git a/pylab/website/tests/test_voting.py b/pylab/website/tests/test_voting.py index c633796..755920a 100644 --- a/pylab/website/tests/test_voting.py +++ b/pylab/website/tests/test_voting.py @@ -4,23 +4,24 @@ from django.contrib.auth.models import User -from pylab.core.models import Project, VotingPoll, Vote +from pylab.core.factories import UserFactory, VotingPollFactory, ProjectFactory +from pylab.core.models import Vote class VotingTests(WebTest): def test_voting_and_voting_poll_details_page(self): - u1 = User.objects.create(username='u1') - u2 = User.objects.create(username='u2') + u1 = UserFactory() + u2 = UserFactory() - p1 = Project.objects.create(author=u1, title='Test title 1', description='Test description') - p2 = Project.objects.create(author=u1, title='Test title 2', description='Test description') + p1 = ProjectFactory(author=u1) + p2 = ProjectFactory(author=u1) - vp = VotingPoll.objects.create(author=u1, title='Test voting poll', description='Test description') + vp = VotingPollFactory(author=u1) vp.projects.add(p1) vp.projects.add(p2) - resp = self.app.get('/vote/test-voting-poll/', user='u1') + resp = self.app.get(vp.get_voting_url(), user=u1) self.assertEqual(resp.status_int, 200) time_before_vote = datetime.datetime.now() @@ -38,7 +39,7 @@ def test_voting_and_voting_poll_details_page(self): self.assertLess(time_before_vote, v.voted) self.assertGreater(time_after_vote, v.voted) - resp = self.app.get('/vote/test-voting-poll/', user='u2') + resp = self.app.get(vp.get_voting_url(), user=u2) self.assertEqual(resp.status_int, 200) time_before_vote = datetime.datetime.now() @@ -56,7 +57,7 @@ def test_voting_and_voting_poll_details_page(self): self.assertLess(time_before_vote, v.voted) self.assertGreater(time_after_vote, v.voted) - resp = self.app.get('/voting-poll/test-voting-poll/', user='u1') + resp = self.app.get(vp.get_absolute_url(), user=u1) self.assertEqual(resp.status_int, 200) self.assertTrue(b'9' in resp.content) From 2be19a3a7d28a7236a2fd9d70e6700a91e0685c3 Mon Sep 17 00:00:00 2001 From: niekas Date: Mon, 14 Sep 2015 21:01:30 +0300 Subject: [PATCH 8/8] Remove unused User import --- pylab/website/tests/test_voting.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pylab/website/tests/test_voting.py b/pylab/website/tests/test_voting.py index 755920a..c656f14 100644 --- a/pylab/website/tests/test_voting.py +++ b/pylab/website/tests/test_voting.py @@ -2,8 +2,6 @@ from django_webtest import WebTest -from django.contrib.auth.models import User - from pylab.core.factories import UserFactory, VotingPollFactory, ProjectFactory from pylab.core.models import Vote