← Back to team overview

widelands-dev team mailing list archive

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

 

kaputtnik has proposed merging lp:~widelands-dev/widelands-website/remove_djangoratings into lp:widelands-website.

Commit message:
Remove djangoratings

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1762158 in Widelands Website: "Replace django-ratings with an up to date app"
  https://bugs.launchpad.net/widelands-website/+bug/1762158

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands-website/remove_djangoratings/+merge/358960

Follow up of https://code.launchpad.net/~widelands-dev/widelands-website/replace_djangoratings

Bye, bye djangoratings :-D
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands-website/remove_djangoratings into lp:widelands-website.
=== 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 @@
-Copyright (c) 2009, David Cramer <dcramer@xxxxxxxxx>
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this 
-list of conditions and the following disclaimer.
-* Redistributions in binary form must reproduce the above copyright notice, 
-this list of conditions and the following disclaimer in the documentation 
-and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=== 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 @@
-import os.path
-import warnings
-
-__version__ = (0, 3, 7)
-
-
-def _get_git_revision(path):
-    revision_file = os.path.join(path, 'refs', 'heads', 'master')
-    if not os.path.exists(revision_file):
-        return None
-    fh = open(revision_file, 'r')
-    try:
-        return fh.read()
-    finally:
-        fh.close()
-
-
-def get_revision():
-    """
-    :returns: Revision number of this branch/checkout, if available. None if
-        no revision number can be determined.
-    """
-    package_dir = os.path.dirname(__file__)
-    checkout_dir = os.path.normpath(os.path.join(package_dir, '..'))
-    path = os.path.join(checkout_dir, '.git')
-    if os.path.exists(path):
-        return _get_git_revision(path)
-    return None
-
-__build__ = get_revision()
-
-
-def lazy_object(location):
-    def inner(*args, **kwargs):
-        parts = location.rsplit('.', 1)
-        warnings.warn('`djangoratings.%s` is deprecated. Please use `%s` instead.' % (
-            parts[1], location), DeprecationWarning)
-        try:
-            imp = __import__(parts[0], globals(), locals(), [parts[1]], -1)
-        except:
-            imp = __import__(parts[0], globals(), locals(), [parts[1]])
-        func = getattr(imp, parts[1])
-        if callable(func):
-            return func(*args, **kwargs)
-        return func
-    return inner
-
-RatingField = lazy_object('djangoratings.fields.RatingField')
-AnonymousRatingField = lazy_object('djangoratings.fields.AnonymousRatingField')
-Rating = lazy_object('djangoratings.fields.Rating')

=== 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 @@
-from django.contrib import admin
-from models import Vote, Score
-
-
-class VoteAdmin(admin.ModelAdmin):
-    list_display = ('content_object', 'user', 'ip_address',
-                    'cookie', 'score', 'date_changed')
-    list_filter = ('score', 'content_type', 'date_changed')
-    search_fields = ('ip_address',)
-    raw_id_fields = ('user',)
-
-
-class ScoreAdmin(admin.ModelAdmin):
-    list_display = ('content_object', 'score', 'votes')
-    list_filter = ('content_type',)
-
-admin.site.register(Vote, VoteAdmin)
-admin.site.register(Score, ScoreAdmin)

=== 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 @@
-from django.conf import settings
-
-# Used to limit the number of unique IPs that can vote on a single object+field.
-#   useful if you're getting rating spam by users registering multiple accounts
-RATINGS_VOTES_PER_IP = 3

=== 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 @@
-class InvalidRating(ValueError):
-    pass
-
-
-class AuthRequired(TypeError):
-    pass
-
-
-class CannotChangeVote(Exception):
-    pass
-
-
-class CannotDeleteVote(Exception):
-    pass
-
-
-class IPLimitReached(Exception):
-    pass

