Skip to content

Commit

Permalink
Merge branch 'add-categories'
Browse files Browse the repository at this point in the history
  • Loading branch information
paltman committed Oct 22, 2011
2 parents 875473c + 2cf4055 commit bd68c0f
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 42 deletions.
2 changes: 1 addition & 1 deletion agon_ratings/__init__.py
@@ -1 +1 @@
__version__ = "0.1.2"
__version__ = "0.2"
28 changes: 28 additions & 0 deletions agon_ratings/categories.py
@@ -0,0 +1,28 @@
from django.conf import settings


RATING_CATEGORY_CHOICES_DICT = getattr(settings, "AGON_RATINGS_CATEGORY_CHOICES", {})
RATING_CATEGORY_CHOICES = []
RATING_CATEGORY_LOOKUP = {}
if len(RATING_CATEGORY_CHOICES_DICT.keys()) > 0:
for model_str in RATING_CATEGORY_CHOICES_DICT.keys():
for choice in RATING_CATEGORY_CHOICES_DICT[model_str].keys():
slug = "%s-%s" % (model_str, choice)
val = len(RATING_CATEGORY_CHOICES) + 1
RATING_CATEGORY_CHOICES.append((val, slug))
RATING_CATEGORY_LOOKUP[slug] = val


def category_label(obj, choice):
obj_str = "%s.%s" % (obj._meta.app_label, obj._meta.object_name)
return RATING_CATEGORY_CHOICES_DICT.get(obj_str, {}).get(choice)


def is_valid_category(obj, choice):
return category_label(obj, choice) is not None


def category_value(obj, choice):
return RATING_CATEGORY_LOOKUP.get(
"%s.%s-%s" % (obj._meta.app_label, obj._meta.object_name, choice)
)
12 changes: 10 additions & 2 deletions agon_ratings/managers.py
Expand Up @@ -2,13 +2,21 @@

from django.contrib.contenttypes.models import ContentType

from agon_ratings.categories import category_value


class OverallRatingManager(models.Manager):

def top_rated(self, klass):
def top_rated(self, klass, category=None):

if category:
cat = category_value(klass, category)
else:
cat = None

return self.filter(
content_type=ContentType.objects.get_for_model(klass)
content_type=ContentType.objects.get_for_model(klass),
category=cat
).extra(
select={
"sortable_rating": "COALESCE(rating, 0)"
Expand Down
9 changes: 7 additions & 2 deletions agon_ratings/models.py
Expand Up @@ -8,6 +8,7 @@
from django.contrib.contenttypes.generic import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

from agon_ratings.categories import RATING_CATEGORY_CHOICES
from agon_ratings.managers import OverallRatingManager


Expand All @@ -18,11 +19,13 @@ class OverallRating(models.Model):
content_object = GenericForeignKey()
rating = models.DecimalField(decimal_places=1, max_digits=3, null=True)

category = models.IntegerField(null=True, choices=RATING_CATEGORY_CHOICES)

objects = OverallRatingManager()

class Meta:
unique_together = [
("object_id", "content_type"),
("object_id", "content_type", "category"),
]

def update(self):
Expand All @@ -42,9 +45,11 @@ class Rating(models.Model):
rating = models.IntegerField()
timestamp = models.DateTimeField(default=datetime.datetime.now)

category = models.IntegerField(null=True, choices=RATING_CATEGORY_CHOICES)

class Meta:
unique_together = [
("object_id", "content_type", "user"),
("object_id", "content_type", "user", "category"),
]

def __unicode__(self):
Expand Down
11 changes: 8 additions & 3 deletions agon_ratings/templates/agon_ratings/_script.html
Expand Up @@ -17,15 +17,20 @@
url: "{{ post_url }}",
type: "POST",
data: {
"rating": current_rating
"rating": current_rating,
"category": "{{ category }}"
},
statusCode: {
403: function(jqXHR, textStatus, errorThrown) {
// Invalid rating was posted
// Invalid rating was posted or invalid category was sent
console.log(errorThrown);
},
200: function(data, textStatus, jqXHR) {
$(".overall_rating").text(data["overall_rating"]);
{% if category %}
$(".overall_rating.category-{{ category }}").text(data["overall_rating"]);
{% else %}
$(".overall_rating").text(data["overall_rating"]);
{% endif %}
}
}
});
Expand Down
67 changes: 48 additions & 19 deletions agon_ratings/templatetags/agon_ratings_tags.py
Expand Up @@ -4,19 +4,21 @@

