Merge lp:~widelands-dev/widelands-website/remove_djangoratings into lp:widelands-website

Proposed by kaputtnik
Status: Merged
Merged at revision: 509
Proposed branch: lp:~widelands-dev/widelands-website/remove_djangoratings
Merge into: lp:widelands-website
Diff against target: 1747 lines (+40/-1472)
26 files modified
djangoratings/LICENSE (+0/-22)
djangoratings/__init__.py (+0/-50)
djangoratings/admin.py (+0/-18)
djangoratings/default_settings.py (+0/-5)
djangoratings/exceptions.py (+0/-18)
djangoratings/fields.py (+0/-434)
djangoratings/forms.py (+0/-7)
djangoratings/management/commands/update_recommendations.py (+0/-9)
djangoratings/managers.py (+0/-124)
djangoratings/migrations/0001_initial.py (+0/-90)
djangoratings/models.py (+0/-98)
djangoratings/runtests.py (+0/-31)
djangoratings/templatetags/ratings_old.py (+0/-101)
djangoratings/tests.py (+0/-184)
djangoratings/views.py (+0/-138)
media/css/base.css (+1/-1)
settings.py (+0/-1)
templates/wlmaps/base.html (+1/-2)
templates/wlmaps/index.html (+1/-3)
templates/wlmaps/map_detail.html (+3/-27)
wlmaps/migrations/0002_auto_20181119_1855.py (+33/-0)
wlmaps/models.py (+0/-3)
wlmaps/templatetags/wlmaps_extra.py (+0/-15)
wlmaps/tests/test_views.py (+0/-63)
wlmaps/urls.py (+1/-3)
wlmaps/views.py (+0/-25)
To merge this branch: bzr merge lp:~widelands-dev/widelands-website/remove_djangoratings
Reviewer Review Type Date Requested Status
GunChleoc Approve
Review via email: mp+358960@code.launchpad.net

Commit message

Remove djangoratings

Description of the change

To post a comment you must log in.
Revision history for this message
GunChleoc (gunchleoc) wrote :

LGTM :)

review: Approve
510. By kaputtnik

merged trunk

511. By kaputtnik

for some reason django complains about missing migrations; Will apply other directly on the server

Revision history for this message
kaputtnik (franku) wrote :

This is merged for now, but not commited. There is a problem showing the stars for voting, which i couldn't solve so far. At home all is fine, but on the server i struggle with the served star images...

512. By kaputtnik

style fix

Revision history for this message
kaputtnik (franku) wrote :

Ok, this is merged and deployed now. I will explain in another merge request why this didn't worked right away.

