Adding voting workflow for answers and improving the count_votes meth… · pythonDrive/bootcamp@e5c4896 · GitHub
Skip to content

Commit e5c4896

Browse files
Adding voting workflow for answers and improving the count_votes method to avoid race conditions.
1 parent 68ae6d0 commit e5c4896

6 files changed

Lines changed: 90 additions & 17 deletions

File tree

Lines changed: 18 additions & 0 deletions

bootcamp/qa/models.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid
2+
from collections import Counter
23

34
from django.conf import settings
45
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
@@ -106,18 +107,12 @@ def __str__(self):
106107
def count_answers(self):
107108
return Answer.objects.filter(question=self).count()
108109

109-
@property
110-
def count_likers(self):
111-
return self.liked.count()
112-
113110
def count_votes(self):
114-
dvotes = self.votes.filter(value=False).count()
115-
uvotes = self.votes.filter(value=True).count()
116-
self.total_votes = uvotes - dvotes
117-
self.save()
118-
119-
def get_likers(self):
120-
return self.liked.all()
111+
"""Method to update the sum of the total votes. Uses this complex query
112+
to avoid race conditions at database level."""
113+
dic = Counter(self.votes.values_list("value", flat=True))
114+
Question.objects.filter(id=self.id).update(total_votes=dic[True] - dic[False])
115+
self.refresh_from_db()
121116

122117
def get_answers(self):
123118
return Answer.objects.filter(question=self)
@@ -134,6 +129,7 @@ class Answer(models.Model):
134129
content = models.TextField(max_length=2500)
135130
uuid_id = models.UUIDField(
136131
primary_key=True, default=uuid.uuid4, editable=False)
132+
total_votes = models.IntegerField(default=0)
137133
timestamp = models.DateTimeField(auto_now_add=True)
138134
is_answer = models.BooleanField(default=False)
139135
votes = GenericRelation(Vote)
@@ -147,10 +143,11 @@ def __str__(self): # pragma: no cover
147143
return self.content
148144

149145
def count_votes(self):
150-
dvotes = self.votes.filter(value=False).count()
151-
uvotes = self.votes.filter(value=True).count()
152-
self.total_votes = uvotes - dvotes
153-
self.save()
146+
"""Method to update the sum of the total votes. Uses this complex query
147+
to avoid race conditions at database level."""
148+
dic = Counter(self.votes.values_list("value", flat=True))
149+
Answer.objects.filter(uuid_id=self.uuid_id).update(total_votes=dic[True] - dic[False])
150+
self.refresh_from_db()
154151

155152
def accept_answer(self):
156153
answer_set = Answer.objects.filter(question=self.question)

bootcamp/qa/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
url(r'^propose-answer/(?P<question_id>\d+)/$',
1313
views.CreateAnswerView.as_view(), name='propose_answer'),
1414
url(r'^question/vote/$', views.question_vote, name='question_vote'),
15+
url(r'^answer/vote/$', views.answer_vote, name='answer_vote'),
1516
]

bootcamp/qa/views.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,33 @@ def question_vote(request):
9292

9393
else:
9494
return HttpResponseBadRequest(content=_("Wrong request type."))
95+
96+
97+
@login_required
98+
@ajax_required
99+
def answer_vote(request):
100+
"""Function view to receive AJAX call, returns the count of votes a given
101+
answer has recieved."""
102+
if request.method == "POST":
103+
answer_id = request.POST["answer"]
104+
value = None
105+
if request.POST["value"] == "U":
106+
value = True
107+
108+
else:
109+
value = False
110+
111+
answer = Answer.objects.get(uuid_id=answer_id)
112+
try:
113+
answer.votes.update_or_create(
114+
user=request.user, defaults={"value": value}, )
115+
answer.count_votes()
116+
return JsonResponse({"votes": answer.total_votes})
117+
118+
except IntegrityError:
119+
return JsonResponse({'status': 'false',
120+
'message': _("Database integrity error.")},
121+
status=500)
122+
123+
else:
124+
return HttpResponseBadRequest(content=_("Wrong request type."))

bootcamp/static/js/qa.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,31 @@ $(function () {
6868
}
6969
});
7070
});
71+
72+
$(".answer-vote").click(function () {
73+
var span = $(this);
74+
var answer = $(this).closest(".answer").attr("answer-id");
75+
vote = null;
76+
if ($(this).hasClass("up-vote")) {
77+
vote = "U";
78+
} else {
79+
vote = "D";
80+
}
81+
$.ajax({
82+
url: '/qa/answer/vote/',
83+
data: {
84+
'answer': answer,
85+
'value': vote
86+
},
87+
type: 'post',
88+
cache: false,
89+
success: function (data) {
90+
$('.vote', span).removeClass('voted');
91+
if (vote === "U") {
92+
$(span).addClass('voted');
93+
}
94+
$("#answerVotes").text(data.votes);
95+
}
96+
});
97+
});
7198
});

bootcamp/templates/qa/answer_sample.html

Lines changed: 2 additions & 2 deletions

0 commit comments

Comments
 (0)