from django.contrib.contenttypes.models import ContentType

from agon_ratings.categories import category_value
from agon_ratings.models import Rating, OverallRating


register = template.Library()


def get_user_rating(user, obj):
def user_rating_value(user, obj, category=None):
try:
ct = ContentType.objects.get_for_model(obj)
rating = Rating.objects.get(
object_id = obj.pk,
content_type = ct,
user = user
user = user,
category = category_value(obj, category)
).rating
except Rating.DoesNotExist:
rating = 0
Expand All @@ -28,31 +30,43 @@ class UserRatingNode(template.Node):
@classmethod
def handle_token(cls, parser, token):
bits = token.split_contents()
if len(bits) != 7:

if len(bits) == 5:
category = None
elif len(bits) == 6:
category = parser.compile_filter(bits[3])
else:
raise template.TemplateSyntaxError()

return cls(
user = parser.compile_filter(bits[2]),
obj = parser.compile_filter(bits[4]),
as_var = bits[6]
user = parser.compile_filter(bits[1]),
obj = parser.compile_filter(bits[2]),
as_var = bits[len(bits) - 1],
category = category
)

def __init__(self, user, obj, as_var):
def __init__(self, user, obj, as_var, category=None):
self.user = user
self.obj = obj
self.as_var = as_var
self.category = category

def render(self, context):
user = self.user.resolve(context)
obj = self.obj.resolve(context)
context[self.as_var] = get_user_rating(user, obj)
if self.category:
category = self.category.resolve(context)
else:
category = None
context[self.as_var] = user_rating_value(user, obj, category)
return ""


@register.tag
def user_rating(parser, token):
"""
Usage:
{% user_rating for user and obj as var %}
{% user_rating user obj [category] as var %}
"""
return UserRatingNode.handle_token(parser, token)

Expand All @@ -62,24 +76,38 @@ class OverallRatingNode(template.Node):
@classmethod
def handle_token(cls, parser, token):
bits = token.split_contents()
if len(bits) != 5:

if len(bits) == 4:
category = None
elif len(bits) == 5:
category = parser.compile_filter(bits[2])
else:
raise template.TemplateSyntaxError()

return cls(
obj = parser.compile_filter(bits[2]),
as_var = bits[4]
obj = parser.compile_filter(bits[1]),
as_var = bits[len(bits) - 1],
category = category
)

def __init__(self, obj, as_var):
def __init__(self, obj, as_var, category=None):
self.obj = obj
self.as_var = as_var
self.category = category

def render(self, context):
obj = self.obj.resolve(context)
if self.category:
category = self.category.resolve(context)
else:
category = None

try:
ct = ContentType.objects.get_for_model(obj)
rating = OverallRating.objects.get(
object_id=obj.pk,
content_type=ct
object_id = obj.pk,
content_type = ct,
category = category_value(obj, category)
).rating or 0
except OverallRating.DoesNotExist:
rating = 0
Expand All @@ -91,7 +119,7 @@ def render(self, context):
def overall_rating(parser, token):
"""
Usage:
{% overall_rating for obj as var %}
{% overall_rating obj [category] as var %}
"""
return OverallRatingNode.handle_token(parser, token)

Expand All @@ -109,15 +137,16 @@ def rating_post_url(user, obj):


@register.inclusion_tag("agon_ratings/_script.html")
def user_rating_js(user, obj):
def user_rating_js(user, obj, category=None):
post_url = rating_post_url(user, obj)
rating = get_user_rating(user, obj)
rating = user_rating_value(user, obj, category)

return {
"obj": obj,
"post_url": post_url,
"category": category,
"the_user_rating": rating,
"STATIC_URL": settings.STATIC_URL
"STATIC_URL": settings.STATIC_URL,
}


