Skip to content

Commit 9587af9

Browse files
committed
refactor: optimize Poll model methods and improve email invitation handling
- Simplified total_votes and voters methods using Django ORM. - Enhanced update_choices method with bulk_update for efficiency. - Streamlined delete_choices method to use bulk delete. - Improved send_vote_invitations to create and send emails in bulk. - Updated all_tags method to use aggregation for better performance. - Refactored topTagsPercent method to utilize annotations for counting polls.
1 parent a674a30 commit 9587af9

2 files changed

Lines changed: 70 additions & 49 deletions

File tree

approval_polls/models.py

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,10 @@ def total_ballots(self):
3333
return self.ballot_set.count()
3434

3535
def total_votes(self):
36-
v = 0
37-
for c in self.choice_set.all():
38-
v += c.votes()
39-
return v
36+
return Vote.objects.filter(choice__poll=self).count()
4037

4138
def voters(self):
42-
v = []
43-
ballots = self.ballot_set.all()
44-
for ballot in ballots:
45-
v.append(ballot.user)
46-
return v
39+
return list(self.ballot_set.values_list("user", flat=True).distinct())
4740

4841
def __unicode__(self):
4942
return self.question
@@ -56,43 +49,40 @@ def add_choices(self, ids, text_data, link_data):
5649
self.choice_set.create(choice_text=text_data[n], choice_link=link_data[n])
5750

5851
def update_choices(self, ids, text_data, link_data):
59-
for u in ids:
60-
c = Choice.objects.get(id=u)
61-
setattr(c, "choice_text", text_data[u])
62-
if not (c.choice_link is None and len(link_data[u]) == 0):
63-
setattr(c, "choice_link", link_data[u])
64-
c.save()
52+
choices = Choice.objects.filter(id__in=ids)
53+
for choice in choices:
54+
choice.choice_text = text_data[choice.id]
55+
if not (choice.choice_link is None and len(link_data[choice.id]) == 0):
56+
choice.choice_link = link_data[choice.id]
57+
Choice.objects.bulk_update(choices, ["choice_text", "choice_link"])
6558

6659
def delete_choices(self, ids):
67-
for d in ids:
68-
cho_d = Choice.objects.get(id=d)
69-
cho_d.delete()
70-
"""
71-
Recommended by RelatedManager, but hasn't worked locally
72-
self.choice_set.remove(cho_d)
73-
"""
60+
Choice.objects.filter(id__in=ids, poll=self).delete()
7461

7562
def send_vote_invitations(self, emails):
76-
# Get all the email Ids to store in the DB.
77-
email_list = []
78-
for email in emails.split(","):
79-
if re.match(r"([^@|\s]+@[^@]+\.[^@|\s]+)", email.strip()):
80-
email_list.append(email.strip())
81-
email_list = list(set(email_list))
82-
83-
# Add in the vote invitation info, if any.
84-
for email in email_list:
85-
vi = VoteInvitation(
86-
email=email,
87-
sent_date=timezone.now(),
88-
poll=self,
89-
key=VoteInvitation.generate_key(),
63+
# Get unique valid email addresses
64+
email_list = {
65+
email.strip()
66+
for email in emails.split(",")
67+
if re.match(r"([^@|\s]+@[^@]+\.[^@|\s]+)", email.strip())
68+
}
69+
70+
# Create all invitations at once
71+
now = timezone.now()
72+
invitations = [
73+
VoteInvitation(
74+
email=email, sent_date=now, poll=self, key=VoteInvitation.generate_key()
9075
)
91-
vi.save()
92-
vi.send_email()
76+
for email in email_list
77+
]
78+
created_invitations = VoteInvitation.objects.bulk_create(invitations)
79+
80+
# Send emails after creation
81+
for invitation in created_invitations:
82+
invitation.send_email()
9383

9484
def invited_emails(self):
95-
return [str(vi.email) for vi in self.voteinvitation_set.all()]
85+
return list(self.voteinvitation_set.values_list("email", flat=True))
9686

9787
def add_tags(self, tags):
9888
for tagtext in tags:
@@ -110,7 +100,19 @@ def delete_tags(self, tags):
110100
self.polltag_set.remove(tag)
111101

112102
def all_tags(self):
113-
return (",").join([str(t.tag_text) for t in self.polltag_set.all()])
103+
from django.db.models import CharField, Value
104+
from django.db.models.functions import Concat
105+
106+
return (
107+
self.polltag_set.annotate(
108+
str_tag_text=Cast("tag_text", CharField())
109+
).aggregate(
110+
tags=Concat("str_tag_text", output_field=CharField(), separator=",")
111+
)[
112+
"tags"
113+
]
114+
or ""
115+
)
114116

115117
def __str__(self):
116118
return self.question
@@ -229,10 +231,20 @@ class PollTag(models.Model):
229231

230232
@classmethod
231233
def topTagsPercent(cls, count):
232-
pollTags = cls.objects.all()
233-
topTags = sorted(pollTags, key=lambda x: x.polls.count(), reverse=True)[:count]
234-
sumTotalPolls = sum([t.polls.count() for t in topTags])
235-
topTagsDict = {}
236-
for t in topTags:
237-
topTagsDict[t.tag_text] = float(t.polls.count()) / sumTotalPolls * 100
238-
return topTagsDict
234+
from django.db.models import Count, F, FloatField
235+
from django.db.models.functions import Cast
236+
237+
# Get top tags with their poll counts
238+
top_tags = cls.objects.annotate(poll_count=Count("polls")).order_by(
239+
"-poll_count"
240+
)[:count]
241+
242+
# Calculate total polls for percentage
243+
total_polls = sum(tag.poll_count for tag in top_tags)
244+
245+
# Calculate percentages
246+
return (
247+
{tag.tag_text: (tag.poll_count / total_polls * 100) for tag in top_tags}
248+
if total_polls > 0
249+
else {}
250+
)

approval_polls/views.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,14 @@ class DetailView(generic.DetailView):
9595
template_name = "detail.html"
9696

9797
def get_queryset(self):
98-
return Poll.objects.filter(pub_date__lte=timezone.now())
98+
return Poll.objects.filter(pub_date__lte=timezone.now()).prefetch_related(
99+
Prefetch(
100+
"ballot_set",
101+
queryset=Ballot.objects.prefetch_related(
102+
Prefetch("vote_set", queryset=Vote.objects.select_related("choice"))
103+
),
104+
)
105+
)
99106

100107
def get_context_data(self, **kwargs):
101108
context = super(DetailView, self).get_context_data(**kwargs)
@@ -272,7 +279,9 @@ def raw_ballots(request, poll_id):
272279
if not invitations:
273280
return JsonResponse({"error": "Access denied"}, status=403)
274281

275-
ballots = poll.ballot_set.prefetch_related("vote_set")
282+
ballots = poll.ballot_set.prefetch_related(
283+
Prefetch("vote_set", queryset=Vote.objects.select_related("choice"))
284+
)
276285
raw_ballots_data = []
277286
for ballot in ballots:
278287
approved_choice_ids = list(ballot.vote_set.values_list("choice_id", flat=True))

0 commit comments

Comments
 (0)