Skip to content

Commit

Permalink
Voting for Nominations #95
Browse files Browse the repository at this point in the history
Feature to send email to selected set of board members for voting.
Voting expires in 10 days from the time of request and user can vote
only once.
Required validations are added.
Added 2 new models to store url hash and capture votes submitted.
  • Loading branch information
vanishan committed Aug 23, 2016
1 parent c739409 commit e2e859e
Show file tree
Hide file tree
Showing 11 changed files with 625 additions and 7 deletions.
20 changes: 20 additions & 0 deletions apps/common/emailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ def send_payment_confirmation_email(user, instance):
_send_payment_confirmation_email_to_staff(user, instance)


def send_voting_email(user, nomination_type, slug, vote_url):
subject = "PSSI Voting Request for {nomination_type} - {slug}".format(nomination_type=nomination_type,
slug=slug)
message = """
Hi %s,
As you are aware that we have closed the Nomination for %s - %s.
We are in the process of voting. Being a PSSI member, your vote makes a lot of difference.
Below is the link for voting.
<a href="%s">Vote</a>
Request you to vote for a nominee and help us select one.
Cheers!
PSSI Board.
""" % (user.first_name, nomination_type, slug, vote_url)

return _send_mail(subject, message, recipient_list=[user.email])


# Private functions
def _send_mail(subject, message, recipient_list):
"""All the email originating from system should go via this interface.
Expand Down
19 changes: 19 additions & 0 deletions apps/nominations/migrations/0004_auto_20160729_0933.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 = [
('nominations', '0003_nominationtype_description'),
]

operations = [
migrations.AlterField(
model_name='nominationtype',
name='description',
field=models.TextField(verbose_name='Description about nomination', default=''),
),
]
53 changes: 53 additions & 0 deletions apps/nominations/migrations/0005_auto_20160823_2212.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
from django.conf import settings


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('nominations', '0004_auto_20160729_0933'),
]

operations = [
migrations.CreateModel(
name='UserVoting',
fields=[
('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('comments', models.TextField()),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
('vote', models.ForeignKey(to='nominations.Nomination')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='VotingURL',
fields=[
('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('url_hash', models.CharField(max_length=32, verbose_name='hash', unique=True)),
('expiry', models.DateTimeField(verbose_name='Expiry')),
('ntype', models.ForeignKey(to='nominations.NominationType')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.AddField(
model_name='uservoting',
name='voting_url',
field=models.ForeignKey(to='nominations.VotingURL'),
preserve_default=True,
),
]
26 changes: 26 additions & 0 deletions apps/nominations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,29 @@ def __str__(self):
ntype=self.ntype.name,
fullname=self.fullname
)


class VotingURL(BaseModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
url_hash = models.CharField("hash", blank=False, max_length=32, unique=True)
expiry = models.DateTimeField("Expiry")
ntype = models.ForeignKey('NominationType')

def __str__(self):
return "{user}: {hash} - {expiry}". format(
user=self.user.username,
hash=self.url_hash,
expiry=self.expiry)


class UserVoting(BaseModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
vote = models.ForeignKey('Nomination')
voting_url = models.ForeignKey('VotingURL')
comments = models.TextField()

def __str__(self):
return "{user}: {vote} - {comments}". format(
user=self.user.username,
vote=self.vote,
comments=self.comments,)
189 changes: 184 additions & 5 deletions apps/nominations/views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import json
import hashlib, pickle

from datetime import datetime, timedelta

from django.conf import settings
from django.utils import timezone
from django.views.generic.edit import CreateView
from django.views.generic import ListView
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import get_object_or_404, render
from django.core.urlresolvers import reverse_lazy, reverse
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseForbidden
from django.http import HttpResponse, HttpResponseForbidden
from django.contrib.auth.models import User
from django.contrib.sites.shortcuts import get_current_site

from .models import Nomination, NominationType
from .models import Nomination, NominationType, VotingURL, UserVoting
from .forms import NominationForm
from common import emailer
from board.models import BoardMember
from apps.common.emailer import send_voting_email
# from django.contrib.sites.models import Sites


class LoginRequiredMixin(object):
Expand Down Expand Up @@ -95,3 +105,172 @@ def form_valid(self, form):
instance=form.instance
)
return super(NominationCreateView, self).form_valid(form)


class ViewNominationListView(ListView, LoginRequiredMixin):
model = NominationType
template_name = 'nominations/nomination_list.html'
context_object_name = 'nomination_types'

def dispatch(self, request, *args, **kwargs):
if not is_board_member(self.request.user):
return HttpResponseForbidden("Sorry! You do not have permission to view the Nominations!")
return super(ViewNominationListView, self).dispatch(request, *args, **kwargs)

def get_context_data(self, *args, **kwargs):
context = super(
ViewNominationListView, self).get_context_data(*args, **kwargs)

nominations = NominationType.objects.values_list('id', 'name', 'slug').distinct()
nomination_dict = {}
for each in nominations:
nomination_dict[each[0]] = '%s - %s' % (each[1], each[2])

context['nomination_types'] = nomination_dict

board_members_list = BoardMember.objects.all() #.filter(end_date__gte=datetime.now())
board_members = {}
for member in board_members_list:
board_members[member.user.id] = member.user.get_full_name()

context['board_members'] = board_members

return context


class CreateVoteUrlView(ListView, LoginRequiredMixin):
model = NominationType
template_name = 'nominations/nomination_list.html'
context_object_name = 'nomination_type_list'

def dispatch(self, request, *args, **kwargs):
if not is_board_member(self.request.user):
return HttpResponseForbidden("Sorry! You do not have permission to view the Nominations!")
return super(CreateVoteUrlView, self).dispatch(request, *args, **kwargs)

def post(self, request, *args, **kwargs):
nom_id = request.POST.get('nomination_id')
nomination = request.POST.get('nomination')
nom_type, slug = nomination.split(' - ')
board_members = request.POST.getlist('board_members[]')
ntype = get_object_or_404(NominationType, id=nom_id)

for member in board_members:
user_obj = get_object_or_404(User, id=int(member))

# unique hash per user
data = [user_obj.get_full_name(), user_obj.email, slug, nom_type, datetime.now()]

hash_ = hashlib.md5(pickle.dumps(data, 0)).hexdigest()
expiry_date = datetime.now() + timedelta(days=10)

host = '{}://{}'.format(settings.SITE_PROTOCOL,
request.META['HTTP_HOST'])
url = '%s%s' % (host, reverse('vote_nominee', kwargs={'nomination': nom_id, 'hash': hash_}))

# store the hash and expiry in DB. Expiry is set to 10 days from date of creation
voting_url = VotingURL(user=user_obj, url_hash=hash_, expiry=expiry_date, ntype=ntype)
voting_url.save()

send_voting_email(user_obj, nom_type, slug, url)

return HttpResponse('Success')


class ViewNominations(ListView, LoginRequiredMixin):
model = Nomination
template_name = 'nominations/nomination_vote.html'
context_object_name = 'nominees'

def dispatch(self, request, *args, **kwargs):
if not is_board_member(self.request.user):
return HttpResponseForbidden("Sorry! You do not have permission to view the Nominations!")
return super(ViewNominations, self).dispatch(request, *args, **kwargs)

def get_context_data(self, *args, **kwargs):
context = super(
ViewNominations, self).get_context_data(*args, **kwargs)

nomination_id = self.kwargs.get('nomination')
hash_ = self.kwargs.get('hash')

ntype = get_object_or_404(NominationType, id=nomination_id)

# logged in user verification
voting_url_obj = VotingURL.objects.filter(user=self.request.user, url_hash=hash_, ntype=ntype)
if voting_url_obj:
voting_url_obj = voting_url_obj[0]
if voting_url_obj.expiry <= timezone.now():
context['message'] = 'Sorry! Voting is closed.'
return context
# check if the user has already voted
if UserVoting.objects.filter(voting_url=voting_url_obj).exists():
context['message'] = 'Sorry! You have already Voted.'
return context

nominees = Nomination.objects.filter(ntype=nomination_id)
context['nominees'] = nominees
context['nomination'] = '%s - %s' % (ntype.name, ntype.slug)
context['expiry'] = voting_url_obj.expiry
context['hash'] = hash_
context['nomination_id'] = nomination_id
return context

else:
context['message'] = 'Sorry! You are not the intended recipient.'
return context

def post(self, request, *args, **kwargs):
nomination_id = self.kwargs.get('nomination')
hash_ = self.kwargs.get('hash')

nominee = request.POST.get('nominee')
comments = request.POST.get('comments')

ntype = get_object_or_404(NominationType, id=nomination_id)
voting_url_obj = get_object_or_404(VotingURL, user=self.request.user, url_hash=hash_, ntype=ntype)
nomination_obj = get_object_or_404(Nomination, id=nominee)

voting = UserVoting(user=request.user, vote=nomination_obj, voting_url=voting_url_obj, comments=comments)
voting.save()

return HttpResponse('Success')


class VotingSummaryList(ListView, LoginRequiredMixin):
model = UserVoting
template_name = 'nominations/voting_summary.html'
context_object_name = 'summary'

def dispatch(self, request, *args, **kwargs):
if not is_board_member(self.request.user):
return HttpResponseForbidden("Sorry! You do not have permission to view the Nominations!")
return super(VotingSummaryList, self).dispatch(request, *args, **kwargs)

def get_context_data(self, *args, **kwargs):
context = super(
VotingSummaryList, self).get_context_data(*args, **kwargs)

nominations = NominationType.objects.values_list('id', 'name', 'slug').distinct()
nomination_dict = {}
for each in nominations:
nomination_dict[each[0]] = '%s - %s' % (each[1], each[2])

context['nomination_types'] = nomination_dict

return context

def post(self, request, *args, **kwargs):

nomination_id = request.POST.get('nomination_id')
ntype = get_object_or_404(NominationType, id=nomination_id)

# get all the nominations for ntype
nominations = Nomination.objects.filter(ntype=ntype)
vote_summary = []

for each_nom in nominations:
vote_count = UserVoting.objects.filter(vote=each_nom).count()
vote_summary.append({'name': each_nom.fullname, 'vote_count': vote_count, 'profession': each_nom.profession,
'contribution_info': each_nom.contribution_info})
return HttpResponse(json.dumps(vote_summary))
2 changes: 2 additions & 0 deletions pssi/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@

SECRET_KEY = '^@p!fj5df100)%gd7g&$c^7znjs0(uJY6qt/<19M-Zkbymc$|C'

SITE_PROTOCOL = 'http'

# Instamojo (payemnt) link

# Add this in local settings
Expand Down
Loading

0 comments on commit e2e859e

Please sign in to comment.