=== 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 @@
-from django.db.models import IntegerField, PositiveIntegerField
-from django.conf import settings
-
-import forms
-import itertools
-from datetime import datetime
-
-from models import Vote, Score
-from default_settings import RATINGS_VOTES_PER_IP
-from exceptions import *
-
-if 'django.contrib.contenttypes' not in settings.INSTALLED_APPS:
-    raise ImportError(
-        'djangoratings requires django.contrib.contenttypes in your INSTALLED_APPS')
-
-from django.contrib.contenttypes.models import ContentType
-
-__all__ = ('Rating', 'RatingField', 'AnonymousRatingField')
-
-try:
-    from hashlib import md5
-except ImportError:
-    from md5 import new as md5
-
-try:
-    from django.utils.timezone import now
-except ImportError:
-    now = datetime.now
-
-
-def md5_hexdigest(value):
-    return md5(value).hexdigest()
-
-
-class Rating(object):
-
-    def __init__(self, score, votes):
-        self.score = score
-        self.votes = votes
-
-
-class RatingManager(object):
-
-    def __init__(self, instance, field):
-        self.content_type = None
-        self.instance = instance
-        self.field = field
-
-        self.votes_field_name = '%s_votes' % (self.field.name,)
-        self.score_field_name = '%s_score' % (self.field.name,)
-
-    def get_percent(self):
-        """get_percent()
-
-        Returns the weighted percentage of the score from min-max values
-
-        """
-        if not (self.votes and self.score):
-            return 0
-        return 100 * (self.get_rating() / self.field.range)
-
-    def get_real_percent(self):
-        """get_real_percent()
-
-        Returns the unmodified percentage of the score based on a 0-point scale.
-
-        """
-        if not (self.votes and self.score):
-            return 0
-        return 100 * (self.get_real_rating() / self.field.range)
-
-    def get_ratings(self):
-        """get_ratings()
-
-        Returns a Vote QuerySet for this rating field.
-
-        """
-        return Vote.objects.filter(content_type=self.get_content_type(), object_id=self.instance.pk, key=self.field.key)
-
-    def get_rating(self):
-        """get_rating()
-
-        Returns the weighted average rating.
-
-        """
-        if not (self.votes and self.score):
-            return 0
-        return float(self.score) / (self.votes + self.field.weight)
-
-    def get_opinion_percent(self):
-        """get_opinion_percent()
-
-        Returns a neutral-based percentage.
-
-        """
-        return (self.get_percent() + 100) / 2
-
-    def get_real_rating(self):
-        """get_rating()
-
-        Returns the unmodified average rating.
-
-        """
-        if not (self.votes and self.score):
-            return 0
-        return float(self.score) / self.votes
-
-    def get_rating_for_user(self, user, ip_address=None, cookies={}):
-        """get_rating_for_user(user, ip_address=None, cookie=None)
-
-        Returns the rating for a user or anonymous IP."""
-        kwargs = dict(
-            content_type=self.get_content_type(),
-            object_id=self.instance.pk,
-            key=self.field.key,
-        )
-
-        if not (user and user.is_authenticated):
-            if not ip_address:
-                raise ValueError('``user`` or ``ip_address`` must be present.')
-            kwargs['user__isnull'] = True
-            kwargs['ip_address'] = ip_address
-        else:
-            kwargs['user'] = user
-
-        use_cookies = (self.field.allow_anonymous and self.field.use_cookies)
-        if use_cookies:
-            # TODO: move 'vote-%d.%d.%s' to settings or something
-            cookie_name = 'vote-%d.%d.%s' % (kwargs['content_type'].pk, kwargs[
-                                             'object_id'], kwargs['key'][:6],)  # -> md5_hexdigest?
-            cookie = cookies.get(cookie_name)
-            if cookie:
-                kwargs['cookie'] = cookie
-            else:
-                kwargs['cookie__isnull'] = True
-
-        try:
-            rating = Vote.objects.get(**kwargs)
-            return rating.score
-        except Vote.MultipleObjectsReturned:
-            pass
-        except Vote.DoesNotExist:
-            pass
-        return
-
-    def get_iterable_range(self):
-        # started from 1, because 0 is equal to delete
-        return range(1, self.field.range)
-
-    def add(self, score, user, ip_address, cookies={}, commit=True):
-        """add(score, user, ip_address)
-
-        Used to add a rating to an object.
-
-        """
-        try:
-            score = int(score)
-        except (ValueError, TypeError):
-            raise InvalidRating('%s is not a valid choice for %s' %
-                                (score, self.field.name))
-
-        delete = (score == 0)
-        if delete and not self.field.allow_delete:
-            raise CannotDeleteVote(
-                'you are not allowed to delete votes for %s' % (self.field.name,))
-            # ... you're also can't delete your vote if you haven't permissions to change it. I leave this case for CannotChangeVote
-
-        if score < 0 or score > self.field.range:
-            raise InvalidRating('%s is not a valid choice for %s' %
-                                (score, self.field.name))
-
-        is_anonymous = (user is None or not user.is_authenticated)
-        if is_anonymous and not self.field.allow_anonymous:
-            raise AuthRequired("user must be a user, not '%r'" % (user,))
-
-        if is_anonymous:
-            user = None
-
-        defaults = dict(
-            score=score,
-            ip_address=ip_address,
-        )
-
-        kwargs = dict(
-            content_type=self.get_content_type(),
-            object_id=self.instance.pk,
-            key=self.field.key,
-            user=user,
-        )
-        if not user:
-            kwargs['ip_address'] = ip_address
-
-        use_cookies = (self.field.allow_anonymous and self.field.use_cookies)
-        if use_cookies:
-            defaults['cookie'] = now().strftime(
-                '%Y%m%d%H%M%S%f')  # -> md5_hexdigest?
-            # TODO: move 'vote-%d.%d.%s' to settings or something
-            cookie_name = 'vote-%d.%d.%s' % (kwargs['content_type'].pk, kwargs[
-                                             'object_id'], kwargs['key'][:6],)  # -> md5_hexdigest?
-            # try to get existent cookie value
-            cookie = cookies.get(cookie_name)
-            if not cookie:
-                kwargs['cookie__isnull'] = True
-            kwargs['cookie'] = cookie
-
-        try:
-            rating, created = Vote.objects.get(**kwargs), False
-        except Vote.DoesNotExist:
-            if delete:
-                raise CannotDeleteVote(
-                    'attempt to find and delete your vote for %s is failed' % (self.field.name,))
-            if getattr(settings, 'RATINGS_VOTES_PER_IP', RATINGS_VOTES_PER_IP):
-                num_votes = Vote.objects.filter(
-                    content_type=kwargs['content_type'],
-                    object_id=kwargs['object_id'],
-                    key=kwargs['key'],
-                    ip_address=ip_address,
-                ).count()
-                if num_votes >= getattr(settings, 'RATINGS_VOTES_PER_IP', RATINGS_VOTES_PER_IP):
-                    raise IPLimitReached()
-            kwargs.update(defaults)
-            if use_cookies:
-                # record with specified cookie was not found ...
-                # ... thus we need to replace old cookie (if presented) with new one
-                cookie = defaults['cookie']
-                # ... and remove 'cookie__isnull' (if presented) from .create()'s **kwargs
-                kwargs.pop('cookie__isnull', '')
-            rating, created = Vote.objects.create(**kwargs), True
-
-        has_changed = False
-        if not created:
-            if self.field.can_change_vote:
-                has_changed = True
-                self.score -= rating.score
-                # you can delete your vote only if you have permission to
-                # change your vote
-                if not delete:
-                    rating.score = score
-                    rating.save()
-                else:
-                    self.votes -= 1
-                    rating.delete()
-            else:
-                raise CannotChangeVote()
-        else:
-            has_changed = True
-            self.votes += 1
-        if has_changed:
-            if not delete:
-                self.score += rating.score
-            if commit:
-                self.instance.save()
-            #setattr(self.instance, self.field.name, Rating(score=self.score, votes=self.votes))
-
-            defaults = dict(
-                score=self.score,
-                votes=self.votes,
-            )
-
-            kwargs = dict(
-                content_type=self.get_content_type(),
-                object_id=self.instance.pk,
-                key=self.field.key,
-            )
-
-            try:
-                score, created = Score.objects.get(**kwargs), False
-            except Score.DoesNotExist:
-                kwargs.update(defaults)
-                score, created = Score.objects.create(**kwargs), True
-
-            if not created:
-                score.__dict__.update(defaults)
-                score.save()
-
-        # return value
-        adds = {}
-        if use_cookies:
-            adds['cookie_name'] = cookie_name
-            adds['cookie'] = cookie
-        if delete:
-            adds['deleted'] = True
-        return adds
-
-    def delete(self, user, ip_address, cookies={}, commit=True):
-        return self.add(0, user, ip_address, cookies, commit)
-
-    def _get_votes(self, default=None):
-        return getattr(self.instance, self.votes_field_name, default)
-
-    def _set_votes(self, value):
-        return setattr(self.instance, self.votes_field_name, value)
-
-    votes = property(_get_votes, _set_votes)
-
-    def _get_score(self, default=None):
-        return getattr(self.instance, self.score_field_name, default)
-
-    def _set_score(self, value):
-        return setattr(self.instance, self.score_field_name, value)
-
-    score = property(_get_score, _set_score)
-
-    def get_content_type(self):
-        if self.content_type is None:
-            self.content_type = ContentType.objects.get_for_model(
-                self.instance)
-        return self.content_type
-
-    def _update(self, commit=False):
-        """Forces an update of this rating (useful for when Vote objects are
-        removed)."""
-        votes = Vote.objects.filter(
-            content_type=self.get_content_type(),
-            object_id=self.instance.pk,
-            key=self.field.key,
-        )
-        obj_score = sum([v.score for v in votes])
-        obj_votes = len(votes)
-
-        score, created = Score.objects.get_or_create(
-            content_type=self.get_content_type(),
-            object_id=self.instance.pk,
-            key=self.field.key,
-            defaults=dict(
-                score=obj_score,
-                votes=obj_votes,
-            )
-        )
-        if not created:
-            score.score = obj_score
-            score.votes = obj_votes
-            score.save()
-        self.score = obj_score
-        self.votes = obj_votes
-        if commit:
-            self.instance.save()
-
-
-class RatingCreator(object):
-
-    def __init__(self, field):
-        self.field = field
-        self.votes_field_name = '%s_votes' % (self.field.name,)
-        self.score_field_name = '%s_score' % (self.field.name,)
-
-    def __get__(self, instance, type=None):
-        if instance is None:
-            return self.field
-            #raise AttributeError('Can only be accessed via an instance.')
-        return RatingManager(instance, self.field)
-
-    def __set__(self, instance, value):
-        if isinstance(value, Rating):
-            setattr(instance, self.votes_field_name, value.votes)
-            setattr(instance, self.score_field_name, value.score)
-        else:
-            raise TypeError("%s value must be a Rating instance, not '%r'" % (
-                self.field.name, value))
-
-
-class RatingField(IntegerField):
-    """A rating field contributes two columns to the model instead of the
-    standard single column."""
-
-    def __init__(self, *args, **kwargs):
-        if 'choices' in kwargs:
-            raise TypeError("%s invalid attribute 'choices'" %
-                            (self.__class__.__name__,))
-        self.can_change_vote = kwargs.pop('can_change_vote', False)
-        self.weight = kwargs.pop('weight', 0)
-        self.range = kwargs.pop('range', 2)
-        self.allow_anonymous = kwargs.pop('allow_anonymous', False)
-        self.use_cookies = kwargs.pop('use_cookies', False)
-        self.allow_delete = kwargs.pop('allow_delete', False)
-        kwargs['editable'] = False
-        kwargs['default'] = 0
-        kwargs['blank'] = True
-        super(RatingField, self).__init__(*args, **kwargs)
-
-    def contribute_to_class(self, cls, name):
-        self.name = name
-
-        # Votes tally field
-        self.votes_field = PositiveIntegerField(
-            editable=False, default=0, blank=True)
-        cls.add_to_class('%s_votes' % (self.name,), self.votes_field)
-
-        # Score sum field
-        self.score_field = IntegerField(
-            editable=False, default=0, blank=True)
-        cls.add_to_class('%s_score' % (self.name,), self.score_field)
-
-        self.key = md5_hexdigest(self.name)
-
-        field = RatingCreator(self)
-
-        if not hasattr(cls, '_djangoratings'):
-            cls._djangoratings = []
-        cls._djangoratings.append(self)
-
-        setattr(cls, name, field)
-
-    def get_db_prep_save(self, value):
-        # XXX: what happens here?
-        pass
-
-    def get_db_prep_lookup(self, lookup_type, value):
-        # TODO: hack in support for __score and __votes
-        # TODO: order_by on this field should use the weighted algorithm
-        raise NotImplementedError(self.get_db_prep_lookup)
-        # if lookup_type in ('score', 'votes'):
-        #     lookup_type =
-        #     return self.score_field.get_db_prep_lookup()
-        if lookup_type == 'exact':
-            return [self.get_db_prep_save(value)]
-        elif lookup_type == 'in':
-            return [self.get_db_prep_save(v) for v in value]
-        else:
-            return super(RatingField, self).get_db_prep_lookup(lookup_type, value)
-
-    def formfield(self, **kwargs):
-        defaults = {'form_class': forms.RatingField}
-        defaults.update(kwargs)
-        return super(RatingField, self).formfield(**defaults)
-
-    # TODO: flatten_data method
-
-
-class AnonymousRatingField(RatingField):
-
-    def __init__(self, *args, **kwargs):
-        kwargs['allow_anonymous'] = True
-        super(AnonymousRatingField, self).__init__(*args, **kwargs)