What remains is the deletion of the djangoratings related tables in the mysql Database. But i saw on the server there are several old tables in the Database. No idea if having unused tables in the Database do affect the performance. I will create a new bugreport for this issue.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Cleaning up unused stuff is always a good idea :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed directory 'djangoratings'
=== removed file 'djangoratings/LICENSE'
--- djangoratings/LICENSE 2016-05-18 19:31:46 +0000
+++ djangoratings/LICENSE 1970-01-01 00:00:00 +0000
@@ -1,22 +0,0 @@
1Copyright (c) 2009, David Cramer <dcramer@gmail.com>
2All rights reserved.
3
4Redistribution and use in source and binary forms, with or without modification,
5are permitted provided that the following conditions are met:
6
7* Redistributions of source code must retain the above copyright notice, this
8list of conditions and the following disclaimer.
9* Redistributions in binary form must reproduce the above copyright notice,
10this list of conditions and the following disclaimer in the documentation
11and/or other materials provided with the distribution.
12
13THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
230
=== removed file 'djangoratings/__init__.py'
--- djangoratings/__init__.py 2016-12-13 18:28:51 +0000
+++ djangoratings/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,50 +0,0 @@
1import os.path
2import warnings
3
4__version__ = (0, 3, 7)
5
6
7def _get_git_revision(path):
8 revision_file = os.path.join(path, 'refs', 'heads', 'master')
9 if not os.path.exists(revision_file):
10 return None
11 fh = open(revision_file, 'r')
12 try:
13 return fh.read()
14 finally:
15 fh.close()
16
17
18def get_revision():
19 """
20 :returns: Revision number of this branch/checkout, if available. None if
21 no revision number can be determined.
22 """
23 package_dir = os.path.dirname(__file__)
24 checkout_dir = os.path.normpath(os.path.join(package_dir, '..'))
25 path = os.path.join(checkout_dir, '.git')
26 if os.path.exists(path):
27 return _get_git_revision(path)
28 return None
29
30__build__ = get_revision()
31
32
33def lazy_object(location):
34 def inner(*args, **kwargs):
35 parts = location.rsplit('.', 1)
36 warnings.warn('`djangoratings.%s` is deprecated. Please use `%s` instead.' % (
37 parts[1], location), DeprecationWarning)
38 try:
39 imp = __import__(parts[0], globals(), locals(), [parts[1]], -1)
40 except:
41 imp = __import__(parts[0], globals(), locals(), [parts[1]])
42 func = getattr(imp, parts[1])
43 if callable(func):
44 return func(*args, **kwargs)
45 return func
46 return inner
47
48RatingField = lazy_object('djangoratings.fields.RatingField')
49AnonymousRatingField = lazy_object('djangoratings.fields.AnonymousRatingField')
50Rating = lazy_object('djangoratings.fields.Rating')
510
=== removed file 'djangoratings/admin.py'
--- djangoratings/admin.py 2016-12-13 18:28:51 +0000
+++ djangoratings/admin.py 1970-01-01 00:00:00 +0000
@@ -1,18 +0,0 @@
1from django.contrib import admin
2from models import Vote, Score
3
4
5class VoteAdmin(admin.ModelAdmin):
6 list_display = ('content_object', 'user', 'ip_address',
7 'cookie', 'score', 'date_changed')
8 list_filter = ('score', 'content_type', 'date_changed')
9 search_fields = ('ip_address',)
10 raw_id_fields = ('user',)
11
12
13class ScoreAdmin(admin.ModelAdmin):
14 list_display = ('content_object', 'score', 'votes')
15 list_filter = ('content_type',)
16
17admin.site.register(Vote, VoteAdmin)
18admin.site.register(Score, ScoreAdmin)
190
=== removed file 'djangoratings/default_settings.py'
--- djangoratings/default_settings.py 2016-12-13 18:28:51 +0000
+++ djangoratings/default_settings.py 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1from django.conf import settings
2
3# Used to limit the number of unique IPs that can vote on a single object+field.
4# useful if you're getting rating spam by users registering multiple accounts
5RATINGS_VOTES_PER_IP = 3
60
=== removed file 'djangoratings/exceptions.py'
--- djangoratings/exceptions.py 2016-12-13 18:28:51 +0000
+++ djangoratings/exceptions.py 1970-01-01 00:00:00 +0000
@@ -1,18 +0,0 @@
1class InvalidRating(ValueError):
2 pass
3
4
5class AuthRequired(TypeError):
6 pass
7
8
9class CannotChangeVote(Exception):
10 pass
11
12
13class CannotDeleteVote(Exception):
14 pass
15
16
17class IPLimitReached(Exception):
18 pass
190
=== removed file 'djangoratings/fields.py'
--- djangoratings/fields.py 2018-04-08 14:29:44 +0000
+++ djangoratings/fields.py 1970-01-01 00:00:00 +0000
@@ -1,434 +0,0 @@
1from django.db.models import IntegerField, PositiveIntegerField
2from django.conf import settings
3
4import forms
5import itertools
6from datetime import datetime
7
8from models import Vote, Score
9from default_settings import RATINGS_VOTES_PER_IP
10from exceptions import *
11
12if 'django.contrib.contenttypes' not in settings.INSTALLED_APPS:
13 raise ImportError(
14 'djangoratings requires django.contrib.contenttypes in your INSTALLED_APPS')
15
16from django.contrib.contenttypes.models import ContentType
17
18__all__ = ('Rating', 'RatingField', 'AnonymousRatingField')
19
20try:
21 from hashlib import md5
22except ImportError:
23 from md5 import new as md5
24
25try:
26 from django.utils.timezone import now
27except ImportError:
28 now = datetime.now
29
30
31def md5_hexdigest(value):
32 return md5(value).hexdigest()
33
34
35class Rating(object):
36
37 def __init__(self, score, votes):
38 self.score = score
39 self.votes = votes
40
41
42class RatingManager(object):
43
44 def __init__(self, instance, field):
45 self.content_type = None
46 self.instance = instance
47 self.field = field
48
49 self.votes_field_name = '%s_votes' % (self.field.name,)
50 self.score_field_name = '%s_score' % (self.field.name,)
51
52 def get_percent(self):
53 """get_percent()
54
55 Returns the weighted percentage of the score from min-max values
56
57 """
58 if not (self.votes and self.score):
59 return 0
60 return 100 * (self.get_rating() / self.field.range)
61
62 def get_real_percent(self):
63 """get_real_percent()
64
65 Returns the unmodified percentage of the score based on a 0-point scale.
66
67 """
68 if not (self.votes and self.score):
69 return 0
70 return 100 * (self.get_real_rating() / self.field.range)
71
72 def get_ratings(self):
73 """get_ratings()
74
75 Returns a Vote QuerySet for this rating field.
76
77 """
78 return Vote.objects.filter(content_type=self.get_content_type(), object_id=self.instance.pk, key=self.field.key)
79
80 def get_rating(self):
81 """get_rating()
82
83 Returns the weighted average rating.
84
85 """
86 if not (self.votes and self.score):
87 return 0
88 return float(self.score) / (self.votes + self.field.weight)
89
90 def get_opinion_percent(self):
91 """get_opinion_percent()
92
93 Returns a neutral-based percentage.
94
95 """
96 return (self.get_percent() + 100) / 2
97
98 def get_real_rating(self):
99 """get_rating()
100
101 Returns the unmodified average rating.
102
103 """
104 if not (self.votes and self.score):
105 return 0
106 return float(self.score) / self.votes
107
108 def get_rating_for_user(self, user, ip_address=None, cookies={}):
109 """get_rating_for_user(user, ip_address=None, cookie=None)
110
111 Returns the rating for a user or anonymous IP."""
112 kwargs = dict(
113 content_type=self.get_content_type(),
114 object_id=self.instance.pk,
115 key=self.field.key,
116 )
117
118 if not (user and user.is_authenticated):
119 if not ip_address:
120 raise ValueError('``user`` or ``ip_address`` must be present.')
121 kwargs['user__isnull'] = True
122 kwargs['ip_address'] = ip_address
123 else:
124 kwargs['user'] = user
125
126 use_cookies = (self.field.allow_anonymous and self.field.use_cookies)
127 if use_cookies:
128 # TODO: move 'vote-%d.%d.%s' to settings or something
129 cookie_name = 'vote-%d.%d.%s' % (kwargs['content_type'].pk, kwargs[
130 'object_id'], kwargs['key'][:6],) # -> md5_hexdigest?
131 cookie = cookies.get(cookie_name)
132 if cookie:
133 kwargs['cookie'] = cookie
134 else:
135 kwargs['cookie__isnull'] = True
136
137 try:
138 rating = Vote.objects.get(**kwargs)
139 return rating.score
140 except Vote.MultipleObjectsReturned:
141 pass
142 except Vote.DoesNotExist:
143 pass
144 return
145
146 def get_iterable_range(self):
147 # started from 1, because 0 is equal to delete
148 return range(1, self.field.range)
149
150 def add(self, score, user, ip_address, cookies={}, commit=True):
151 """add(score, user, ip_address)
152
153 Used to add a rating to an object.
154
155 """
156 try:
157 score = int(score)
158 except (ValueError, TypeError):
159 raise InvalidRating('%s is not a valid choice for %s' %
160 (score, self.field.name))
161
162 delete = (score == 0)
163 if delete and not self.field.allow_delete:
164 raise CannotDeleteVote(
165 'you are not allowed to delete votes for %s' % (self.field.name,))
166 # ... you're also can't delete your vote if you haven't permissions to change it. I leave this case for CannotChangeVote
167
168 if score < 0 or score > self.field.range:
169 raise InvalidRating('%s is not a valid choice for %s' %
170 (score, self.field.name))
171
172 is_anonymous = (user is None or not user.is_authenticated)
173 if is_anonymous and not self.field.allow_anonymous:
174 raise AuthRequired("user must be a user, not '%r'" % (user,))
175
176 if is_anonymous:
177 user = None
178
179 defaults = dict(
180 score=score,
181 ip_address=ip_address,
182 )
183
184 kwargs = dict(
185 content_type=self.get_content_type(),
186 object_id=self.instance.pk,
187 key=self.field.key,
188 user=user,
189 )
190 if not user:
191 kwargs['ip_address'] = ip_address
192
193 use_cookies = (self.field.allow_anonymous and self.field.use_cookies)
194 if use_cookies:
195 defaults['cookie'] = now().strftime(
196 '%Y%m%d%H%M%S%f') # -> md5_hexdigest?
197 # TODO: move 'vote-%d.%d.%s' to settings or something
198 cookie_name = 'vote-%d.%d.%s' % (kwargs['content_type'].pk, kwargs[
199 'object_id'], kwargs['key'][:6],) # -> md5_hexdigest?
200 # try to get existent cookie value
201 cookie = cookies.get(cookie_name)
202 if not cookie:
203 kwargs['cookie__isnull'] = True
204 kwargs['cookie'] = cookie
205
206 try:
207 rating, created = Vote.objects.get(**kwargs), False
208 except Vote.DoesNotExist:
209 if delete:
210 raise CannotDeleteVote(
211 'attempt to find and delete your vote for %s is failed' % (self.field.name,))
212 if getattr(settings, 'RATINGS_VOTES_PER_IP', RATINGS_VOTES_PER_IP):
213 num_votes = Vote.objects.filter(
214 content_type=kwargs['content_type'],
215 object_id=kwargs['object_id'],
216 key=kwargs['key'],
217 ip_address=ip_address,
218 ).count()
219 if num_votes >= getattr(settings, 'RATINGS_VOTES_PER_IP', RATINGS_VOTES_PER_IP):
220 raise IPLimitReached()
221 kwargs.update(defaults)
222 if use_cookies:
223 # record with specified cookie was not found ...
224 # ... thus we need to replace old cookie (if presented) with new one
225 cookie = defaults['cookie']
226 # ... and remove 'cookie__isnull' (if presented) from .create()'s **kwargs
227 kwargs.pop('cookie__isnull', '')
228 rating, created = Vote.objects.create(**kwargs), True
229
230 has_changed = False
231 if not created:
232 if self.field.can_change_vote:
233 has_changed = True
234 self.score -= rating.score
235 # you can delete your vote only if you have permission to
236 # change your vote
237 if not delete:
238 rating.score = score
239 rating.save()
240 else:
241 self.votes -= 1
242 rating.delete()
243 else:
244 raise CannotChangeVote()
245 else:
246 has_changed = True
247 self.votes += 1
248 if has_changed:
249 if not delete:
250 self.score += rating.score
251 if commit:
252 self.instance.save()
253 #setattr(self.instance, self.field.name, Rating(score=self.score, votes=self.votes))
254
255 defaults = dict(
256 score=self.score,
257 votes=self.votes,
258 )
259
260 kwargs = dict(
261 content_type=self.get_content_type(),
262 object_id=self.instance.pk,
263 key=self.field.key,
264 )
265
266 try:
267 score, created = Score.objects.get(**kwargs), False
268 except Score.DoesNotExist:
269 kwargs.update(defaults)
270 score, created = Score.objects.create(**kwargs), True
271
272 if not created:
273 score.__dict__.update(defaults)
274 score.save()
275
276 # return value
277 adds = {}
278 if use_cookies:
279 adds['cookie_name'] = cookie_name
280 adds['cookie'] = cookie
281 if delete:
282 adds['deleted'] = True
283 return adds
284
285 def delete(self, user, ip_address, cookies={}, commit=True):
286 return self.add(0, user, ip_address, cookies, commit)
287
288 def _get_votes(self, default=None):
289 return getattr(self.instance, self.votes_field_name, default)
290
291 def _set_votes(self, value):
292 return setattr(self.instance, self.votes_field_name, value)
293
294 votes = property(_get_votes, _set_votes)
295
296 def _get_score(self, default=None):
297 return getattr(self.instance, self.score_field_name, default)
298
299 def _set_score(self, value):
300 return setattr(self.instance, self.score_field_name, value)
301
302 score = property(_get_score, _set_score)
303
304 def get_content_type(self):
305 if self.content_type is None:
306 self.content_type = ContentType.objects.get_for_model(
307 self.instance)
308 return self.content_type
309
310 def _update(self, commit=False):
311 """Forces an update of this rating (useful for when Vote objects are
312 removed)."""
313 votes = Vote.objects.filter(
314 content_type=self.get_content_type(),
315 object_id=self.instance.pk,
316 key=self.field.key,
317 )
318 obj_score = sum([v.score for v in votes])
319 obj_votes = len(votes)
320
321 score, created = Score.objects.get_or_create(
322 content_type=self.get_content_type(),
323 object_id=self.instance.pk,
324 key=self.field.key,
325 defaults=dict(
326 score=obj_score,
327 votes=obj_votes,
328 )
329 )
330 if not created:
331 score.score = obj_score
332 score.votes = obj_votes
333 score.save()
334 self.score = obj_score
335 self.votes = obj_votes
336 if commit:
337 self.instance.save()
338
339
340class RatingCreator(object):
341
342 def __init__(self, field):
343 self.field = field
344 self.votes_field_name = '%s_votes' % (self.field.name,)
345 self.score_field_name = '%s_score' % (self.field.name,)
346
347 def __get__(self, instance, type=None):
348 if instance is None:
349 return self.field
350 #raise AttributeError('Can only be accessed via an instance.')
351 return RatingManager(instance, self.field)
352
353 def __set__(self, instance, value):
354 if isinstance(value, Rating):
355 setattr(instance, self.votes_field_name, value.votes)
356 setattr(instance, self.score_field_name, value.score)
357 else:
358 raise TypeError("%s value must be a Rating instance, not '%r'" % (
359 self.field.name, value))
360
361
362class RatingField(IntegerField):
363 """A rating field contributes two columns to the model instead of the
364 standard single column."""
365
366 def __init__(self, *args, **kwargs):
367 if 'choices' in kwargs:
368 raise TypeError("%s invalid attribute 'choices'" %
369 (self.__class__.__name__,))
370 self.can_change_vote = kwargs.pop('can_change_vote', False)
371 self.weight = kwargs.pop('weight', 0)
372 self.range = kwargs.pop('range', 2)
373 self.allow_anonymous = kwargs.pop('allow_anonymous', False)
374 self.use_cookies = kwargs.pop('use_cookies', False)
375 self.allow_delete = kwargs.pop('allow_delete', False)
376 kwargs['editable'] = False
377 kwargs['default'] = 0
378 kwargs['blank'] = True
379 super(RatingField, self).__init__(*args, **kwargs)
380
381 def contribute_to_class(self, cls, name):
382 self.name = name
383
384 # Votes tally field
385 self.votes_field = PositiveIntegerField(
386 editable=False, default=0, blank=True)
387 cls.add_to_class('%s_votes' % (self.name,), self.votes_field)
388
389 # Score sum field
390 self.score_field = IntegerField(
391 editable=False, default=0, blank=True)
392 cls.add_to_class('%s_score' % (self.name,), self.score_field)
393
394 self.key = md5_hexdigest(self.name)
395
396 field = RatingCreator(self)
397
398 if not hasattr(cls, '_djangoratings'):
399 cls._djangoratings = []
400 cls._djangoratings.append(self)
401
402 setattr(cls, name, field)
403
404 def get_db_prep_save(self, value):
405 # XXX: what happens here?
406 pass
407
408 def get_db_prep_lookup(self, lookup_type, value):
409 # TODO: hack in support for __score and __votes
410 # TODO: order_by on this field should use the weighted algorithm
411 raise NotImplementedError(self.get_db_prep_lookup)
412 # if lookup_type in ('score', 'votes'):
413 # lookup_type =
414 # return self.score_field.get_db_prep_lookup()
415 if lookup_type == 'exact':
416 return [self.get_db_prep_save(value)]
417 elif lookup_type == 'in':
418 return [self.get_db_prep_save(v) for v in value]
419 else:
420 return super(RatingField, self).get_db_prep_lookup(lookup_type, value)
421
422 def formfield(self, **kwargs):
423 defaults = {'form_class': forms.RatingField}
424 defaults.update(kwargs)
425 return super(RatingField, self).formfield(**defaults)
426
427 # TODO: flatten_data method
428
429
430class AnonymousRatingField(RatingField):
431
432 def __init__(self, *args, **kwargs):
433 kwargs['allow_anonymous'] = True
434 super(AnonymousRatingField, self).__init__(*args, **kwargs)
4350
=== removed file 'djangoratings/forms.py'
--- djangoratings/forms.py 2016-12-13 18:28:51 +0000
+++ djangoratings/forms.py 1970-01-01 00:00:00 +0000
@@ -1,7 +0,0 @@
1from django import forms
2
3__all__ = ('RatingField',)
4
5
6class RatingField(forms.ChoiceField):
7 pass
80
=== removed directory 'djangoratings/management'
=== removed file 'djangoratings/management/__init__.py'
=== removed directory 'djangoratings/management/commands'
=== removed file 'djangoratings/management/commands/__init__.py'
=== removed file 'djangoratings/management/commands/update_recommendations.py'
--- djangoratings/management/commands/update_recommendations.py 2018-04-05 07:30:42 +0000
+++ djangoratings/management/commands/update_recommendations.py 1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
1from django.core.management.base import BaseCommand, CommandError
2
3from djangoratings.models import SimilarUser
4
5
6class Command(BaseCommand):
7
8 def handle(self, *args, **options):
9 SimilarUser.objects.update_recommendations()
100
=== removed file 'djangoratings/managers.py'
--- djangoratings/managers.py 2016-12-13 18:28:51 +0000
+++ djangoratings/managers.py 1970-01-01 00:00:00 +0000
@@ -1,124 +0,0 @@
1from django.db.models import Manager
2from django.db.models.query import QuerySet
3
4from django.contrib.contenttypes.models import ContentType
5import itertools
6
7
8class VoteQuerySet(QuerySet):
9
10 def delete(self, *args, **kwargs):
11 """Handles updating the related `votes` and `score` fields attached to
12 the model."""
13 # XXX: circular import
14 from fields import RatingField
15
16 qs = self.distinct().values_list(
17 'content_type', 'object_id').order_by('content_type')
18
19 to_update = []
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()
23 if model_class:
24 to_update.extend(
25 list(model_class.objects.filter(pk__in=list(objects)[0])))
26
27 retval = super(VoteQuerySet, self).delete(*args, **kwargs)
28
29 # TODO: this could be improved
30 for obj in to_update:
31 for field in getattr(obj, '_djangoratings', []):
32 getattr(obj, field.name)._update(commit=False)
33 obj.save()
34
35 return retval
36
37
38class VoteManager(Manager):
39
40 def get_query_set(self):
41 return VoteQuerySet(self.model)
42
43 def get_for_user_in_bulk(self, objects, user):
44 objects = list(objects)
45 if len(objects) > 0:
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()
49 for obj in objects],
50 user__pk=user.id))
51 vote_dict = dict([(vote.object_id, vote) for vote in votes])
52 else:
53 vote_dict = {}
54 return vote_dict
55
56
57class SimilarUserManager(Manager):
58
59 def get_recommendations(self, user, model_class, min_score=1):
60 from djangoratings.models import Vote, IgnoredObject
61
62 content_type = ContentType.objects.get_for_model(model_class)
63
64 params = dict(
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,
69 )
70
71 objects = model_class._default_manager.extra(
72 tables=[params['v']],
73 where=[
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,
81 ],
82 params=[content_type.id, user.id, min_score, user.id, user.id]
83 ).distinct()
84
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),
89 # ).exclude(
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))
92
93 return objects
94
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
110 from %(t2)s as v1
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,
122 ))
123 cursor.execute('commit')
124 cursor.close()
1250
=== removed directory 'djangoratings/migrations'
=== removed file 'djangoratings/migrations/0001_initial.py'
--- djangoratings/migrations/0001_initial.py 2016-12-13 18:28:51 +0000
+++ djangoratings/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
@@ -1,90 +0,0 @@
1# -*- coding: utf-8 -*-
2from __future__ import unicode_literals
3
4from django.db import models, migrations
5import django.utils.timezone
6from django.conf import settings
7
8
9class Migration(migrations.Migration):
10
11 dependencies = [
12 ('contenttypes', '0002_remove_content_type_name'),
13 migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 ]
15
16 operations = [
17 migrations.CreateModel(
18 name='IgnoredObject',
19 fields=[
20 ('id', models.AutoField(verbose_name='ID',
21 serialize=False, auto_created=True, primary_key=True)),
22 ('object_id', models.PositiveIntegerField()),
23 ('content_type', models.ForeignKey(to='contenttypes.ContentType')),
24 ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
25 ],
26 ),
27 migrations.CreateModel(
28 name='Score',
29 fields=[
30 ('id', models.AutoField(verbose_name='ID',
31 serialize=False, auto_created=True, primary_key=True)),
32 ('object_id', models.PositiveIntegerField()),
33 ('key', models.CharField(max_length=32)),
34 ('score', models.IntegerField()),
35 ('votes', models.PositiveIntegerField()),
36 ('content_type', models.ForeignKey(to='contenttypes.ContentType')),
37 ],
38 ),
39 migrations.CreateModel(
40 name='SimilarUser',
41 fields=[
42 ('id', models.AutoField(verbose_name='ID',
43 serialize=False, auto_created=True, primary_key=True)),
44 ('agrees', models.PositiveIntegerField(default=0)),
45 ('disagrees', models.PositiveIntegerField(default=0)),
46 ('exclude', models.BooleanField(default=False)),
47 ('from_user', models.ForeignKey(
48 related_name='similar_users', to=settings.AUTH_USER_MODEL)),
49 ('to_user', models.ForeignKey(
50 related_name='similar_users_from', to=settings.AUTH_USER_MODEL)),
51 ],
52 ),
53 migrations.CreateModel(
54 name='Vote',
55 fields=[
56 ('id', models.AutoField(verbose_name='ID',
57 serialize=False, auto_created=True, primary_key=True)),
58 ('object_id', models.PositiveIntegerField()),
59 ('key', models.CharField(max_length=32)),
60 ('score', models.IntegerField()),
61 ('ip_address', models.GenericIPAddressField()),
62 ('cookie', models.CharField(max_length=32, null=True, blank=True)),
63 ('date_added', models.DateTimeField(
64 default=django.utils.timezone.now, editable=False)),
65 ('date_changed', models.DateTimeField(
66 default=django.utils.timezone.now, editable=False)),
67 ('content_type', models.ForeignKey(
68 related_name='votes', to='contenttypes.ContentType')),
69 ('user', models.ForeignKey(related_name='votes',
70 blank=True, to=settings.AUTH_USER_MODEL, null=True)),
71 ],
72 ),
73 migrations.AlterUniqueTogether(
74 name='vote',
75 unique_together=set(
76 [('content_type', 'object_id', 'key', 'user', 'ip_address', 'cookie')]),
77 ),
78 migrations.AlterUniqueTogether(
79 name='similaruser',
80 unique_together=set([('from_user', 'to_user')]),
81 ),
82 migrations.AlterUniqueTogether(
83 name='score',
84 unique_together=set([('content_type', 'object_id', 'key')]),
85 ),
86 migrations.AlterUniqueTogether(
87 name='ignoredobject',
88 unique_together=set([('content_type', 'object_id')]),
89 ),
90 ]
910
=== removed file 'djangoratings/migrations/__init__.py'
=== removed file 'djangoratings/models.py'
--- djangoratings/models.py 2016-12-13 18:28:51 +0000
+++ djangoratings/models.py 1970-01-01 00:00:00 +0000
@@ -1,98 +0,0 @@
1from datetime import datetime
2
3from django.db import models
4from django.contrib.contenttypes.models import ContentType
5from django.contrib.contenttypes.fields import GenericForeignKey
6from django.contrib.auth.models import User
7
8try:
9 from django.utils.timezone import now
10except ImportError:
11 now = datetime.now
12
13from managers import VoteManager, SimilarUserManager
14
15
16class Vote(models.Model):
17 content_type = models.ForeignKey(ContentType, related_name='votes')
18 object_id = models.PositiveIntegerField()
19 key = models.CharField(max_length=32)
20 score = models.IntegerField()
21 user = models.ForeignKey(User, blank=True, null=True, related_name='votes')
22 ip_address = models.GenericIPAddressField()
23 cookie = models.CharField(max_length=32, blank=True, null=True)
24 date_added = models.DateTimeField(default=now, editable=False)
25 date_changed = models.DateTimeField(default=now, editable=False)
26
27 objects = VoteManager()
28
29 content_object = GenericForeignKey()
30
31 class Meta:
32 unique_together = (('content_type', 'object_id',
33 'key', 'user', 'ip_address', 'cookie'))
34
35 def __unicode__(self):
36 return u"%s voted %s on %s" % (self.user_display, self.score, self.content_object)
37
38 def save(self, *args, **kwargs):
39 self.date_changed = now()
40 super(Vote, self).save(*args, **kwargs)
41
42 def user_display(self):
43 if self.user:
44 return '%s (%s)' % (self.user.username, self.ip_address)
45 return self.ip_address
46 user_display = property(user_display)
47
48 def partial_ip_address(self):
49 ip = self.ip_address.split('.')
50 ip[-1] = 'xxx'
51 return '.'.join(ip)
52 partial_ip_address = property(partial_ip_address)
53
54
55class Score(models.Model):
56 content_type = models.ForeignKey(ContentType)
57 object_id = models.PositiveIntegerField()
58 key = models.CharField(max_length=32)
59 score = models.IntegerField()
60 votes = models.PositiveIntegerField()
61
62 content_object = GenericForeignKey()
63
64 class Meta:
65 unique_together = (('content_type', 'object_id', 'key'),)
66
67 def __unicode__(self):
68 return u"%s scored %s with %s votes" % (self.content_object, self.score, self.votes)
69
70
71class SimilarUser(models.Model):
72 from_user = models.ForeignKey(User, related_name='similar_users')
73 to_user = models.ForeignKey(User, related_name='similar_users_from')
74 agrees = models.PositiveIntegerField(default=0)
75 disagrees = models.PositiveIntegerField(default=0)
76 exclude = models.BooleanField(default=False)
77
78 objects = SimilarUserManager()
79
80 class Meta:
81 unique_together = (('from_user', 'to_user'),)
82
83 def __unicode__(self):
84 print u"%s %s similar to %s" % (self.from_user, self.exclude and 'is not' or 'is', self.to_user)
85
86
87class IgnoredObject(models.Model):
88 user = models.ForeignKey(User)
89 content_type = models.ForeignKey(ContentType)
90 object_id = models.PositiveIntegerField()
91
92 content_object = GenericForeignKey()
93
94 class Meta:
95 unique_together = (('content_type', 'object_id'),)
96
97 def __unicode__(self):
98 return self.content_object
990
=== removed file 'djangoratings/runtests.py'
--- djangoratings/runtests.py 2016-12-13 18:28:51 +0000
+++ djangoratings/runtests.py 1970-01-01 00:00:00 +0000
@@ -1,31 +0,0 @@
1#!/usr/bin/env python
2import sys
3
4from os.path import dirname, abspath
5
6from django.conf import settings
7
8if not settings.configured:
9 settings.configure(
10 DATABASE_ENGINE='sqlite3',
11 INSTALLED_APPS=[
12 'django.contrib.auth',
13 'django.contrib.contenttypes',
14 'djangoratings',
15 ]
16 )
17
18from django.test.simple import run_tests
19
20
21def runtests(*test_args):
22 if not test_args:
23 test_args = ['djangoratings']
24 parent = dirname(abspath(__file__))
25 sys.path.insert(0, parent)
26 failures = run_tests(test_args, verbosity=1, interactive=True)
27 sys.exit(failures)
28
29
30if __name__ == '__main__':
31 runtests(*sys.argv[1:])
320
=== removed directory 'djangoratings/templatetags'
=== removed file 'djangoratings/templatetags/__init__.py'
=== removed file 'djangoratings/templatetags/ratings_old.py'
--- djangoratings/templatetags/ratings_old.py 2018-11-18 16:16:04 +0000
+++ djangoratings/templatetags/ratings_old.py 1970-01-01 00:00:00 +0000
@@ -1,101 +0,0 @@
1"""Template tags for Django."""
2# TODO: add in Jinja tags if Coffin is available
3
4from django import template
5from django.contrib.contenttypes.models import ContentType
6from django.db.models import ObjectDoesNotExist
7
8from djangoratings.models import Vote
9from wl_utils import get_real_ip
10
11register = template.Library()
12
13
14class RatingByRequestNode(template.Node):
15
16 def __init__(self, request, obj, context_var):
17 self.request = request
18 self.obj, self.field_name = obj.split('.')
19 self.context_var = context_var
20
21 def render(self, context):
22 try:
23 request = django.template.Variable(self.request).resolve(context)
24 obj = django.template.Variable(self.obj).resolve(context)
25 field = getattr(obj, self.field_name)
26 except (template.VariableDoesNotExist, AttributeError):
27 return ''
28 try:
29 vote = field.get_rating_for_user(
30 request.user, get_real_ip(request), request.COOKIES)
31 context[self.context_var] = vote
32 except ObjectDoesNotExist:
33 context[self.context_var] = 0
34 return ''
35
36
37def do_rating_by_request(parser, token):
38 """
39 Retrieves the ``Vote`` cast by a user on a particular object and
40 stores it in a context variable. If the user has not voted, the
41 context variable will be 0.
42
43 Example usage::
44
45 {% rating_by_request request on instance as vote %}
46 """
47
48 bits = token.contents.split()
49 if len(bits) != 6:
50 raise template.TemplateSyntaxError(
51 "'%s' tag takes exactly five arguments" % bits[0])
52 if bits[2] != 'on':
53 raise template.TemplateSyntaxError(
54 "second argument to '%s' tag must be 'on'" % bits[0])
55 if bits[4] != 'as':
56 raise template.TemplateSyntaxError(
57 "fourth argument to '%s' tag must be 'as'" % bits[0])
58 return RatingByRequestNode(bits[1], bits[3], bits[5])
59register.tag('rating_by_request', do_rating_by_request)
60
61
62class RatingByUserNode(RatingByRequestNode):
63
64 def render(self, context):
65 try:
66 user = django.template.Variable(self.request).resolve(context)
67 obj = django.template.Variable(self.obj).resolve(context)
68 field = getattr(obj, self.field_name)
69 except template.VariableDoesNotExist:
70 return ''
71 try:
72 vote = field.get_rating_for_user(user)
73 context[self.context_var] = vote
74 except ObjectDoesNotExist:
75 context[self.context_var] = 0
76 return ''
77
78
79def do_rating_by_user(parser, token):
80 """
81 Retrieves the ``Vote`` cast by a user on a particular object and
82 stores it in a context variable. If the user has not voted, the
83 context variable will be 0.
84
85 Example usage::
86
87 {% rating_by_user user on instance as vote %}
88 """
89
90 bits = token.contents.split()
91 if len(bits) != 6:
92 raise template.TemplateSyntaxError(
93 "'%s' tag takes exactly five arguments" % bits[0])
94 if bits[2] != 'on':
95 raise template.TemplateSyntaxError(
96 "second argument to '%s' tag must be 'on'" % bits[0])
97 if bits[4] != 'as':
98 raise template.TemplateSyntaxError(
99 "fourth argument to '%s' tag must be 'as'" % bits[0])
100 return RatingByUserNode(bits[1], bits[3], bits[5])
101register.tag('rating_by_user', do_rating_by_user)
1020
=== removed file 'djangoratings/tests.py'
--- djangoratings/tests.py 2016-12-13 18:28:51 +0000
+++ djangoratings/tests.py 1970-01-01 00:00:00 +0000
@@ -1,184 +0,0 @@
1import unittest
2import random
3
4from django.db import models
5from django.contrib.auth.models import User
6from django.contrib.contenttypes.models import ContentType
7from django.conf import settings
8
9from exceptions import *
10from models import Vote, SimilarUser, IgnoredObject
11from fields import AnonymousRatingField, RatingField
12
13settings.RATINGS_VOTES_PER_IP = 1
14
15
16class RatingTestModel(models.Model):
17 rating = AnonymousRatingField(range=2, can_change_vote=True)
18 rating2 = RatingField(range=2, can_change_vote=False)
19
20 def __unicode__(self):
21 return unicode(self.pk)
22
23
24class RatingTestCase(unittest.TestCase):
25
26 def testRatings(self):
27 instance = RatingTestModel.objects.create()
28
29 # Test adding votes
30 instance.rating.add(score=1, user=None, ip_address='127.0.0.1')
31 self.assertEquals(instance.rating.score, 1)
32 self.assertEquals(instance.rating.votes, 1)
33
34 # Test adding votes
35 instance.rating.add(score=2, user=None, ip_address='127.0.0.2')
36 self.assertEquals(instance.rating.score, 3)
37 self.assertEquals(instance.rating.votes, 2)
38
39 # Test changing of votes
40 instance.rating.add(score=2, user=None, ip_address='127.0.0.1')
41 self.assertEquals(instance.rating.score, 4)
42 self.assertEquals(instance.rating.votes, 2)
43
44 # Test users
45 user = User.objects.create(username=str(random.randint(0, 100000000)))
46 user2 = User.objects.create(username=str(random.randint(0, 100000000)))
47
48 instance.rating.add(score=2, user=user, ip_address='127.0.0.3')
49 self.assertEquals(instance.rating.score, 6)
50 self.assertEquals(instance.rating.votes, 3)
51
52 instance.rating2.add(score=2, user=user, ip_address='127.0.0.3')
53 self.assertEquals(instance.rating2.score, 2)
54 self.assertEquals(instance.rating2.votes, 1)
55
56 self.assertRaises(IPLimitReached, instance.rating2.add,
57 score=2, user=user2, ip_address='127.0.0.3')
58
59 # Test deletion hooks
60 Vote.objects.filter(ip_address='127.0.0.3').delete()
61
62 instance = RatingTestModel.objects.get(pk=instance.pk)
63
64 self.assertEquals(instance.rating.score, 4)
65 self.assertEquals(instance.rating.votes, 2)
66 self.assertEquals(instance.rating2.score, 0)
67 self.assertEquals(instance.rating2.votes, 0)
68
69
70class RecommendationsTestCase(unittest.TestCase):
71
72 def setUp(self):
73 self.instance = RatingTestModel.objects.create()
74 self.instance2 = RatingTestModel.objects.create()
75 self.instance3 = RatingTestModel.objects.create()
76 self.instance4 = RatingTestModel.objects.create()
77 self.instance5 = RatingTestModel.objects.create()
78
79 # Test users
80 self.user = User.objects.create(
81 username=str(random.randint(0, 100000000)))
82 self.user2 = User.objects.create(
83 username=str(random.randint(0, 100000000)))
84
85 def testExclusions(self):
86 Vote.objects.all().delete()
87
88 self.instance.rating.add(
89 score=1, user=self.user, ip_address='127.0.0.1')
90 self.instance2.rating.add(
91 score=1, user=self.user, ip_address='127.0.0.1')
92 self.instance3.rating.add(
93 score=1, user=self.user, ip_address='127.0.0.1')
94 self.instance4.rating.add(
95 score=1, user=self.user, ip_address='127.0.0.1')
96 self.instance5.rating.add(
97 score=1, user=self.user, ip_address='127.0.0.1')
98 self.instance.rating.add(
99 score=1, user=self.user2, ip_address='127.0.0.2')
100
101 # we should only need to call this once
102 SimilarUser.objects.update_recommendations()
103
104 self.assertEquals(SimilarUser.objects.count(), 2)
105
106 recs = list(SimilarUser.objects.get_recommendations(
107 self.user2, RatingTestModel))
108 self.assertEquals(len(recs), 4)
109
110 ct = ContentType.objects.get_for_model(RatingTestModel)
111
112 IgnoredObject.objects.create(
113 user=self.user2, content_type=ct, object_id=self.instance2.pk)
114
115 recs = list(SimilarUser.objects.get_recommendations(
116 self.user2, RatingTestModel))
117 self.assertEquals(len(recs), 3)
118
119 IgnoredObject.objects.create(
120 user=self.user2, content_type=ct, object_id=self.instance3.pk)
121 IgnoredObject.objects.create(
122 user=self.user2, content_type=ct, object_id=self.instance4.pk)
123
124 recs = list(SimilarUser.objects.get_recommendations(
125 self.user2, RatingTestModel))
126 self.assertEquals(len(recs), 1)
127 self.assertEquals(recs, [self.instance5])
128
129 self.instance5.rating.add(
130 score=1, user=self.user2, ip_address='127.0.0.2')
131 recs = list(SimilarUser.objects.get_recommendations(
132 self.user2, RatingTestModel))
133 self.assertEquals(len(recs), 0)
134
135 def testSimilarUsers(self):
136 Vote.objects.all().delete()
137
138 self.instance.rating.add(
139 score=1, user=self.user, ip_address='127.0.0.1')
140 self.instance2.rating.add(
141 score=1, user=self.user, ip_address='127.0.0.1')
142 self.instance3.rating.add(
143 score=1, user=self.user, ip_address='127.0.0.1')
144 self.instance4.rating.add(
145 score=1, user=self.user, ip_address='127.0.0.1')
146 self.instance5.rating.add(
147 score=1, user=self.user, ip_address='127.0.0.1')
148 self.instance.rating.add(
149 score=1, user=self.user2, ip_address='127.0.0.2')
150 self.instance2.rating.add(
151 score=1, user=self.user2, ip_address='127.0.0.2')
152 self.instance3.rating.add(
153 score=1, user=self.user2, ip_address='127.0.0.2')
154
155 SimilarUser.objects.update_recommendations()
156
157 self.assertEquals(SimilarUser.objects.count(), 2)
158
159 recs = list(SimilarUser.objects.get_recommendations(
160 self.user2, RatingTestModel))
161 self.assertEquals(len(recs), 2)
162
163 self.instance4.rating.add(
164 score=1, user=self.user2, ip_address='127.0.0.2')
165
166 SimilarUser.objects.update_recommendations()
167
168 self.assertEquals(SimilarUser.objects.count(), 2)
169
170 recs = list(SimilarUser.objects.get_recommendations(
171 self.user2, RatingTestModel))
172 self.assertEquals(len(recs), 1)
173 self.assertEquals(recs, [self.instance5])
174
175 self.instance5.rating.add(
176 score=1, user=self.user2, ip_address='127.0.0.2')
177
178 SimilarUser.objects.update_recommendations()
179
180 self.assertEquals(SimilarUser.objects.count(), 2)
181
182 recs = list(SimilarUser.objects.get_recommendations(
183 self.user2, RatingTestModel))
184 self.assertEquals(len(recs), 0)
1850
=== removed file 'djangoratings/views.py'
--- djangoratings/views.py 2016-12-13 18:28:51 +0000
+++ djangoratings/views.py 1970-01-01 00:00:00 +0000
@@ -1,138 +0,0 @@
1from django.contrib.contenttypes.models import ContentType
2from django.core.exceptions import ObjectDoesNotExist
3from django.http import HttpResponse, Http404
4
5from exceptions import *
6from django.conf import settings
7from default_settings import RATINGS_VOTES_PER_IP
8from wl_utils import get_real_ip
9
10
11class AddRatingView(object):
12
13 def __call__(self, request, content_type_id, object_id, field_name, score):
14 """__call__(request, content_type_id, object_id, field_name, score)
15
16 Adds a vote to the specified model field.
17
18 """
19
20 try:
21 instance = self.get_instance(content_type_id, object_id)
22 except ObjectDoesNotExist:
23 raise Http404('Object does not exist')
24
25 context = self.get_context(request)
26 context['instance'] = instance
27
28 try:
29 field = getattr(instance, field_name)
30 except AttributeError:
31 return self.invalid_field_response(request, context)
32
33 context.update({
34 'field': field,
35 'score': score,
36 })
37
38 had_voted = bool(field.get_rating_for_user(
39 request.user, get_real_ip(request), request.COOKIES))
40
41 context['had_voted'] = had_voted
42
43 try:
44 adds = field.add(score, request.user,
45 get_real_ip(request), request.COOKIES)
46 except IPLimitReached:
47 return self.too_many_votes_from_ip_response(request, context)
48 except AuthRequired:
49 return self.authentication_required_response(request, context)
50 except InvalidRating:
51 return self.invalid_rating_response(request, context)
52 except CannotChangeVote:
53 return self.cannot_change_vote_response(request, context)
54 except CannotDeleteVote:
55 return self.cannot_delete_vote_response(request, context)
56 if had_voted:
57 return self.rating_changed_response(request, context, adds)
58 return self.rating_added_response(request, context, adds)
59
60 def get_context(self, request, context={}):
61 return context
62
63 def render_to_response(self, template, context, request):
64 raise NotImplementedError
65
66 def too_many_votes_from_ip_response(self, request, context):
67 response = HttpResponse(
68 'Too many votes from this IP address for this object.')
69 return response
70
71 def rating_changed_response(self, request, context, adds={}):
72 response = HttpResponse('Vote changed.')
73 if 'cookie' in adds:
74 cookie_name, cookie = adds['cookie_name'], adds['cookie']
75 if 'deleted' in adds:
76 response.delete_cookie(cookie_name)
77 else:
78 # TODO: move cookie max_age to settings
79 response.set_cookie(cookie_name, cookie, 31536000, path='/')
80 return response
81
82 def rating_added_response(self, request, context, adds={}):
83 response = HttpResponse('Vote recorded.')
84 if 'cookie' in adds:
85 cookie_name, cookie = adds['cookie_name'], adds['cookie']
86 if 'deleted' in adds:
87 response.delete_cookie(cookie_name)
88 else:
89 # TODO: move cookie max_age to settings
90 response.set_cookie(cookie_name, cookie, 31536000, path='/')
91 return response
92
93 def authentication_required_response(self, request, context):
94 response = HttpResponse('You must be logged in to vote.')
95 response.status_code = 403
96 return response
97
98 def cannot_change_vote_response(self, request, context):
99 response = HttpResponse('You have already voted.')
100 response.status_code = 403
101 return response
102
103 def cannot_delete_vote_response(self, request, context):
104 response = HttpResponse('You can\'t delete this vote.')
105 response.status_code = 403
106 return response
107
108 def invalid_field_response(self, request, context):
109 response = HttpResponse('Invalid field name.')
110 response.status_code = 403
111 return response
112
113 def invalid_rating_response(self, request, context):
114 response = HttpResponse('Invalid rating value.')
115 response.status_code = 403
116 return response
117
118 def get_instance(self, content_type_id, object_id):
119 return ContentType.objects.get(pk=content_type_id)\
120 .get_object_for_this_type(pk=object_id)
121
122
123class AddRatingFromModel(AddRatingView):
124
125 def __call__(self, request, model, app_label, object_id, field_name, score):
126 """__call__(request, model, app_label, object_id, field_name, score)
127
128 Adds a vote to the specified model field.
129
130 """
131 try:
132 content_type = ContentType.objects.get(
133 model=model, app_label=app_label)
134 except ContentType.DoesNotExist:
135 raise Http404('Invalid `model` or `app_label`.')
136
137 return super(AddRatingFromModel, self).__call__(request, content_type.id,
138 object_id, field_name, score)
1390
=== modified file 'media/css/base.css'
--- media/css/base.css 2018-11-18 16:16:04 +0000
+++ media/css/base.css 2018-11-21 06:22:00 +0000
@@ -108,7 +108,7 @@
108 font-weight: bold;108 font-weight: bold;
109}109}
110110
111.star-ratings input, button {111.star-ratings input, .star-ratings button {
112 padding: inherit;112 padding: inherit;
113}113}
114114
115115
=== modified file 'settings.py'
--- settings.py 2018-11-19 17:19:29 +0000
+++ settings.py 2018-11-21 06:22:00 +0000
@@ -111,7 +111,6 @@
111 'dj_pagination',111 'dj_pagination',
112 'tagging',112 'tagging',
113 'star_ratings',113 'star_ratings',
114 'djangoratings', # included as wlapp
115]114]
116115
117MIDDLEWARE = [116MIDDLEWARE = [
118117
=== modified file 'templates/wlmaps/base.html'
--- templates/wlmaps/base.html 2018-11-18 11:31:20 +0000
+++ templates/wlmaps/base.html 2018-11-21 06:22:00 +0000
@@ -1,6 +1,5 @@
1{% extends "base.html" %}1{% extends "base.html" %}
2{% load static %}2{% load static %}
3
4{% comment %}3{% comment %}
5 vim:ft=htmldjango4 vim:ft=htmldjango
6{% endcomment %}5{% endcomment %}
@@ -8,7 +7,7 @@
8{% block extra_head %}7{% block extra_head %}
9<link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/forum.css" />8<link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/forum.css" />
10<link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/maps.css" />9<link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/maps.css" />
11<link rel="stylesheet" href="{% static 'star-ratings/css/star-ratings.css' %}">10<link rel="stylesheet" href="{% static 'star-ratings/css/star-ratings.css' %}" />
12<script type="text/javascript" src="{% static 'star-ratings/js/dist/star-ratings.min.js' %}"></script>11<script type="text/javascript" src="{% static 'star-ratings/js/dist/star-ratings.min.js' %}"></script>
13{{block.super}}12{{block.super}}
14{% endblock %}13{% endblock %}
1514
=== modified file 'templates/wlmaps/index.html'
--- templates/wlmaps/index.html 2018-11-18 16:16:04 +0000
+++ templates/wlmaps/index.html 2018-11-21 06:22:00 +0000
@@ -5,11 +5,9 @@
55
6{% load custom_date %}6{% load custom_date %}
7{% load wlprofile_extras %}7{% load wlprofile_extras %}
8{% load wlmaps_extra %}
9{% load threadedcommentstags %}8{% load threadedcommentstags %}
10{% load pagination_tags %}9{% load pagination_tags %}
11{% load ratings %}10{% load ratings %}
12{% load ratings_old %}
1311
14{% block content_header %}12{% block content_header %}
15 <h1>Maps</h1>13 <h1>Maps</h1>
@@ -59,7 +57,7 @@
59 <td class="grey">Rating:</td>57 <td class="grey">Rating:</td>
60 <td>58 <td>
61 {% ratings map read_only template_name='star_rating/average.html' %}59 {% ratings map read_only template_name='star_rating/average.html' %}
62 {{ map.rating|average_rating }} ({{ map.rating.votes }} Votes)</td>60 </td>
63 <td class="spacer"></td>61 <td class="spacer"></td>
64 {% get_comment_count for map as ccount %}62 {% get_comment_count for map as ccount %}
65 <td class="grey">Comments:</td><td>{{ ccount }}</td>63 <td class="grey">Comments:</td><td>{{ ccount }}</td>
6664
=== modified file 'templates/wlmaps/map_detail.html'
--- templates/wlmaps/map_detail.html 2018-11-18 16:16:04 +0000
+++ templates/wlmaps/map_detail.html 2018-11-21 06:22:00 +0000
@@ -4,35 +4,16 @@
4{% endcomment %}4{% endcomment %}
55
6{% load custom_date %}6{% load custom_date %}
7{% load wlmaps_extra %}
8{% load wlprofile_extras %}7{% load wlprofile_extras %}
9{% load threadedcommentstags %}8{% load threadedcommentstags %}
10{% load wl_markdown %}9{% load wl_markdown %}
11{% load ratings %}10{% load ratings %}
12{% load ratings_old %}
1311
14{% block title %}{{ map.name }} - {{ block.super }}{% endblock %}12{% block title %}{{ map.name }} - {{ block.super }}{% endblock %}
1513
16{% block extra_head %}14{% block extra_head %}
17{{ block.super }}15{{ block.super }}
18<link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/comments.css" />16 <link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/comments.css" />
19{% if not user.is_anonymous %}
20<script src="{{ MEDIA_URL}}/js/jquery.sexy-vote.js" type="text/javascript"></script>
21<script type="text/javascript">
22$(function() {
23 $('#vote').sexyVote( {
24 activeImageSrc: "{{ MEDIA_URL }}img/active_star.gif",
25 passiveImageSrc: "{{ MEDIA_URL }}img/passive_star.gif",
26 maxScore: 10,
27 messages: ["","","","","","","","","",""],
28 fn: function(e, score) {
29 $.post("{% url 'wlmaps_rate' map.slug %}",{ vote: score });
30 }
31 });
32});
33</script>
34
35{% endif %}
36{% endblock %}17{% endblock %}
3718
38{% block content_header %}19{% block content_header %}
@@ -97,13 +78,8 @@
97 </tr>78 </tr>
98 <tr>79 <tr>
99 <td class="grey">Rating:</td>80 <td class="grey">Rating:</td>
100 <td>{% ratings map template_name='star_rating/rate.html' %}81 <td>
101 {{ map.rating|average_rating }} ({{ map.rating.votes }} Votes)82 {% ratings map template_name='star_rating/rate.html' %}
102 {% if not user.is_anonymous %}
103 <span id="vote"></span>
104 {% else %}
105 - Login to vote
106 {% endif %}
107 </td>83 </td>
108 </tr>84 </tr>
109 <tr>85 <tr>
11086
=== added file 'wlmaps/migrations/0002_auto_20181119_1855.py'
--- wlmaps/migrations/0002_auto_20181119_1855.py 1970-01-01 00:00:00 +0000
+++ wlmaps/migrations/0002_auto_20181119_1855.py 2018-11-21 06:22:00 +0000
@@ -0,0 +1,33 @@
1# -*- coding: utf-8 -*-
2# Generated by Django 1.11.12 on 2018-11-19 18:55
3from __future__ import unicode_literals
4
5from django.db import migrations, models
6
7
8class Migration(migrations.Migration):
9
10 dependencies = [
11 ('wlmaps', '0001_initial'),
12 ]
13
14 operations = [
15 migrations.RemoveField(
16 model_name='map',
17 name='rating_score',
18 ),
19 migrations.RemoveField(
20 model_name='map',
21 name='rating_votes',
22 ),
23 migrations.AlterField(
24 model_name='map',
25 name='hint',
26 field=models.TextField(blank=True, verbose_name=b'Hint'),
27 ),
28 migrations.AlterField(
29 model_name='map',
30 name='world_name',
31 field=models.CharField(blank=True, max_length=50),
32 ),
33 ]
034
=== modified file 'wlmaps/models.py'
--- wlmaps/models.py 2018-11-18 16:16:04 +0000
+++ wlmaps/models.py 2018-11-21 06:22:00 +0000
@@ -11,8 +11,6 @@
11except ImportError:11except ImportError:
12 notification = None12 notification = None
1313
14from djangoratings.fields import AnonymousRatingField
15
1614
17class Map(models.Model):15class Map(models.Model):
18 name = models.CharField(max_length=255, unique=True)16 name = models.CharField(max_length=255, unique=True)
@@ -38,7 +36,6 @@
38 nr_downloads = models.PositiveIntegerField(36 nr_downloads = models.PositiveIntegerField(
39 verbose_name='Download count', default=0)37 verbose_name='Download count', default=0)
4038
41 rating = AnonymousRatingField(range=10, can_change_vote=True)
4239
43 class Meta:40 class Meta:
44 ordering = ('-pub_date',)41 ordering = ('-pub_date',)
4542
=== removed directory 'wlmaps/templatetags'
=== removed file 'wlmaps/templatetags/__init__.py'
=== removed file 'wlmaps/templatetags/wlmaps_extra.py'
--- wlmaps/templatetags/wlmaps_extra.py 2018-11-18 16:16:04 +0000
+++ wlmaps/templatetags/wlmaps_extra.py 1970-01-01 00:00:00 +0000
@@ -1,15 +0,0 @@
1#!/usr/bin/env python -tt
2# encoding: utf-8
3
4from django import template
5
6register = template.Library()
7
8
9@register.filter
10def average_rating(rating):
11 if rating.votes > 0:
12 avg = '%.1f' % (float(rating.score) / rating.votes)
13 else:
14 avg = '0.0'
15 return 'Old: {}'.format(avg)
160
=== modified file 'wlmaps/tests/test_views.py'
--- wlmaps/tests/test_views.py 2018-04-08 14:40:17 +0000
+++ wlmaps/tests/test_views.py 2018-11-21 06:22:00 +0000
@@ -143,66 +143,3 @@
143 reverse('wlmaps_view', args=('a-map-that-doesnt-exist',)))143 reverse('wlmaps_view', args=('a-map-that-doesnt-exist',)))
144 self.assertEqual(c.status_code, 404)144 self.assertEqual(c.status_code, 404)
145145
146
147############
148# RATING #
149############
150class TestWLMapsViews_Rating(_LoginToSite):
151
152 def setUp(self):
153 _LoginToSite.setUp(self)
154
155 # Add maps
156 nm = Map.objects.create(
157 name='Map',
158 author='Author',
159 w=128,
160 h=64,
161 nr_players=4,
162 descr='a good map to play with',
163 minimap='/wlmaps/minimaps/Map.png',
164 world_name='blackland',
165
166 uploader=self.user,
167 uploader_comment='Rockdamap'
168 )
169 nm.save()
170 self.map = nm
171
172 def test_RatingNonExistingMap_Except404(self):
173 c = self.client.post(
174 reverse('wlmaps_rate', args=('a-map-that-doesnt-exist',)),
175 {'vote': 10})
176 self.assertEqual(c.status_code, 404)
177
178 def test_RatingGet_Except405(self):
179 c = self.client.get(
180 reverse('wlmaps_rate', args=('map',)),
181 {'vote': 10})
182 self.assertEqual(c.status_code, 405)
183
184 def test_RatingInvalidValue_Except400(self):
185 c = self.client.post(
186 reverse('wlmaps_rate', args=('map',)),
187 {'vote': 11})
188 self.assertEqual(c.status_code, 400)
189
190 def test_RatingNonIntegerValue_Except400(self):
191 c = self.client.post(
192 reverse('wlmaps_rate', args=('map',)),
193 {'vote': 'shubidu'})
194 self.assertEqual(c.status_code, 400)
195
196 def test_RatingExistingMap_ExceptCorrectResult(self):
197 c = self.client.post(
198 reverse('wlmaps_rate', args=('map',)),
199 {'vote': 7})
200 # Except redirect
201 self.assertEqual(c.status_code, 302)
202
203 # We have to refetch this map, because
204 # votes doesn't hit the database
205 m = Map.objects.get(slug='map')
206
207 self.assertEqual(m.rating.votes, 1)
208 self.assertEqual(m.rating.score, 7)
209146
=== modified file 'wlmaps/urls.py'
--- wlmaps/urls.py 2016-12-13 18:28:51 +0000
+++ wlmaps/urls.py 2018-11-21 06:22:00 +0000
@@ -4,6 +4,7 @@
4from models import Map4from models import Map
5from views import *5from views import *
66
7
7urlpatterns = [8urlpatterns = [
8 url(r'^$', index, name='wlmaps_index'),9 url(r'^$', index, name='wlmaps_index'),
9 url(r'^upload/$', upload, name='wlmaps_upload'),10 url(r'^upload/$', upload, name='wlmaps_upload'),
@@ -14,7 +15,4 @@
14 edit_comment, name='wlmaps_edit_comment'),15 edit_comment, name='wlmaps_edit_comment'),
15 url(r'^(?P<map_slug>[-\w]+)/download/$',16 url(r'^(?P<map_slug>[-\w]+)/download/$',
16 download, name='wlmaps_download'),17 download, name='wlmaps_download'),
17
18 url(r'^(?P<map_slug>[-\w]+)/rate/$',
19 rate, name='wlmaps_rate'),
20]18]
2119
=== modified file 'wlmaps/views.py'
--- wlmaps/views.py 2018-04-08 14:40:17 +0000
+++ wlmaps/views.py 2018-11-21 06:22:00 +0000
@@ -24,31 +24,6 @@
24 })24 })
2525
2626
27def rate(request, map_slug):
28 """Rate a given map."""
29 if request.method != 'POST':
30 return HttpResponseNotAllowed(['post'])
31
32 m = get_object_or_404(models.Map, slug=map_slug)
33
34 if not 'vote' in request.POST:
35 return HttpResponseBadRequest()
36 try:
37 val = int(request.POST['vote'])
38 except ValueError:
39 return HttpResponseBadRequest()
40
41 if not (0 < val <= 10):
42 return HttpResponseBadRequest()
43
44 m.rating.add(score=val, user=request.user,
45 ip_address=get_real_ip(request))
46
47 # m.save() is called in djangoratings.fields.add for each instance
48
49 return HttpResponseRedirect(reverse('wlmaps_view', kwargs= {'map_slug': m.slug}))
50
51
52def download(request, map_slug):27def download(request, map_slug):
53 """Very simple view that just returns the binary data of this map and28 """Very simple view that just returns the binary data of this map and
54 increases the download count."""29 increases the download count."""

Subscribers

People subscribed via source and target branches