forked from brosner/django-voting
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Set eol-style property to native on files which did not have it set
git-svn-id: https://django-voting.googlecode.com/svn/trunk@58 662f01ad-f42a-0410-a340-718c64ddaef4
- Loading branch information
Showing
1 changed file
with
158 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -1,158 +1,158 @@ | |||
from django.conf import settings | from django.conf import settings | ||
from django.db import connection, models | from django.db import connection, models | ||
from django.contrib.contenttypes.models import ContentType | from django.contrib.contenttypes.models import ContentType | ||
|
|
||
qn = connection.ops.quote_name | qn = connection.ops.quote_name | ||
|
|
||
class VoteManager(models.Manager): | class VoteManager(models.Manager): | ||
def get_score(self, obj): | def get_score(self, obj): | ||
""" | """ | ||
Get a dictionary containing the total score for ``obj`` and | Get a dictionary containing the total score for ``obj`` and | ||
the number of votes it's received. | the number of votes it's received. | ||
""" | """ | ||
query = """ | query = """ | ||
SELECT SUM(vote), COUNT(vote) | SELECT SUM(vote), COUNT(vote) | ||
FROM %s | FROM %s | ||
WHERE content_type_id = %%s | WHERE content_type_id = %%s | ||
AND object_id = %%s""" % qn(self.model._meta.db_table) | AND object_id = %%s""" % qn(self.model._meta.db_table) | ||
ctype = ContentType.objects.get_for_model(obj) | ctype = ContentType.objects.get_for_model(obj) | ||
cursor = connection.cursor() | cursor = connection.cursor() | ||
cursor.execute(query, [ctype.id, obj._get_pk_val()]) | cursor.execute(query, [ctype.id, obj._get_pk_val()]) | ||
result = cursor.fetchall()[0] | result = cursor.fetchall()[0] | ||
# MySQL returns floats and longs respectively for these | # MySQL returns floats and longs respectively for these | ||
# results, so we need to convert them to ints explicitly. | # results, so we need to convert them to ints explicitly. | ||
return { | return { | ||
'score': result[0] and int(result[0]) or 0, | 'score': result[0] and int(result[0]) or 0, | ||
'num_votes': int(result[1]), | 'num_votes': int(result[1]), | ||
} | } | ||
|
|
||
def get_scores_in_bulk(self, objects): | def get_scores_in_bulk(self, objects): | ||
""" | """ | ||
Get a dictionary mapping object ids to total score and number | Get a dictionary mapping object ids to total score and number | ||
of votes for each object. | of votes for each object. | ||
""" | """ | ||
query = """ | query = """ | ||
SELECT object_id, SUM(vote), COUNT(vote) | SELECT object_id, SUM(vote), COUNT(vote) | ||
FROM %s | FROM %s | ||
WHERE content_type_id = %%s | WHERE content_type_id = %%s | ||
AND object_id IN (%s) | AND object_id IN (%s) | ||
GROUP BY object_id""" % ( | GROUP BY object_id""" % ( | ||
qn(self.model._meta.db_table), | qn(self.model._meta.db_table), | ||
','.join(['%s'] * len(objects)) | ','.join(['%s'] * len(objects)) | ||
) | ) | ||
ctype = ContentType.objects.get_for_model(objects[0]) | ctype = ContentType.objects.get_for_model(objects[0]) | ||
cursor = connection.cursor() | cursor = connection.cursor() | ||
cursor.execute(query, [ctype.id] + [obj._get_pk_val() \ | cursor.execute(query, [ctype.id] + [obj._get_pk_val() \ | ||
for obj in objects]) | for obj in objects]) | ||
results = cursor.fetchall() | results = cursor.fetchall() | ||
return dict([(int(object_id), { | return dict([(int(object_id), { | ||
'score': int(score), | 'score': int(score), | ||
'num_votes': int(num_votes), | 'num_votes': int(num_votes), | ||
}) for object_id, score, num_votes in results]) | }) for object_id, score, num_votes in results]) | ||
|
|
||
def record_vote(self, obj, user, vote): | def record_vote(self, obj, user, vote): | ||
""" | """ | ||
Record a user's vote on a given object. Only allows a given user | Record a user's vote on a given object. Only allows a given user | ||
to vote once, though that vote may be changed. | to vote once, though that vote may be changed. | ||
A zero vote indicates that any existing vote should be removed. | A zero vote indicates that any existing vote should be removed. | ||
""" | """ | ||
if vote not in (+1, 0, -1): | if vote not in (+1, 0, -1): | ||
raise ValueError('Invalid vote (must be +1/0/-1)') | raise ValueError('Invalid vote (must be +1/0/-1)') | ||
ctype = ContentType.objects.get_for_model(obj) | ctype = ContentType.objects.get_for_model(obj) | ||
try: | try: | ||
v = self.get(user=user, content_type=ctype, | v = self.get(user=user, content_type=ctype, | ||
object_id=obj._get_pk_val()) | object_id=obj._get_pk_val()) | ||
if vote == 0: | if vote == 0: | ||
v.delete() | v.delete() | ||
else: | else: | ||
v.vote = vote | v.vote = vote | ||
v.save() | v.save() | ||
except models.ObjectDoesNotExist: | except models.ObjectDoesNotExist: | ||
if vote != 0: | if vote != 0: | ||
self.create(user=user, content_type=ctype, | self.create(user=user, content_type=ctype, | ||
object_id=obj._get_pk_val(), vote=vote) | object_id=obj._get_pk_val(), vote=vote) | ||
|
|
||
def get_top(self, Model, limit=10, reversed=False): | def get_top(self, Model, limit=10, reversed=False): | ||
""" | """ | ||
Get the top N scored objects for a given model. | Get the top N scored objects for a given model. | ||
Yields (object, score) tuples. | Yields (object, score) tuples. | ||
""" | """ | ||
ctype = ContentType.objects.get_for_model(Model) | ctype = ContentType.objects.get_for_model(Model) | ||
query = """ | query = """ | ||
SELECT object_id, SUM(vote) as %s | SELECT object_id, SUM(vote) as %s | ||
FROM %s | FROM %s | ||
WHERE content_type_id = %%s | WHERE content_type_id = %%s | ||
GROUP BY object_id""" % ( | GROUP BY object_id""" % ( | ||
qn('score'), | qn('score'), | ||
qn(self.model._meta.db_table), | qn(self.model._meta.db_table), | ||
) | ) | ||
|
|
||
# MySQL has issues with re-using the aggregate function in the | # MySQL has issues with re-using the aggregate function in the | ||
# HAVING clause, so we alias the score and use this alias for | # HAVING clause, so we alias the score and use this alias for | ||
# its benefit. | # its benefit. | ||
if settings.DATABASE_ENGINE == 'mysql': | if settings.DATABASE_ENGINE == 'mysql': | ||
having_score = qn('score') | having_score = qn('score') | ||
else: | else: | ||
having_score = 'SUM(vote)' | having_score = 'SUM(vote)' | ||
if reversed: | if reversed: | ||
having_sql = ' HAVING %(having_score)s < 0 ORDER BY %(having_score)s ASC %(limit_offset)s' | having_sql = ' HAVING %(having_score)s < 0 ORDER BY %(having_score)s ASC %(limit_offset)s' | ||
else: | else: | ||
having_sql = ' HAVING %(having_score)s > 0 ORDER BY %(having_score)s DESC %(limit_offset)s' | having_sql = ' HAVING %(having_score)s > 0 ORDER BY %(having_score)s DESC %(limit_offset)s' | ||
query += having_sql % { | query += having_sql % { | ||
'having_score': having_score, | 'having_score': having_score, | ||
'limit_offset': connection.ops.limit_offset_sql(limit), | 'limit_offset': connection.ops.limit_offset_sql(limit), | ||
} | } | ||
|
|
||
cursor = connection.cursor() | cursor = connection.cursor() | ||
cursor.execute(query, [ctype.id]) | cursor.execute(query, [ctype.id]) | ||
results = cursor.fetchall() | results = cursor.fetchall() | ||
|
|
||
# Use in_bulk() to avoid O(limit) db hits. | # Use in_bulk() to avoid O(limit) db hits. | ||
objects = Model.objects.in_bulk([id for id, score in results]) | objects = Model.objects.in_bulk([id for id, score in results]) | ||
|
|
||
# Yield each object, score pair. Because of the lazy nature of generic | # Yield each object, score pair. Because of the lazy nature of generic | ||
# relations, missing objects are silently ignored. | # relations, missing objects are silently ignored. | ||
for id, score in results: | for id, score in results: | ||
if id in objects: | if id in objects: | ||
yield objects[id], int(score) | yield objects[id], int(score) | ||
|
|
||
def get_bottom(self, Model, limit=10): | def get_bottom(self, Model, limit=10): | ||
""" | """ | ||
Get the bottom (i.e. most negative) N scored objects for a given | Get the bottom (i.e. most negative) N scored objects for a given | ||
model. | model. | ||
Yields (object, score) tuples. | Yields (object, score) tuples. | ||
""" | """ | ||
return self.get_top(Model, limit, True) | return self.get_top(Model, limit, True) | ||
|
|
||
def get_for_user(self, obj, user): | def get_for_user(self, obj, user): | ||
""" | """ | ||
Get the vote made on the given object by the given user, or | Get the vote made on the given object by the given user, or | ||
``None`` if no matching vote exists. | ``None`` if no matching vote exists. | ||
""" | """ | ||
if not user.is_authenticated(): | if not user.is_authenticated(): | ||
return None | return None | ||
ctype = ContentType.objects.get_for_model(obj) | ctype = ContentType.objects.get_for_model(obj) | ||
try: | try: | ||
vote = self.get(content_type=ctype, object_id=obj._get_pk_val(), | vote = self.get(content_type=ctype, object_id=obj._get_pk_val(), | ||
user=user) | user=user) | ||
except models.ObjectDoesNotExist: | except models.ObjectDoesNotExist: | ||
vote = None | vote = None | ||
return vote | return vote | ||
|
|
||
def get_for_user_in_bulk(self, objects, user): | def get_for_user_in_bulk(self, objects, user): | ||
""" | """ | ||
Get a dictionary mapping object ids to votes made by the given | Get a dictionary mapping object ids to votes made by the given | ||
user on the corresponding objects. | user on the corresponding objects. | ||
""" | """ | ||
vote_dict = {} | vote_dict = {} | ||
if len(objects) > 0: | if len(objects) > 0: | ||
ctype = ContentType.objects.get_for_model(objects[0]) | ctype = ContentType.objects.get_for_model(objects[0]) | ||
votes = list(self.filter(content_type__pk=ctype.id, | votes = list(self.filter(content_type__pk=ctype.id, | ||
object_id__in=[obj._get_pk_val() \ | object_id__in=[obj._get_pk_val() \ | ||
for obj in objects], | for obj in objects], | ||
user__pk=user.id)) | user__pk=user.id)) | ||
vote_dict = dict([(vote.object_id, vote) for vote in votes]) | vote_dict = dict([(vote.object_id, vote) for vote in votes]) | ||
return vote_dict | return vote_dict |