=== 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 @@
-from django import forms
-
-__all__ = ('RatingField',)
-
-
-class RatingField(forms.ChoiceField):
-    pass

=== 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 @@
-from django.core.management.base import BaseCommand, CommandError
-
-from djangoratings.models import SimilarUser
-
-
-class Command(BaseCommand):
-
-    def handle(self, *args, **options):
-        SimilarUser.objects.update_recommendations()

=== 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 @@
-from django.db.models import Manager
-from django.db.models.query import QuerySet
-
-from django.contrib.contenttypes.models import ContentType
-import itertools
-
-
-class VoteQuerySet(QuerySet):
-
-    def delete(self, *args, **kwargs):
-        """Handles updating the related `votes` and `score` fields attached to
-        the model."""
-        # XXX: circular import
-        from fields import RatingField
-
-        qs = self.distinct().values_list(
-            'content_type', 'object_id').order_by('content_type')
-
-        to_update = []
-        for content_type, objects in itertools.groupby(qs, key=lambda x: x[0]):
-            model_class = ContentType.objects.get(
-                pk=content_type).model_class()
-            if model_class:
-                to_update.extend(
-                    list(model_class.objects.filter(pk__in=list(objects)[0])))
-
-        retval = super(VoteQuerySet, self).delete(*args, **kwargs)
-
-        # TODO: this could be improved
-        for obj in to_update:
-            for field in getattr(obj, '_djangoratings', []):
-                getattr(obj, field.name)._update(commit=False)
-            obj.save()
-
-        return retval
-
-
-class VoteManager(Manager):
-
-    def get_query_set(self):
-        return VoteQuerySet(self.model)
-
-    def get_for_user_in_bulk(self, objects, user):
-        objects = list(objects)
-        if len(objects) > 0:
-            ctype = ContentType.objects.get_for_model(objects[0])
-            votes = list(self.filter(content_type__pk=ctype.id,
-                                     object_id__in=[obj._get_pk_val()
-                                                    for obj in objects],
-                                     user__pk=user.id))
-            vote_dict = dict([(vote.object_id, vote) for vote in votes])
-        else:
-            vote_dict = {}
-        return vote_dict
-
-
-class SimilarUserManager(Manager):
-
-    def get_recommendations(self, user, model_class, min_score=1):
-        from djangoratings.models import Vote, IgnoredObject
-
-        content_type = ContentType.objects.get_for_model(model_class)
-
-        params = dict(
-            v=Vote._meta.db_table,
-            sm=self.model._meta.db_table,
-            m=model_class._meta.db_table,
-            io=IgnoredObject._meta.db_table,
-        )
-
-        objects = model_class._default_manager.extra(
-            tables=[params['v']],
-            where=[
-                '%(v)s.object_id = %(m)s.id and %(v)s.content_type_id = %%s' % params,
-                '%(v)s.user_id IN (select to_user_id from %(sm)s where from_user_id = %%s and exclude = 0)' % params,
-                '%(v)s.score >= %%s' % params,
-                # Exclude already rated maps
-                '%(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,
-                # IgnoredObject exclusions
-                '%(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,
-            ],
-            params=[content_type.id, user.id, min_score, user.id, user.id]
-        ).distinct()
-
-        # objects = model_class._default_manager.filter(pk__in=content_type.votes.extra(
-        #     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)],
-        # ).filter(score__gte=min_score).exclude(
-        #     object_id__in=IgnoredObject.objects.filter(content_type=content_type, user=user).values_list('object_id', flat=True),
-        # ).exclude(
-        #     object_id__in=Vote.objects.filter(content_type=content_type, user=user).values_list('object_id', flat=True)
-        # ).distinct().values_list('object_id', flat=True))
-
-        return objects
-
-    def update_recommendations(self):
-        # TODO: this is mysql only atm
-        # TODO: this doesnt handle scores that have multiple values (e.g. 10 points, 5 stars)
-        # due to it calling an agreement as score = score. We need to loop each rating instance
-        # and express the condition based on the range.
-        from djangoratings.models import Vote
-        from django.db import connection
-        cursor = connection.cursor()
-        cursor.execute('begin')
-        cursor.execute('truncate table %s' % (self.model._meta.db_table,))
-        cursor.execute("""insert into %(t1)s
-          (to_user_id, from_user_id, agrees, disagrees, exclude)
-          select v1.user_id, v2.user_id,
-                 sum(if(v2.score = v1.score, 1, 0)) as agrees,
-                 sum(if(v2.score != v1.score, 1, 0)) as disagrees, 0
-            from %(t2)s as v1
-              inner join %(t2)s as v2
-                on v1.user_id != v2.user_id
-                and v1.object_id = v2.object_id
-                and v1.content_type_id = v2.content_type_id
-            where v1.user_id is not null
-              and v2.user_id is not null
-            group by v1.user_id, v2.user_id
-            having agrees / (disagrees + 0.0001) > 3
-          on duplicate key update agrees = values(agrees), disagrees = values(disagrees);""" % dict(
-            t1=self.model._meta.db_table,
-            t2=Vote._meta.db_table,
-        ))
-        cursor.execute('commit')
-        cursor.close()

