1
from django.db.models import Manager
2
from django.db.models.query import QuerySet
4
from django.contrib.contenttypes.models import ContentType
8
class VoteQuerySet(QuerySet):
10
def delete(self, *args, **kwargs):
11
"""Handles updating the related `votes` and `score` fields attached to
13
# XXX: circular import
14
from fields import RatingField
16
qs = self.distinct().values_list(
17
'content_type', 'object_id').order_by('content_type')
20
for content_type, objects in itertools.groupby(qs, key=lambda x: x[0]):
21
model_class = ContentType.objects.get(
22
pk=content_type).model_class()
25
list(model_class.objects.filter(pk__in=list(objects)[0])))
27
retval = super(VoteQuerySet, self).delete(*args, **kwargs)
29
# TODO: this could be improved
31
for field in getattr(obj, '_djangoratings', []):
32
getattr(obj, field.name)._update(commit=False)
38
class VoteManager(Manager):
40
def get_query_set(self):
41
return VoteQuerySet(self.model)
43
def get_for_user_in_bulk(self, objects, user):
44
objects = list(objects)
46
ctype = ContentType.objects.get_for_model(objects[0])
47
votes = list(self.filter(content_type__pk=ctype.id,
48
object_id__in=[obj._get_pk_val()
51
vote_dict = dict([(vote.object_id, vote) for vote in votes])
57
class SimilarUserManager(Manager):
59
def get_recommendations(self, user, model_class, min_score=1):
60
from djangoratings.models import Vote, IgnoredObject
62
content_type = ContentType.objects.get_for_model(model_class)
65
v=Vote._meta.db_table,
66
sm=self.model._meta.db_table,
67
m=model_class._meta.db_table,
68
io=IgnoredObject._meta.db_table,
71
objects = model_class._default_manager.extra(
74
'%(v)s.object_id = %(m)s.id and %(v)s.content_type_id = %%s' % params,
75
'%(v)s.user_id IN (select to_user_id from %(sm)s where from_user_id = %%s and exclude = 0)' % params,
76
'%(v)s.score >= %%s' % params,
77
# Exclude already rated maps
78
'%(v)s.object_id NOT IN (select object_id from %(v)s where content_type_id = %(v)s.content_type_id and user_id = %%s)' % params,
79
# IgnoredObject exclusions
80
'%(v)s.object_id NOT IN (select object_id from %(io)s where content_type_id = %(v)s.content_type_id and user_id = %%s)' % params,
82
params=[content_type.id, user.id, min_score, user.id, user.id]
85
# objects = model_class._default_manager.filter(pk__in=content_type.votes.extra(
86
# where=['user_id IN (select to_user_id from %s where from_user_id = %d and exclude = 0)' % (self.model._meta.db_table, user.pk)],
87
# ).filter(score__gte=min_score).exclude(
88
# object_id__in=IgnoredObject.objects.filter(content_type=content_type, user=user).values_list('object_id', flat=True),
90
# object_id__in=Vote.objects.filter(content_type=content_type, user=user).values_list('object_id', flat=True)
91
# ).distinct().values_list('object_id', flat=True))
95
def update_recommendations(self):
96
# TODO: this is mysql only atm
97
# TODO: this doesnt handle scores that have multiple values (e.g. 10 points, 5 stars)
98
# due to it calling an agreement as score = score. We need to loop each rating instance
99
# and express the condition based on the range.
100
from djangoratings.models import Vote
101
from django.db import connection
102
cursor = connection.cursor()
103
cursor.execute('begin')
104
cursor.execute('truncate table %s' % (self.model._meta.db_table,))
105
cursor.execute("""insert into %(t1)s
106
(to_user_id, from_user_id, agrees, disagrees, exclude)
107
select v1.user_id, v2.user_id,
108
sum(if(v2.score = v1.score, 1, 0)) as agrees,
109
sum(if(v2.score != v1.score, 1, 0)) as disagrees, 0
111
inner join %(t2)s as v2
112
on v1.user_id != v2.user_id
113
and v1.object_id = v2.object_id
114
and v1.content_type_id = v2.content_type_id
115
where v1.user_id is not null
116
and v2.user_id is not null
117
group by v1.user_id, v2.user_id
118
having agrees / (disagrees + 0.0001) > 3
119
on duplicate key update agrees = values(agrees), disagrees = values(disagrees);""" % dict(
120
t1=self.model._meta.db_table,
121
t2=Vote._meta.db_table,
123
cursor.execute('commit')