diff --git a/README.rst b/README.rst index aa8413e..e80469e 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,8 @@ django-ratings A generic ratings module. The field itself appends two additional fields on the model, for optimization reasons. It adds ``_score``, and ``_votes`` fields, which are both integer fields. +This fork extends upon David Cramer's django-ratings in a way to make ratings generic to better support and adapt to different rating systems, such as votings, star ratings, like/dislike, flags and others. + ============ Installation ============ @@ -46,11 +48,47 @@ to obtain a higher rating, you can use the ``weight`` kwarg:: ``RatingField`` allows the following options: -* ``range = 2`` - The range in which values are accepted. For example, a range of 2, says there are 2 possible vote scores. +* ``lower = 0`` - The lower value in the range for which values are accepted. For example, a lower value of -1, says the voting scores start at -1. +* ``upper = 2`` - The upper value in the range for which values are accepted. For example, an upper value of 1, says the highest possible voting score is 1. +* ``range = 2`` - Upper and range are synonymous. For example, a range of 2, says there are 2 possible vote scores (provided lower is not set, or is 0). * ``can_change_vote = False`` - Allow the modification of votes that have already been made. * ``allow_delete = False`` - Allow the deletion of existent votes. Works only if ``can_change_vote = True`` * ``allow_anonymous = False`` - Whether to allow anonymous votes. -* ``use_cookies = False`` - Use COOKIES to authenticate user votes. Works only if ``allow_anonymous = True``. +* ``use_cookies = False`` - Use COOKIES to authenticate user votes. Works only if ``allow_anonymous = True``. +* ``values = [lower ... upper]`` - List of strings accepted as alternative to score integer values. For example, ``['clear', 'favorite']`` would make it so the voting system accepts eigher 'clear' or 'favorite' in addition to 0 and 1, respectively. +* ``titles = []`` - List of strings used to have verbose names the voting scores. For example, ``[_("Clear"), _("Favorite")]``. +* ``widget_template = 'djangoratings/_rating.html'`` - Accepts the template name used to display the widget. + +Also available there are ``VotingField``, ``FavoriteField`` and ``FlagField``, with their anonymous alternatives:: + + from djangoratings.fields import VotingField + + class MyModel(models.Model): + rating = VotingField() # accepting 'down', 'clear' and 'up' + +``VotingField``'s default options are: + +* ``lower = -1`` +* ``upper = 1`` +* ``values = ['down', 'clear', 'up']`` +* ``titles = [_("Down"), _("Clear"), _("Up")]`` +* ``widget_template = 'djangoratings/_voting.html'`` + +``FavoriteField``'s default options are: + +* ``lower = 0`` +* ``upper = 1`` +* ``values = ['clear', 'favorite']`` +* ``titles = [_("Clear"), _("Favorite")]`` +* ``widget_template = 'djangoratings/_favorite.html'`` + +``FlagField``'s default options are: + +* ``lower = 0`` +* ``upper = 1`` +* ``values = ['clear', 'flag']`` +* ``titles = [_("Clear"), _("Flag")]`` +* ``widget_template = 'djangoratings/_flag.html'`` =================== Using the model API @@ -188,3 +226,38 @@ stores it in a context variable. If the user has not voted, the context variable will be 0:: {% rating_by_user user on instance.field as vote %} + +------------- +rating_widget +------------- + +Uses ``widget_template`` passed to the field to render the rating field widget:: + + {% rating_widget on instance.field %} + +If you want to use a different ``widget_template``, pass the template name as:: + + {% rating_widget on instance.field using "template_name.html" %} + +The context is passed to the template and additionally, the template receives: + +* ``content_type`` - The content type of the instance object. +* ``instance`` - The object instance. +* ``model`` - The model name for the object. +* ``app_label`` - The app label for the object. +* ``object_id`` - The object instance ID. +* ``field_name`` - The field name. +* ``had_voted`` - If the user has voted previously, the voted score. +* ``votes`` - Number of total votes. +* ``score`` - The overall voting score for the object. +* ``vote`` - The overall voting score for the object, as an integer. +* ``percent`` - The overall voting score for the object, as a percentage. +* ``real_percent`` - The overall voting score for the object, as a percentage (without taking into account the weights). +* ``positive`` - Number of positive votes (when applicable). +* ``negative`` - Number of negative votes (when applicable). +* ``ratings`` - a list of ``checked``, ``value`` and ``title``. For example:: + + [ + { 'checked': False, 'value': 'clear', 'title: 'Clear' }, + { 'checked': True, 'value': 'favorite', 'title: 'Favorite' }, + ] diff --git a/djangoratings/fields.py b/djangoratings/fields.py index ad64aff..046a9eb 100644 --- a/djangoratings/fields.py +++ b/djangoratings/fields.py @@ -1,8 +1,8 @@ from django.db.models import IntegerField, PositiveIntegerField +from django.utils.translation import ugettext_lazy as _ from django.conf import settings import forms -import itertools from datetime import datetime from models import Vote, Score @@ -21,14 +21,17 @@ except ImportError: from md5 import new as md5 + 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 @@ -42,17 +45,13 @@ 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) + return 100 * ((self.get_rating() - self.field.range_lower) / (self.field.range_upper - self.field.range_lower)) 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) + return 100 * ((self.get_real_rating() - self.field.range_lower) / (self.field.range_upper - self.field.range_lower)) def get_ratings(self): """get_ratings() @@ -64,7 +63,7 @@ def get_rating(self): """get_rating() Returns the weighted average rating.""" - if not (self.votes and self.score): + if not self.votes: return 0 return float(self.score)/(self.votes+self.field.weight) @@ -78,7 +77,7 @@ def get_real_rating(self): """get_rating() Returns the unmodified average rating.""" - if not (self.votes and self.score): + if not self.votes: return 0 return float(self.score)/self.votes @@ -112,7 +111,10 @@ def get_rating_for_user(self, user, ip_address=None, cookies={}): try: rating = Vote.objects.get(**kwargs) - return rating.score + try: + return self.field.values[rating.score - self.field.range_lower] + except IndexError: + pass except Vote.MultipleObjectsReturned: pass except Vote.DoesNotExist: @@ -123,6 +125,9 @@ def add(self, score, user, ip_address, cookies={}, commit=True): """add(score, user, ip_address) Used to add a rating to an object.""" + if score in self.field.types: + score = self.field.types[score] + try: score = int(score) except (ValueError, TypeError): @@ -133,7 +138,7 @@ def add(self, score, user, ip_address, cookies={}, commit=True): 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: + if score and (score < self.field.range_lower or score > self.field.range_upper): raise InvalidRating("%s is not a valid choice for %s" % (score, self.field.name)) is_anonymous = (user is None or not user.is_authenticated()) @@ -205,6 +210,7 @@ def add(self, score, user, ip_address, cookies={}, commit=True): else: has_changed = True self.votes += 1 + if has_changed: if not delete: self.score += rating.score @@ -212,25 +218,20 @@ def add(self, score, user, ip_address, cookies={}, commit=True): self.instance.save() #setattr(self.instance, self.field.name, Rating(score=self.score, votes=self.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 = 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) + if (score.score != self.score or + score.votes != self.votes): + score.score = self.score + score.votes = self.votes score.save() # return value @@ -257,6 +258,8 @@ def _get_score(self, default=None): return getattr(self.instance, self.score_field_name, default) def _set_score(self, value): + if value in self.field.types: + value = self.field.types[value] return setattr(self.instance, self.score_field_name, value) score = property(_get_score, _set_score) @@ -294,6 +297,7 @@ def _update(self, commit=False): if commit: self.instance.save() + class RatingCreator(object): def __init__(self, field): self.field = field @@ -313,6 +317,7 @@ def __set__(self, instance, value): 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. @@ -321,11 +326,19 @@ 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) + self.widget_template = kwargs.pop('widget_template', 'djangoratings/_rating.html') + self.weight = kwargs.pop('weight', 0) + self.range_lower = kwargs.pop('lower', 1) + self.range_upper = kwargs.pop('upper', None) + if self.range_upper is None: + self.range_upper = kwargs.pop('range', 2) + self.titles = kwargs.pop('titles', []) + self.values = kwargs.pop('values', range(self.range_lower, self.range_upper+1)) + self.types = dict(zip(self.values, range(self.range_lower, self.range_upper+1))) + self.types[''] = 0 kwargs['editable'] = False kwargs['default'] = 0 kwargs['blank'] = True @@ -354,11 +367,11 @@ def contribute_to_class(self, cls, name): setattr(cls, name, field) - def get_db_prep_save(self, value): + def get_db_prep_save(self, value, connection=None): # XXX: what happens here? pass - def get_db_prep_lookup(self, lookup_type, value): + def get_db_prep_lookup(self, lookup_type, value, connection=None, prepared=False): # 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) @@ -366,9 +379,9 @@ def get_db_prep_lookup(self, lookup_type, value): # lookup_type = # return self.score_field.get_db_prep_lookup() if lookup_type == 'exact': - return [self.get_db_prep_save(value)] + return [self.get_db_prep_save(value, connection)] elif lookup_type == 'in': - return [self.get_db_prep_save(v) for v in value] + return [self.get_db_prep_save(v, connection) for v in value] else: return super(RatingField, self).get_db_prep_lookup(lookup_type, value) @@ -384,3 +397,51 @@ class AnonymousRatingField(RatingField): def __init__(self, *args, **kwargs): kwargs['allow_anonymous'] = True super(AnonymousRatingField, self).__init__(*args, **kwargs) + + +class VotingField(RatingField): + def __init__(self, *args, **kwargs): + kwargs['widget_template'] = kwargs.get('widget_template', 'djangoratings/_voting.html') + kwargs['lower'] = -1 + kwargs['upper'] = 1 + kwargs['titles'] = (_("Down"), _("Clear"), _("Up")) + kwargs['values'] = ('down', 'clear', 'up') + super(VotingField, self).__init__(*args, **kwargs) + + +class AnonymousVotingField(VotingField): + def __init__(self, *args, **kwargs): + kwargs['allow_anonymous'] = True + super(AnonymousVotingField, self).__init__(*args, **kwargs) + + +class FavoriteField(RatingField): + def __init__(self, *args, **kwargs): + kwargs['widget_template'] = kwargs.get('widget_template', 'djangoratings/_favorite.html') + kwargs['lower'] = 0 + kwargs['upper'] = 1 + kwargs['titles'] = (_("Clear"), _("Favorite")) + kwargs['values'] = ('clear', 'favorite') + super(FavoriteField, self).__init__(*args, **kwargs) + + +class AnonymousFavoriteField(FavoriteField): + def __init__(self, *args, **kwargs): + kwargs['allow_anonymous'] = True + super(AnonymousFavoriteField, self).__init__(*args, **kwargs) + + +class FlagField(RatingField): + def __init__(self, *args, **kwargs): + kwargs['widget_template'] = kwargs.get('widget_template', 'djangoratings/_flag.html') + kwargs['lower'] = 0 + kwargs['upper'] = 1 + kwargs['titles'] = (_("Clear"), _("Flag")) + kwargs['values'] = ('clear', 'flag') + super(FlagField, self).__init__(*args, **kwargs) + + +class AnonymousFlagField(FlagField): + def __init__(self, *args, **kwargs): + kwargs['allow_anonymous'] = True + super(AnonymousFlagField, self).__init__(*args, **kwargs) diff --git a/djangoratings/models.py b/djangoratings/models.py index 70d9a71..3c83513 100644 --- a/djangoratings/models.py +++ b/djangoratings/models.py @@ -7,6 +7,7 @@ from managers import VoteManager, SimilarUserManager + class Vote(models.Model): content_type = models.ForeignKey(ContentType, related_name="votes") object_id = models.PositiveIntegerField() @@ -44,6 +45,7 @@ def partial_ip_address(self): return '.'.join(ip) partial_ip_address = property(partial_ip_address) + class Score(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() @@ -59,6 +61,7 @@ class Meta: 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") @@ -74,6 +77,7 @@ class Meta: 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) diff --git a/djangoratings/templatetags/ratings.py b/djangoratings/templatetags/ratings.py index f2dee1b..d6432b4 100644 --- a/djangoratings/templatetags/ratings.py +++ b/djangoratings/templatetags/ratings.py @@ -4,17 +4,20 @@ # 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 django.template.loader import render_to_string +from django.utils.safestring import mark_safe -from djangoratings.models import Vote +from djangoratings.views import _rating_widget register = template.Library() + class RatingByRequestNode(template.Node): def __init__(self, request, obj, context_var): self.request = request - self.obj, self.field_name = obj.split('.') + split = obj.rsplit('.', 1) + self.field_name = split[-1] + self.obj = split[0] self.context_var = context_var def render(self, context): @@ -24,26 +27,28 @@ def render(self, context): field = getattr(obj, self.field_name) except (template.VariableDoesNotExist, AttributeError): return '' - try: - vote = field.get_rating_for_user(request.user, request.META['REMOTE_ADDR'], request.COOKIES) - context[self.context_var] = vote - except ObjectDoesNotExist: - context[self.context_var] = 0 + vote = field.get_rating_for_user(request.user, request.META['REMOTE_ADDR'], request.COOKIES) + context[self.context_var] = vote 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. + stores it in a context variable. If the user has not rated, the + context variable will be ``None``. Example usage:: + + {% rating on instance.field as vote %} - {% rating_by_request request on instance as vote %} + {% rating_by_request request on instance.field as vote %} """ bits = token.contents.split() - if len(bits) != 6: + if len(bits) == 5: + bits.insert(1, 'request') + elif 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]) @@ -51,31 +56,38 @@ def do_rating_by_request(parser, token): 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) +register.tag('rating', do_rating_by_request) -class RatingByUserNode(RatingByRequestNode): + +class RatingByUserNode(template.Node): + def __init__(self, user, obj, context_var): + self.user = user + split = obj.rsplit('.', 1) + self.field_name = split[-1] + self.obj = split[0] + self.context_var = context_var + def render(self, context): try: - user = template.resolve_variable(self.request, context) + user = template.resolve_variable(self.user, context) obj = template.resolve_variable(self.obj, 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 + vote = field.get_rating_for_user(user) + context[self.context_var] = vote 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. + stores it in a context variable. If the user has not rated, the + context variable will be ``None``. Example usage:: - {% rating_by_user user on instance as vote %} + {% rating_by_user user on instance.field as vote %} """ bits = token.contents.split() @@ -87,3 +99,137 @@ def do_rating_by_user(parser, token): 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) + + +class RatingWidgetByRequestNode(template.Node): + def __init__(self, request, obj, widget_template): + self.request = request + split = obj.rsplit('.', 1) + self.field_name = split[-1] + self.obj = split[0] + self.widget_template = widget_template + + def render(self, context): + try: + request = template.resolve_variable(self.request, context) + obj = template.resolve_variable(self.obj, context) + field = getattr(obj, self.field_name) + widget_template = template.resolve_variable(self.widget_template, context) if self.widget_template else field.field.widget_template + except (template.VariableDoesNotExist, AttributeError): + return '' + had_voted = field.get_rating_for_user(request.user, request.META['REMOTE_ADDR'], request.COOKIES) + return render_to_string(widget_template, _rating_widget(obj, field, had_voted), context) + + +def do_rating_widget_by_request(parser, token): + """ + Retrieves the ``Vote`` cast by a user on a particular object and + outputs the widget. + + Example usage:: + + {% rating_widget on instance.field %} + + {% rating_widget_by_request request on instance.field %} + + {% rating_widget_by_request request on instance.field using "template_name.html" %} + """ + + bits = token.contents.split() + + if len(bits) > 1 and bits[1] == 'on': + bits.insert(1, 'request') + + if len(bits) not in (3, 4, 6): + raise template.TemplateSyntaxError("'%s' tag takes exactly two, three or five arguments" % bits[0]) + + if bits[2] != 'on': + raise template.TemplateSyntaxError("first or second argument to '%s' tag must be 'on'" % bits[0]) + + if len(bits) > 3 and bits[4] != 'using': + raise template.TemplateSyntaxError("fourth argument to '%s' tag must be 'using'" % bits[0]) + + request = bits[1] + field = bits[3] + + if len(bits) == 6: + widget_template = bits[5] + else: + widget_template = None + + return RatingWidgetByRequestNode(request, field, widget_template) +register.tag('rating_widget_by_request', do_rating_widget_by_request) +register.tag('rating_widget', do_rating_widget_by_request) + + +class RatingWidgetByUserNode(template.Node): + def __init__(self, user, obj): + self.user = user + split = obj.rsplit('.', 1) + self.field_name = split[-1] + self.obj = split[0] + + def render(self, context): + try: + user = template.resolve_variable(self.user, context) + obj = template.resolve_variable(self.obj, context) + field = getattr(obj, self.field_name) + except (template.VariableDoesNotExist, AttributeError): + return '' + had_voted = field.get_rating_for_user(user) + return render_to_string(field.field.widget_template, _rating_widget(obj, field, had_voted), context) + + +def do_rating_widget_by_user(parser, token): + """ + Retrieves the ``Vote`` cast by a user on a particular object and + outputs the widget. + + Example usage:: + + {% rating_widget_by_user user on instance.field %} + """ + + bits = token.contents.split() + if len(bits) == 3: + bits.insert(0, 'request') + elif len(bits) != 4: + 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]) + return RatingWidgetByRequestNode(bits[1], bits[3]) +register.tag('rating_widget_by_user', do_rating_widget_by_user) + + +def _rates(rate, out_of, stars): + stars = float(stars) + if float(rate) and float(out_of): + rate = (float(rate) / float(out_of)) * stars + if rate > stars: + rate = stars + else: + rate = 0 + stars *= 16 + rate *= 16 + return '' % (int(stars), int(rate)) + + +@register.simple_tag +def rates(rate, out_of=5, stars=5): + return _rates(rate, out_of, stars) + + +# Filters + + +@register.filter +def rating_display(rating): + """ + Given a rating returns a stars field. + + Example usage:: + + {{ rating|rating_display }} + """ + cnt = rating.field.range_upper - rating.field.range_lower + 1 + return mark_safe(_rates(rating.get_rating(), cnt, cnt)) diff --git a/djangoratings/views.py b/djangoratings/views.py index a294fcb..46de870 100644 --- a/djangoratings/views.py +++ b/djangoratings/views.py @@ -1,11 +1,51 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist +from django.utils.translation import ugettext_lazy as _ from django.http import HttpResponse, Http404 from exceptions import * -from django.conf import settings from default_settings import RATINGS_VOTES_PER_IP +def _rating_widget(instance, field, had_voted): + ratings = [] + for num in range(field.field.range_lower, field.field.range_upper+1): + if num == field.score: + checked = True + else: + checked = False + try: + value = field.field.values[num-field.field.range_lower] + except IndexError: + value = num + try: + title = field.field.titles[num-field.field.range_lower] + except IndexError: + title = '' + ratings.append({ + 'checked': checked, + 'value': value, + 'title': title, + }) + content_type = ContentType.objects.get_for_model(instance) + return { + 'content_type': content_type, + 'instance': instance, + 'model': content_type.model, + 'app_label': content_type.app_label, + 'object_id': instance.id, + 'field_name': field.field.name, + 'had_voted' : had_voted, + 'score': field.score, + 'votes': field.votes, + 'vote': int(round(field.score)), + 'ratings': ratings, + 'percent': field.get_percent(), + 'real_percent': field.get_real_percent(), + 'positive': int((field.votes * field.get_real_percent() / 100) + 0.5), + 'negative': int((field.votes - (field.votes * (field.get_real_percent() / 100))) + 0.5), + } + + 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) @@ -15,7 +55,8 @@ def __call__(self, request, content_type_id, object_id, field_name, score): try: instance = self.get_instance(content_type_id, object_id) except ObjectDoesNotExist: - raise Http404('Object does not exist') + message = _("Object does not exist") + raise Http404(message) context = self.get_context(request) context['instance'] = instance @@ -30,7 +71,7 @@ def __call__(self, request, content_type_id, object_id, field_name, score): 'score': score, }) - had_voted = bool(field.get_rating_for_user(request.user, request.META['REMOTE_ADDR'], request.COOKIES)) + had_voted = field.get_rating_for_user(request.user, request.META['REMOTE_ADDR'], request.COOKIES) context['had_voted'] = had_voted @@ -46,7 +87,7 @@ def __call__(self, request, content_type_id, object_id, field_name, score): return self.cannot_change_vote_response(request, context) except CannotDeleteVote: return self.cannot_delete_vote_response(request, context) - if had_voted: + if had_voted is not None: return self.rating_changed_response(request, context, adds) return self.rating_added_response(request, context, adds) @@ -57,11 +98,13 @@ 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.') + message = _("Too many votes from this IP address for this object.") + response = HttpResponse(message) return response def rating_changed_response(self, request, context, adds={}): - response = HttpResponse('Vote changed.') + message = _("Vote changed.") + response = HttpResponse(message) if 'cookie' in adds: cookie_name, cookie = adds['cookie_name'], adds['cookie'] if 'deleted' in adds: @@ -71,7 +114,8 @@ def rating_changed_response(self, request, context, adds={}): return response def rating_added_response(self, request, context, adds={}): - response = HttpResponse('Vote recorded.') + message = _("Vote recorded.") + response = HttpResponse(message) if 'cookie' in adds: cookie_name, cookie = adds['cookie_name'], adds['cookie'] if 'deleted' in adds: @@ -81,27 +125,32 @@ def rating_added_response(self, request, context, adds={}): return response def authentication_required_response(self, request, context): - response = HttpResponse('You must be logged in to vote.') + message = _("You must be logged in to vote.") + response = HttpResponse(message) response.status_code = 403 return response def cannot_change_vote_response(self, request, context): - response = HttpResponse('You have already voted.') + message = "You have already voted." + response = HttpResponse(message) response.status_code = 403 return response def cannot_delete_vote_response(self, request, context): - response = HttpResponse('You can\'t delete this vote.') + message = _("You can't delete this vote.") + response = HttpResponse(message) response.status_code = 403 return response def invalid_field_response(self, request, context): - response = HttpResponse('Invalid field name.') + message = _("Invalid field name.") + response = HttpResponse(message) response.status_code = 403 return response def invalid_rating_response(self, request, context): - response = HttpResponse('Invalid rating value.') + message = _("Invalid rating value.") + response = HttpResponse(message) response.status_code = 403 return response @@ -118,7 +167,8 @@ def __call__(self, request, model, app_label, object_id, field_name, score): try: content_type = ContentType.objects.get(model=model, app_label=app_label) except ContentType.DoesNotExist: - raise Http404('Invalid `model` or `app_label`.') + message = _("Invalid `model` or `app_label`.") + raise Http404(message) return super(AddRatingFromModel, self).__call__(request, content_type.id, object_id, field_name, score) diff --git a/setup.py b/setup.py old mode 100755 new mode 100644