=== 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 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import models, migrations
-import django.utils.timezone
-from django.conf import settings
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('contenttypes', '0002_remove_content_type_name'),
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='IgnoredObject',
-            fields=[
-                ('id', models.AutoField(verbose_name='ID',
-                                        serialize=False, auto_created=True, primary_key=True)),
-                ('object_id', models.PositiveIntegerField()),
-                ('content_type', models.ForeignKey(to='contenttypes.ContentType')),
-                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
-            ],
-        ),
-        migrations.CreateModel(
-            name='Score',
-            fields=[
-                ('id', models.AutoField(verbose_name='ID',
-                                        serialize=False, auto_created=True, primary_key=True)),
-                ('object_id', models.PositiveIntegerField()),
-                ('key', models.CharField(max_length=32)),
-                ('score', models.IntegerField()),
-                ('votes', models.PositiveIntegerField()),
-                ('content_type', models.ForeignKey(to='contenttypes.ContentType')),
-            ],
-        ),
-        migrations.CreateModel(
-            name='SimilarUser',
-            fields=[
-                ('id', models.AutoField(verbose_name='ID',
-                                        serialize=False, auto_created=True, primary_key=True)),
-                ('agrees', models.PositiveIntegerField(default=0)),
-                ('disagrees', models.PositiveIntegerField(default=0)),
-                ('exclude', models.BooleanField(default=False)),
-                ('from_user', models.ForeignKey(
-                    related_name='similar_users', to=settings.AUTH_USER_MODEL)),
-                ('to_user', models.ForeignKey(
-                    related_name='similar_users_from', to=settings.AUTH_USER_MODEL)),
-            ],
-        ),
-        migrations.CreateModel(
-            name='Vote',
-            fields=[
-                ('id', models.AutoField(verbose_name='ID',
-                                        serialize=False, auto_created=True, primary_key=True)),
-                ('object_id', models.PositiveIntegerField()),
-                ('key', models.CharField(max_length=32)),
-                ('score', models.IntegerField()),
-                ('ip_address', models.GenericIPAddressField()),
-                ('cookie', models.CharField(max_length=32, null=True, blank=True)),
-                ('date_added', models.DateTimeField(
-                    default=django.utils.timezone.now, editable=False)),
-                ('date_changed', models.DateTimeField(
-                    default=django.utils.timezone.now, editable=False)),
-                ('content_type', models.ForeignKey(
-                    related_name='votes', to='contenttypes.ContentType')),
-                ('user', models.ForeignKey(related_name='votes',
-                                           blank=True, to=settings.AUTH_USER_MODEL, null=True)),
-            ],
-        ),
-        migrations.AlterUniqueTogether(
-            name='vote',
-            unique_together=set(
-                [('content_type', 'object_id', 'key', 'user', 'ip_address', 'cookie')]),
-        ),
-        migrations.AlterUniqueTogether(
-            name='similaruser',
-            unique_together=set([('from_user', 'to_user')]),
-        ),
-        migrations.AlterUniqueTogether(
-            name='score',
-            unique_together=set([('content_type', 'object_id', 'key')]),
-        ),
-        migrations.AlterUniqueTogether(
-            name='ignoredobject',
-            unique_together=set([('content_type', 'object_id')]),
-        ),
-    ]

=== 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 @@
-from datetime import datetime
-
-from django.db import models
-from django.contrib.contenttypes.models import ContentType
-from django.contrib.contenttypes.fields import GenericForeignKey
-from django.contrib.auth.models import User
-
-try:
-    from django.utils.timezone import now
-except ImportError:
-    now = datetime.now
-
-from managers import VoteManager, SimilarUserManager
-
-
-class Vote(models.Model):
-    content_type = models.ForeignKey(ContentType, related_name='votes')
-    object_id = models.PositiveIntegerField()
-    key = models.CharField(max_length=32)
-    score = models.IntegerField()
-    user = models.ForeignKey(User, blank=True, null=True, related_name='votes')
-    ip_address = models.GenericIPAddressField()
-    cookie = models.CharField(max_length=32, blank=True, null=True)
-    date_added = models.DateTimeField(default=now, editable=False)
-    date_changed = models.DateTimeField(default=now, editable=False)
-
-    objects = VoteManager()
-
-    content_object = GenericForeignKey()
-
-    class Meta:
-        unique_together = (('content_type', 'object_id',
-                            'key', 'user', 'ip_address', 'cookie'))
-
-    def __unicode__(self):
-        return u"%s voted %s on %s" % (self.user_display, self.score, self.content_object)
-
-    def save(self, *args, **kwargs):
-        self.date_changed = now()
-        super(Vote, self).save(*args, **kwargs)
-
-    def user_display(self):
-        if self.user:
-            return '%s (%s)' % (self.user.username, self.ip_address)
-        return self.ip_address
-    user_display = property(user_display)
-
-    def partial_ip_address(self):
-        ip = self.ip_address.split('.')
-        ip[-1] = 'xxx'
-        return '.'.join(ip)
-    partial_ip_address = property(partial_ip_address)
-
-
-class Score(models.Model):
-    content_type = models.ForeignKey(ContentType)
-    object_id = models.PositiveIntegerField()
-    key = models.CharField(max_length=32)
-    score = models.IntegerField()
-    votes = models.PositiveIntegerField()
-
-    content_object = GenericForeignKey()
-
-    class Meta:
-        unique_together = (('content_type', 'object_id', 'key'),)
-
-    def __unicode__(self):
-        return u"%s scored %s with %s votes" % (self.content_object, self.score, self.votes)
-
-
-class SimilarUser(models.Model):
-    from_user = models.ForeignKey(User, related_name='similar_users')
-    to_user = models.ForeignKey(User, related_name='similar_users_from')
-    agrees = models.PositiveIntegerField(default=0)
-    disagrees = models.PositiveIntegerField(default=0)
-    exclude = models.BooleanField(default=False)
-
-    objects = SimilarUserManager()
-
-    class Meta:
-        unique_together = (('from_user', 'to_user'),)
-
-    def __unicode__(self):
-        print u"%s %s similar to %s" % (self.from_user, self.exclude and 'is not' or 'is', self.to_user)
-
-
-class IgnoredObject(models.Model):
-    user = models.ForeignKey(User)
-    content_type = models.ForeignKey(ContentType)
-    object_id = models.PositiveIntegerField()
-
-    content_object = GenericForeignKey()
-
-    class Meta:
-        unique_together = (('content_type', 'object_id'),)
-
-    def __unicode__(self):
-        return self.content_object