Expand Down
32 changes: 24 additions & 8 deletions agon_ratings/views.py
Expand Up @@ -7,6 +7,7 @@
from django.contrib.auth.decorators import login_required
from django.contrib.contenttypes.models import ContentType

from agon_ratings.categories import category_value
from agon_ratings.models import Rating, OverallRating


Expand All @@ -19,10 +20,26 @@ def rate(request, content_type_id, object_id):
ct = get_object_or_404(ContentType, pk=content_type_id)
obj = get_object_or_404(ct.model_class(), pk=object_id)
rating_input = int(request.POST.get("rating"))
category = request.POST.get("category")
if category:
cat_choice = category_value(obj, category)
else:
cat_choice = None

# Check for errors and bail early
if category is not None and cat_choice is None:
return HttpResponseForbidden(
"Invalid category. It must match a preconfigured setting"
)
if rating_input not in range(NUM_OF_RATINGS + 1):
return HttpResponseForbidden(
"Invalid rating. It must be a value between 0 and %s" % NUM_OF_RATINGS
)

data = {
"user_rating": rating_input,
"overall_rating": 0
"overall_rating": 0,
"category": category
}

# @@@ Seems like this could be much more DRY with a model method or something
Expand All @@ -31,35 +48,34 @@ def rate(request, content_type_id, object_id):
rating = Rating.objects.get(
object_id = object_id,
content_type = ct,
user = request.user
user = request.user,
category = cat_choice
)
overall = rating.overall_rating
rating.delete()
overall.update()
data["overall_rating"] = str(overall.rating)
except Rating.DoesNotExist:
pass
elif 1 <= rating_input <= NUM_OF_RATINGS: # set the rating
else: # set the rating
rating, created = Rating.objects.get_or_create(
object_id = obj.pk,
content_type = ct,
user = request.user,
category = cat_choice,
defaults = {
"rating": rating_input
}
)
overall, created = OverallRating.objects.get_or_create(
object_id = obj.pk,
content_type = ct
content_type = ct,
category = cat_choice
)
rating.overall_rating = overall
rating.rating = rating_input
rating.save()
overall.update()
data["overall_rating"] = str(overall.rating)
else: # whoops
return HttpResponseForbidden(
"Invalid rating. It must be a value between 0 and %s" % NUM_OF_RATINGS
)

return HttpResponse(json.dumps(data), mimetype="application/json")
24 changes: 24 additions & 0 deletions docs/changelog.rst
Expand Up @@ -3,6 +3,30 @@
ChangeLog
=========

0.2
---

- added support for ratings to have categories instead of just a single
rating for an object
- dropped natural language of template tags

Migrations
^^^^^^^^^^

Added a category model and updated the unique index on both models::

ALTER TABLE "agon_ratings_overallrating" ADD COLUMN "category" int;
ALTER TABLE "agon_ratings_rating" ADD COLUMN "category" int;
CREATE UNIQUE INDEX "agon_ratings_overallrating_unq_object_id_content_type_id_category_idx"
ON "agon_ratings_overallrating" (object_id, content_type_id, category);
CREATE UNIQUE INDEX "agon_ratings_rating_unq_object_id_content_type_id_user_id_category_idx"
ON "agon_ratings_rating" (object_id, content_type_id, user_id, category);
ALTER TABLE "agon_ratings_rating" DROP CONSTRAINT
IF EXISTS "agon_ratings_rating_object_id_content_type_id_user_id_key";
ALTER TABLE "agon_ratings_overallrating" DROP CONSTRAINT
IF EXISTS "agon_ratings_overallrating_object_id_content_type_id_key";


0.1.2
-----

Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Expand Up @@ -4,8 +4,8 @@
master_doc = 'index'
project = u'agon_ratings'
copyright = u'2011, Eldarion'
version = '0.1.2'
release = '0.1.2'
version = '0.2'
release = '0.2'
exclude_patterns = ['_build']
pygments_style = 'sphinx'
html_theme = 'default'
Expand Down

0 comments on commit bd68c0f

Please sign in to comment.