Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion pylab/core/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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())
19 changes: 19 additions & 0 deletions pylab/core/migrations/0009_votingpoll_projects.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
7 changes: 7 additions & 0 deletions pylab/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,17 @@ 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

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()
Expand Down
36 changes: 27 additions & 9 deletions pylab/website/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,34 @@ def clean(self):
self.check_existing_events(title, starts)


class VotePointsForm(forms.ModelForm):
class ProjectPointsForm(forms.ModelForm):
points = forms.IntegerField(
min_value=0,
widget=forms.NumberInput(attrs={'max': 99, 'min': 0, 'class': 'vote-points-input'}),
)

def __init__(self, user, voting_poll, *args, **kwargs):
self.user = user
self.voting_poll = voting_poll
super(ProjectPointsForm, self).__init__(*args, **kwargs)

try:
vote = Vote.objects.get(voter=self.user, project__id=self.initial['id'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you are doing two queries instead of one. Use try ... except.

self.fields['points'].initial = vote.points
except Vote.DoesNotExist:
pass

class Meta:
model = Vote
fields = ('points',)
widgets = {
'points': forms.NumberInput(attrs={'max': 99, 'class': 'vote-points-input'}),
}
model = Project
fields = tuple()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you need empty tuple here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this form is not devoted for modifing Project fields, instead we need one extra field and instance object in the form. fields or exlucde must be defined, so I chose to define empty fields.


def save(self, commit=True, *args, **kwargs):
vote = super(VotePointsForm, self).save(commit=False, *args, **kwargs)
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
)
vote.points = self.cleaned_data['points']
vote.voted = datetime.datetime.now()
vote.save()

Expand All @@ -74,7 +92,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:
Expand Down
2 changes: 1 addition & 1 deletion pylab/website/static/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
}

.vote-points-input {
width: 38px;
width: 42px;
text-align: right;
font-weight: bold;
}
Expand Down
3 changes: 1 addition & 2 deletions pylab/website/templates/website/voting_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ <h1>{{ voting_poll.title }}</h1>
{{ formset.management_form }}
{% for form in formset %}
<div class="form-group">
{{ form.errors }}
<p>
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
<div class="form-group">
{{ field.errors }}
{{ field }} <a href="{{ form.instance.project.get_absolute_url }}">{{ form.instance.project.title }}</a>
{{ field }} <a href="{{ form.instance.get_absolute_url }}">{{ form.instance.title }}</a>
</div>
{% endfor %}
</p>
Expand Down
59 changes: 26 additions & 33 deletions pylab/website/tests/test_voting.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,24 @@

from django_webtest import WebTest

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_page(self):
u1 = User.objects.create(username='u1')

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')
def test_voting_and_voting_poll_details_page(self):
u1 = UserFactory()
u2 = UserFactory()

vp = VotingPoll.objects.create(author=u1, title='Test voting poll', description='Test description')
p1 = ProjectFactory(author=u1)
p2 = ProjectFactory(author=u1)

Vote.objects.create(voter=u1, voting_poll=vp, project=p1)
Vote.objects.create(voter=u1, voting_poll=vp, project=p2)
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()
Expand All @@ -32,38 +31,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(vp.get_voting_url(), 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, 200)

def test_voting_poll_details(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')
self.assertEqual(resp.status_int, 302)

vp = VotingPoll.objects.create(author=u1, title='Test voting poll', description='Test description')
time_after_vote = datetime.datetime.now()

Vote.objects.create(voter=u1, voting_poll=vp, project=p1, points=1)
Vote.objects.create(voter=u1, voting_poll=vp, project=p2, points=2)
Vote.objects.create(voter=u2, voting_poll=vp, project=p1, points=3)
Vote.objects.create(voter=u2, voting_poll=vp, project=p2, points=4)
self.assertEqual(list(Vote.objects.filter(voter=u2).values_list('points', flat=True)), [6, 5])

resp = self.app.get('/voting-poll/test-voting-poll/', user='u1')
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(vp.get_absolute_url(), user=u1)
self.assertEqual(resp.status_int, 200)
self.assertTrue(b'4' in resp.content)
self.assertTrue(b'6' in resp.content)

self.assertTrue(b'9' in resp.content)
self.assertTrue(b'7' in resp.content)
21 changes: 13 additions & 8 deletions pylab/website/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -7,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
Expand Down Expand Up @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like a dark magic, but this is more related with FormSet limitation, probably, I will look into it later, when I have more time. I'm sure, that there can be better solution.


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,
Expand Down