=== 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 @@
-#!/usr/bin/env python
-import sys
-
-from os.path import dirname, abspath
-
-from django.conf import settings
-
-if not settings.configured:
-    settings.configure(
-        DATABASE_ENGINE='sqlite3',
-        INSTALLED_APPS=[
-            'django.contrib.auth',
-            'django.contrib.contenttypes',
-            'djangoratings',
-        ]
-    )
-
-from django.test.simple import run_tests
-
-
-def runtests(*test_args):
-    if not test_args:
-        test_args = ['djangoratings']
-    parent = dirname(abspath(__file__))
-    sys.path.insert(0, parent)
-    failures = run_tests(test_args, verbosity=1, interactive=True)
-    sys.exit(failures)
-
-
-if __name__ == '__main__':
-    runtests(*sys.argv[1:])

=== removed directory 'djangoratings/templatetags'
=== removed file 'djangoratings/templatetags/__init__.py'
=== removed file 'djangoratings/templatetags/ratings.py'
--- djangoratings/templatetags/ratings.py	2018-04-05 07:30:42 +0000
+++ djangoratings/templatetags/ratings.py	1970-01-01 00:00:00 +0000
@@ -1,101 +0,0 @@
-"""Template tags for Django."""
-# TODO: add in Jinja tags if Coffin is available
-
-from django import template
-from django.contrib.contenttypes.models import ContentType
-from django.db.models import ObjectDoesNotExist
-
-from djangoratings.models import Vote
-from wl_utils import get_real_ip
-
-register = template.Library()
-
-
-class RatingByRequestNode(template.Node):
-
-    def __init__(self, request, obj, context_var):
-        self.request = request
-        self.obj, self.field_name = obj.split('.')
-        self.context_var = context_var
-
-    def render(self, context):
-        try:
-            request = django.template.Variable(self.request).resolve(context)
-            obj = django.template.Variable(self.obj).resolve(context)
-            field = getattr(obj, self.field_name)
-        except (template.VariableDoesNotExist, AttributeError):
-            return ''
-        try:
-            vote = field.get_rating_for_user(
-                request.user, get_real_ip(request), request.COOKIES)
-            context[self.context_var] = vote
-        except ObjectDoesNotExist:
-            context[self.context_var] = 0
-        return ''
-
-
-def do_rating_by_request(parser, token):
-    """
-    Retrieves the ``Vote`` cast by a user on a particular object and
-    stores it in a context variable. If the user has not voted, the
-    context variable will be 0.
-
-    Example usage::
-
-        {% rating_by_request request on instance as vote %}
-    """
-
-    bits = token.contents.split()
-    if len(bits) != 6:
-        raise template.TemplateSyntaxError(
-            "'%s' tag takes exactly five arguments" % bits[0])
-    if bits[2] != 'on':
-        raise template.TemplateSyntaxError(
-            "second argument to '%s' tag must be 'on'" % bits[0])
-    if bits[4] != 'as':
-        raise template.TemplateSyntaxError(
-            "fourth argument to '%s' tag must be 'as'" % bits[0])
-    return RatingByRequestNode(bits[1], bits[3], bits[5])
-register.tag('rating_by_request', do_rating_by_request)
-
-
-class RatingByUserNode(RatingByRequestNode):
-
-    def render(self, context):
-        try:
-            user = django.template.Variable(self.request).resolve(context)
-            obj = django.template.Variable(self.obj).resolve(context)
-            field = getattr(obj, self.field_name)
-        except template.VariableDoesNotExist:
-            return ''
-        try:
-            vote = field.get_rating_for_user(user)
-            context[self.context_var] = vote
-        except ObjectDoesNotExist:
-            context[self.context_var] = 0
-        return ''
-
-
-def do_rating_by_user(parser, token):
-    """
-    Retrieves the ``Vote`` cast by a user on a particular object and
-    stores it in a context variable. If the user has not voted, the
-    context variable will be 0.
-
-    Example usage::
-
-        {% rating_by_user user on instance as vote %}
-    """
-
-    bits = token.contents.split()
-    if len(bits) != 6:
-        raise template.TemplateSyntaxError(
-            "'%s' tag takes exactly five arguments" % bits[0])
-    if bits[2] != 'on':
-        raise template.TemplateSyntaxError(
-            "second argument to '%s' tag must be 'on'" % bits[0])
-    if bits[4] != 'as':
-        raise template.TemplateSyntaxError(
-            "fourth argument to '%s' tag must be 'as'" % bits[0])
-    return RatingByUserNode(bits[1], bits[3], bits[5])
-register.tag('rating_by_user', do_rating_by_user)

=== 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 @@
-import unittest
-import random
-
-from django.db import models
-from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
-from django.conf import settings
-
-from exceptions import *
-from models import Vote, SimilarUser, IgnoredObject
-from fields import AnonymousRatingField, RatingField
-
-settings.RATINGS_VOTES_PER_IP = 1
-
-
-class RatingTestModel(models.Model):
-    rating = AnonymousRatingField(range=2, can_change_vote=True)
-    rating2 = RatingField(range=2, can_change_vote=False)
-
-    def __unicode__(self):
-        return unicode(self.pk)
-
-
-class RatingTestCase(unittest.TestCase):
-
-    def testRatings(self):
-        instance = RatingTestModel.objects.create()
-
-        # Test adding votes
-        instance.rating.add(score=1, user=None, ip_address='127.0.0.1')
-        self.assertEquals(instance.rating.score, 1)
-        self.assertEquals(instance.rating.votes, 1)
-
-        # Test adding votes
-        instance.rating.add(score=2, user=None, ip_address='127.0.0.2')
-        self.assertEquals(instance.rating.score, 3)
-        self.assertEquals(instance.rating.votes, 2)
-
-        # Test changing of votes
-        instance.rating.add(score=2, user=None, ip_address='127.0.0.1')
-        self.assertEquals(instance.rating.score, 4)
-        self.assertEquals(instance.rating.votes, 2)
-
-        # Test users
-        user = User.objects.create(username=str(random.randint(0, 100000000)))
-        user2 = User.objects.create(username=str(random.randint(0, 100000000)))
-
-        instance.rating.add(score=2, user=user, ip_address='127.0.0.3')
-        self.assertEquals(instance.rating.score, 6)
-        self.assertEquals(instance.rating.votes, 3)
-
-        instance.rating2.add(score=2, user=user, ip_address='127.0.0.3')
-        self.assertEquals(instance.rating2.score, 2)
-        self.assertEquals(instance.rating2.votes, 1)
-
-        self.assertRaises(IPLimitReached, instance.rating2.add,
-                          score=2, user=user2, ip_address='127.0.0.3')
-
-        # Test deletion hooks
-        Vote.objects.filter(ip_address='127.0.0.3').delete()
-
-        instance = RatingTestModel.objects.get(pk=instance.pk)
-
-        self.assertEquals(instance.rating.score, 4)
-        self.assertEquals(instance.rating.votes, 2)
-        self.assertEquals(instance.rating2.score, 0)
-        self.assertEquals(instance.rating2.votes, 0)
-
-
-class RecommendationsTestCase(unittest.TestCase):
-
-    def setUp(self):
-        self.instance = RatingTestModel.objects.create()
-        self.instance2 = RatingTestModel.objects.create()
-        self.instance3 = RatingTestModel.objects.create()
-        self.instance4 = RatingTestModel.objects.create()
-        self.instance5 = RatingTestModel.objects.create()
-
-        # Test users
-        self.user = User.objects.create(
-            username=str(random.randint(0, 100000000)))
-        self.user2 = User.objects.create(
-            username=str(random.randint(0, 100000000)))
-
-    def testExclusions(self):
-        Vote.objects.all().delete()
-
-        self.instance.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance2.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance3.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance4.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance5.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance.rating.add(
-            score=1, user=self.user2, ip_address='127.0.0.2')
-
-        # we should only need to call this once
-        SimilarUser.objects.update_recommendations()
-
-        self.assertEquals(SimilarUser.objects.count(), 2)
-
-        recs = list(SimilarUser.objects.get_recommendations(
-            self.user2, RatingTestModel))
-        self.assertEquals(len(recs), 4)
-
-        ct = ContentType.objects.get_for_model(RatingTestModel)
-
-        IgnoredObject.objects.create(
-            user=self.user2, content_type=ct, object_id=self.instance2.pk)
-
-        recs = list(SimilarUser.objects.get_recommendations(
-            self.user2, RatingTestModel))
-        self.assertEquals(len(recs), 3)
-
-        IgnoredObject.objects.create(
-            user=self.user2, content_type=ct, object_id=self.instance3.pk)
-        IgnoredObject.objects.create(
-            user=self.user2, content_type=ct, object_id=self.instance4.pk)
-
-        recs = list(SimilarUser.objects.get_recommendations(
-            self.user2, RatingTestModel))
-        self.assertEquals(len(recs), 1)
-        self.assertEquals(recs, [self.instance5])
-
-        self.instance5.rating.add(
-            score=1, user=self.user2, ip_address='127.0.0.2')
-        recs = list(SimilarUser.objects.get_recommendations(
-            self.user2, RatingTestModel))
-        self.assertEquals(len(recs), 0)
-
-    def testSimilarUsers(self):
-        Vote.objects.all().delete()
-
-        self.instance.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance2.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance3.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance4.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance5.rating.add(
-            score=1, user=self.user, ip_address='127.0.0.1')
-        self.instance.rating.add(
-            score=1, user=self.user2, ip_address='127.0.0.2')
-        self.instance2.rating.add(
-            score=1, user=self.user2, ip_address='127.0.0.2')
-        self.instance3.rating.add(
-            score=1, user=self.user2, ip_address='127.0.0.2')
-
-        SimilarUser.objects.update_recommendations()
-
-        self.assertEquals(SimilarUser.objects.count(), 2)
-
-        recs = list(SimilarUser.objects.get_recommendations(
-            self.user2, RatingTestModel))
-        self.assertEquals(len(recs), 2)
-
-        self.instance4.rating.add(
-            score=1, user=self.user2, ip_address='127.0.0.2')
-
-        SimilarUser.objects.update_recommendations()
-
-        self.assertEquals(SimilarUser.objects.count(), 2)
-
-        recs = list(SimilarUser.objects.get_recommendations(
-            self.user2, RatingTestModel))
-        self.assertEquals(len(recs), 1)
-        self.assertEquals(recs, [self.instance5])
-
-        self.instance5.rating.add(
-            score=1, user=self.user2, ip_address='127.0.0.2')
-
-        SimilarUser.objects.update_recommendations()
-
-        self.assertEquals(SimilarUser.objects.count(), 2)
-
-        recs = list(SimilarUser.objects.get_recommendations(
-            self.user2, RatingTestModel))
-        self.assertEquals(len(recs), 0)

=== 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 @@
-from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ObjectDoesNotExist
-from django.http import HttpResponse, Http404
-
-from exceptions import *
-from django.conf import settings
-from default_settings import RATINGS_VOTES_PER_IP
-from wl_utils import get_real_ip
-
-
-class AddRatingView(object):
-
-    def __call__(self, request, content_type_id, object_id, field_name, score):
-        """__call__(request, content_type_id, object_id, field_name, score)
-
-        Adds a vote to the specified model field.
-
-        """
-
-        try:
-            instance = self.get_instance(content_type_id, object_id)
-        except ObjectDoesNotExist:
-            raise Http404('Object does not exist')
-
-        context = self.get_context(request)
-        context['instance'] = instance
-
-        try:
-            field = getattr(instance, field_name)
-        except AttributeError:
-            return self.invalid_field_response(request, context)
-
-        context.update({
-            'field': field,
-            'score': score,
-        })
-
-        had_voted = bool(field.get_rating_for_user(
-            request.user, get_real_ip(request), request.COOKIES))
-
-        context['had_voted'] = had_voted
-
-        try:
-            adds = field.add(score, request.user,
-                             get_real_ip(request), request.COOKIES)
-        except IPLimitReached:
-            return self.too_many_votes_from_ip_response(request, context)
-        except AuthRequired:
-            return self.authentication_required_response(request, context)
-        except InvalidRating:
-            return self.invalid_rating_response(request, context)
-        except CannotChangeVote:
-            return self.cannot_change_vote_response(request, context)
-        except CannotDeleteVote:
-            return self.cannot_delete_vote_response(request, context)
-        if had_voted:
-            return self.rating_changed_response(request, context, adds)
-        return self.rating_added_response(request, context, adds)
-
-    def get_context(self, request, context={}):
-        return context
-
-    def render_to_response(self, template, context, request):
-        raise NotImplementedError
-
-    def too_many_votes_from_ip_response(self, request, context):
-        response = HttpResponse(
-            'Too many votes from this IP address for this object.')
-        return response
-
-    def rating_changed_response(self, request, context, adds={}):
-        response = HttpResponse('Vote changed.')
-        if 'cookie' in adds:
-            cookie_name, cookie = adds['cookie_name'], adds['cookie']
-            if 'deleted' in adds:
-                response.delete_cookie(cookie_name)
-            else:
-                # TODO: move cookie max_age to settings
-                response.set_cookie(cookie_name, cookie, 31536000, path='/')
-        return response
-
-    def rating_added_response(self, request, context, adds={}):
-        response = HttpResponse('Vote recorded.')
-        if 'cookie' in adds:
-            cookie_name, cookie = adds['cookie_name'], adds['cookie']
-            if 'deleted' in adds:
-                response.delete_cookie(cookie_name)
-            else:
-                # TODO: move cookie max_age to settings
-                response.set_cookie(cookie_name, cookie, 31536000, path='/')
-        return response
-
-    def authentication_required_response(self, request, context):
-        response = HttpResponse('You must be logged in to vote.')
-        response.status_code = 403
-        return response
-
-    def cannot_change_vote_response(self, request, context):
-        response = HttpResponse('You have already voted.')
-        response.status_code = 403
-        return response
-
-    def cannot_delete_vote_response(self, request, context):
-        response = HttpResponse('You can\'t delete this vote.')
-        response.status_code = 403
-        return response
-
-    def invalid_field_response(self, request, context):
-        response = HttpResponse('Invalid field name.')
-        response.status_code = 403
-        return response
-
-    def invalid_rating_response(self, request, context):
-        response = HttpResponse('Invalid rating value.')
-        response.status_code = 403
-        return response
-
-    def get_instance(self, content_type_id, object_id):
-        return ContentType.objects.get(pk=content_type_id)\
-            .get_object_for_this_type(pk=object_id)
-
-
-class AddRatingFromModel(AddRatingView):
-
-    def __call__(self, request, model, app_label, object_id, field_name, score):
-        """__call__(request, model, app_label, object_id, field_name, score)
-
-        Adds a vote to the specified model field.
-
-        """
-        try:
-            content_type = ContentType.objects.get(
-                model=model, app_label=app_label)
-        except ContentType.DoesNotExist:
-            raise Http404('Invalid `model` or `app_label`.')
-
-        return super(AddRatingFromModel, self).__call__(request, content_type.id,
-                                                        object_id, field_name, score)

=== modified file 'media/css/base.css'
--- media/css/base.css	2018-10-15 07:13:24 +0000
+++ media/css/base.css	2018-11-18 17:28:32 +0000
@@ -108,6 +108,10 @@
 	font-weight: bold;
 }
 
+.star-ratings input, button {
+	padding: inherit;
+}
+
 textarea {
 	font-weight: normal;
 	font-size: 0.9em;

=== modified file 'pip_requirements.txt'
--- pip_requirements.txt	2018-11-05 17:12:12 +0000
+++ pip_requirements.txt	2018-11-18 17:28:32 +0000
@@ -7,6 +7,7 @@
 # Do not install newer versions because our notifications app is affected
 -e git://github.com/arneb/django-messages.git@2d8dabb755e0b5ace876bde25f45d07c2051ac37#egg=django_messages
 django-nocaptcha-recaptcha==0.0.19
+django-star-ratings==0.7.0
 dj-pagination==2.3.2
 django-registration==2.4.1
 django-tagging==0.4.5

=== modified file 'settings.py'
--- settings.py	2018-11-12 19:36:22 +0000
+++ settings.py	2018-11-18 17:28:32 +0000
@@ -110,7 +110,7 @@
     'django_messages_wl.apps.WLDjangoMessagesConfig',
     'dj_pagination',
     'tagging',
-    'djangoratings',    # included as wlapp
+    'star_ratings',
 ]
 
 MIDDLEWARE = [
@@ -333,6 +333,14 @@
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-age
 CSRF_COOKIE_AGE = None
 
+##############################
+# star_rarting configuration #
+##############################
+
+STAR_RATINGS_STAR_HEIGHT = 14
+STAR_RATINGS_STAR_WIDTH = 14
+STAR_RATINGS_RANGE = 10
+
 try:
     from local_settings import *
 except ImportError:

=== added directory 'templates/star_rating'
=== added file 'templates/star_rating/average.html'
--- templates/star_rating/average.html	1970-01-01 00:00:00 +0000
+++ templates/star_rating/average.html	2018-11-18 17:28:32 +0000
@@ -0,0 +1,7 @@
+{% extends "star_ratings/widget_base.html" %}
+{% block rating_stars %}
+    {# No stars here #}
+{% endblock %}
+{% block rating_detail %}
+{{ rating.average|floatformat:"-2" }} ({{ rating.count }} Vote{{ rating.count|pluralize }})
+{% endblock %}
\ No newline at end of file

=== added file 'templates/star_rating/rate.html'
--- templates/star_rating/rate.html	1970-01-01 00:00:00 +0000
+++ templates/star_rating/rate.html	2018-11-18 17:28:32 +0000
@@ -0,0 +1,27 @@
+{% extends "star_ratings/widget_base.html" %}
+{% load i18n %}
+
+{% block rating_detail %}
+{% if not user.is_authenticated and not anonymous_ratings and not read_only %}
+    <p>
+        {{ rating.average|floatformat:"-2" }} ({{ rating.count }} Vote{{ rating.count|pluralize }})
+        <a href="{% url 'auth_login' %}?next={{ request.path }}">{% trans 'Please log in to vote.' %}</a>
+    </p>
+{% else %}
+    {% block rating_user %}
+    <p class="star-ratings-rating-user">
+        {{ rating.average|floatformat:"-2" }} ({{ rating.count }} Vote{{ rating.count|pluralize }}) - 
+        {% trans 'You voted: ' %}
+        <span class='star-ratings-rating-value'>
+        {% if user_rating %}
+            {{ user_rating.score }}
+        {% else %}
+            {% trans 'Not voted' %}
+        {% endif %}
+        </span>
+    </p>
+    {% endblock rating_user %}
+    <p class="star-ratings-errors"><p>
+{% endif %}
+{% endblock rating_detail %}
+

=== modified file 'templates/wlmaps/base.html'
--- templates/wlmaps/base.html	2015-02-18 22:30:08 +0000
+++ templates/wlmaps/base.html	2018-11-18 17:28:32 +0000
@@ -1,4 +1,5 @@
 {% extends "base.html" %}
+{% load static %}
 
 {% comment %}
    vim:ft=htmldjango
@@ -7,6 +8,8 @@
 {% block extra_head %}
 <link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/forum.css" />
 <link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/maps.css" />
+<link rel="stylesheet" href="{% static 'star-ratings/css/star-ratings.css' %}">
+<script type="text/javascript" src="{% static 'star-ratings/js/dist/star-ratings.min.js' %}"></script>
 {{block.super}}
 {% endblock %}
 

=== modified file 'templates/wlmaps/index.html'
--- templates/wlmaps/index.html	2018-10-14 13:24:15 +0000
+++ templates/wlmaps/index.html	2018-11-18 17:28:32 +0000
@@ -5,9 +5,9 @@
 
 {% load custom_date %}
 {% load wlprofile_extras %}
-{% load wlmaps_extra %}
 {% load threadedcommentstags %}
 {% load pagination_tags %}
+{% load ratings %}
 
 {% block content_header %}
 	<h1>Maps</h1>
@@ -55,7 +55,9 @@
 					</tr>
 					<tr>
 						<td class="grey">Rating:</td>
-						<td>{{ map.rating|average_rating }} ({{ map.rating.votes }} Votes)</td>
+						<td>
+							{% ratings map read_only template_name='star_rating/average.html' %}
+						</td>
 						<td class="spacer"></td>
 						{% get_comment_count for map as ccount %}
 						<td class="grey">Comments:</td><td>{{ ccount }}</td>

=== modified file 'templates/wlmaps/map_detail.html'
--- templates/wlmaps/map_detail.html	2018-10-14 13:24:15 +0000
+++ templates/wlmaps/map_detail.html	2018-11-18 17:28:32 +0000
@@ -4,32 +4,16 @@
 {% endcomment %}
 
 {% load custom_date %}
-{% load wlmaps_extra %}
 {% load wlprofile_extras %}
 {% load threadedcommentstags %}
 {% load wl_markdown %}
+{% load ratings %}
 
 {% block title %}{{ map.name }} - {{ block.super }}{% endblock %}
 
 {% block extra_head %}
 {{ block.super }}
-<link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/comments.css" />
-{% if not user.is_anonymous %}
-<script src="{{ MEDIA_URL}}/js/jquery.sexy-vote.js" type="text/javascript"></script>
-<script type="text/javascript">
-$(function() {
-    $('#vote').sexyVote( {
-        activeImageSrc: "{{ MEDIA_URL }}img/active_star.gif",
-        passiveImageSrc: "{{ MEDIA_URL }}img/passive_star.gif",
-        maxScore: 10,
-        messages: ["","","","","","","","","",""],
-        fn: function(e, score) {
-            $.post("{% url 'wlmaps_rate' map.slug %}",{ vote: score });
-            }
-        });
-});
-</script>
-{% endif %}
+	<link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/comments.css" />
 {% endblock %}
 
 {% block content_header %}
@@ -95,12 +79,7 @@
 			<tr>
 				<td class="grey">Rating:</td>
 				<td>
-					{{ map.rating|average_rating }} ({{ map.rating.votes }} Votes)
-					{% if not user.is_anonymous %}
-					<span id="vote"></span>
-					{% else %}
-					- Login to vote
-					{% endif %}
+					{% ratings map template_name='star_rating/rate.html' %}
 				</td>
 			</tr>
 			<tr>

=== modified file 'urls.py'
--- urls.py	2018-10-10 17:04:53 +0000
+++ urls.py	2018-11-18 17:28:32 +0000
@@ -27,6 +27,7 @@
     url(r'^accounts/', include('registration.backends.hmac.urls')),
     url('^', include('django.contrib.auth.urls')),
 
+    url(r'^ratings/', include('star_ratings.urls', namespace='ratings', app_name='ratings')),
     # Formerly 3rd party
     url(r'^notification/', include('notification.urls')),
     

=== added file 'wlmaps/migrations/0002_auto_20181118_1758.py'
--- wlmaps/migrations/0002_auto_20181118_1758.py	1970-01-01 00:00:00 +0000
+++ wlmaps/migrations/0002_auto_20181118_1758.py	2018-11-18 17:28:32 +0000
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.12 on 2018-11-18 17:58
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wlmaps', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='map',
+            name='rating_score',
+        ),
+        migrations.RemoveField(
+            model_name='map',
+            name='rating_votes',
+        ),
+    ]

=== modified file 'wlmaps/models.py'
--- wlmaps/models.py	2018-04-08 16:23:55 +0000
+++ wlmaps/models.py	2018-11-18 17:28:32 +0000
@@ -11,8 +11,6 @@
 except ImportError:
     notification = None
 
-from djangoratings.fields import AnonymousRatingField
-
 
 class Map(models.Model):
     name = models.CharField(max_length=255, unique=True)
@@ -38,7 +36,6 @@
     nr_downloads = models.PositiveIntegerField(
         verbose_name='Download count', default=0)
 
-    rating = AnonymousRatingField(range=10, can_change_vote=True)
 
     class Meta:
         ordering = ('-pub_date',)

=== removed directory 'wlmaps/templatetags'
=== removed file 'wlmaps/templatetags/__init__.py'
=== removed file 'wlmaps/templatetags/wlmaps_extra.py'
--- wlmaps/templatetags/wlmaps_extra.py	2016-12-13 18:28:51 +0000
+++ wlmaps/templatetags/wlmaps_extra.py	1970-01-01 00:00:00 +0000
@@ -1,15 +0,0 @@
-#!/usr/bin/env python -tt
-# encoding: utf-8
-
-from django import template
-
-register = template.Library()
-
-
-@register.filter
-def average_rating(rating):
-    if rating.votes > 0:
-        avg = '%.1f' % (float(rating.score) / rating.votes)
-    else:
-        avg = '0.0'
-    return avg

=== 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-18 17:28:32 +0000
@@ -143,66 +143,3 @@
             reverse('wlmaps_view', args=('a-map-that-doesnt-exist',)))
         self.assertEqual(c.status_code, 404)
 
-
-############
-#  RATING  #
-############
-class TestWLMapsViews_Rating(_LoginToSite):
-
-    def setUp(self):
-        _LoginToSite.setUp(self)
-
-        # Add maps
-        nm = Map.objects.create(
-            name='Map',
-            author='Author',
-            w=128,
-            h=64,
-            nr_players=4,
-            descr='a good map to play with',
-            minimap='/wlmaps/minimaps/Map.png',
-            world_name='blackland',
-
-            uploader=self.user,
-            uploader_comment='Rockdamap'
-        )
-        nm.save()
-        self.map = nm
-
-    def test_RatingNonExistingMap_Except404(self):
-        c = self.client.post(
-            reverse('wlmaps_rate', args=('a-map-that-doesnt-exist',)),
-            {'vote': 10})
-        self.assertEqual(c.status_code, 404)
-
-    def test_RatingGet_Except405(self):
-        c = self.client.get(
-            reverse('wlmaps_rate', args=('map',)),
-            {'vote': 10})
-        self.assertEqual(c.status_code, 405)
-
-    def test_RatingInvalidValue_Except400(self):
-        c = self.client.post(
-            reverse('wlmaps_rate', args=('map',)),
-            {'vote': 11})
-        self.assertEqual(c.status_code, 400)
-
-    def test_RatingNonIntegerValue_Except400(self):
-        c = self.client.post(
-            reverse('wlmaps_rate', args=('map',)),
-            {'vote': 'shubidu'})
-        self.assertEqual(c.status_code, 400)
-
-    def test_RatingExistingMap_ExceptCorrectResult(self):
-        c = self.client.post(
-            reverse('wlmaps_rate', args=('map',)),
-            {'vote': 7})
-        # Except redirect
-        self.assertEqual(c.status_code, 302)
-
-        # We have to refetch this map, because
-        # votes doesn't hit the database
-        m = Map.objects.get(slug='map')
-
-        self.assertEqual(m.rating.votes, 1)
-        self.assertEqual(m.rating.score, 7)

=== modified file 'wlmaps/urls.py'
--- wlmaps/urls.py	2016-12-13 18:28:51 +0000
+++ wlmaps/urls.py	2018-11-18 17:28:32 +0000
@@ -4,6 +4,7 @@
 from models import Map
 from views import *
 
+
 urlpatterns = [
     url(r'^$', index, name='wlmaps_index'),
     url(r'^upload/$', upload, name='wlmaps_upload'),
@@ -14,7 +15,4 @@
         edit_comment, name='wlmaps_edit_comment'),
     url(r'^(?P<map_slug>[-\w]+)/download/$',
         download, name='wlmaps_download'),
-
-    url(r'^(?P<map_slug>[-\w]+)/rate/$',
-        rate, name='wlmaps_rate'),
 ]

=== modified file 'wlmaps/views.py'
--- wlmaps/views.py	2018-04-08 14:40:17 +0000
+++ wlmaps/views.py	2018-11-18 17:28:32 +0000
@@ -24,31 +24,6 @@
                                })
 
 
-def rate(request, map_slug):
-    """Rate a given map."""
-    if request.method != 'POST':
-        return HttpResponseNotAllowed(['post'])
-
-    m = get_object_or_404(models.Map, slug=map_slug)
-
-    if not 'vote' in request.POST:
-        return HttpResponseBadRequest()
-    try:
-        val = int(request.POST['vote'])
-    except ValueError:
-        return HttpResponseBadRequest()
-
-    if not (0 < val <= 10):
-        return HttpResponseBadRequest()
-
-    m.rating.add(score=val, user=request.user,
-                 ip_address=get_real_ip(request))
-
-    # m.save() is called in djangoratings.fields.add for each instance
-
-    return HttpResponseRedirect(reverse('wlmaps_view', kwargs= {'map_slug': m.slug}))
-
-
 def download(request, map_slug):
     """Very simple view that just returns the binary data of this map and
     increases the download count."""


Follow ups