Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add one-step user voting widget, other fixes #2

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
52 changes: 33 additions & 19 deletions agon_ratings/categories.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
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
AGON_RATINGS_CATEGORY_CHOICES = getattr(settings, "AGON_RATINGS_CATEGORY_CHOICES", {})
ALLOWED_RATINGS = {}

if len(AGON_RATINGS_CATEGORY_CHOICES.keys()) > 0:
for model_str in AGON_RATINGS_CATEGORY_CHOICES.keys():
categories = AGON_RATINGS_CATEGORY_CHOICES[model_str]

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)
if categories is True:
categories = []

ALLOWED_RATINGS[model_str] = categories

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

def get_model_string(obj):
"""
Takes a model instance, and returns a unique identifier for this model type
"""
return "%s.%s" % (obj._meta.app_label, obj._meta.object_name)

def category_value(obj, choice):
return RATING_CATEGORY_LOOKUP.get(
"%s.%s-%s" % (obj._meta.app_label, obj._meta.object_name, choice)
)

def is_valid_category(obj, category=None):
"""
Check if a given object and category is a valid combination for rating

Parameters:
obj - The object that is being rated
category - The rating category. Can be None.

Returns:
True if the combination of object and category is valid, False otherwise
"""
obj_str = get_model_string(obj)
if obj_str not in ALLOWED_RATINGS:
return False

if category is None:
return True
else:
return category in ALLOWED_RATINGS[obj_str]
9 changes: 1 addition & 8 deletions agon_ratings/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,14 @@

from django.contrib.contenttypes.models import ContentType

from agon_ratings.categories import category_value


class OverallRatingManager(models.Manager):

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),
category=cat
category=category
).extra(
select={
"sortable_rating": "COALESCE(rating, 0)"
Expand Down
5 changes: 2 additions & 3 deletions agon_ratings/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
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 @@ -19,7 +18,7 @@ class OverallRating(models.Model):
content_object = GenericForeignKey()
rating = models.DecimalField(decimal_places=1, max_digits=6, null=True)

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

objects = OverallRatingManager()

Expand All @@ -45,7 +44,7 @@ class Rating(models.Model):
rating = models.IntegerField()
timestamp = models.DateTimeField(default=datetime.datetime.now)

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

class Meta:
unique_together = [
Expand Down
60 changes: 60 additions & 0 deletions agon_ratings/static/agon_ratings/css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
form.agon-ratings-widget {
display: inline-block;
margin-bottom: 0;
}
form.agon-ratings-widget.agon-ratings-processing:after {
display: inline-block;
content: "";
width: 16px;
height: 16px;
background-image: url('../img/ajax-loader.gif');
vertical-align: middle;
}

form.agon-ratings-widget button.agon-ratings-button {
width: 18px;
height: 16px;
border: solid transparent;
border-width: 0px 1px;
text-indent: -99999px;
background: none transparent;
vertical-align: middle;

background-image: url('../img/ratings-sprite.png');

/* These will only work in webkit nightlies and chrom 18+ at the time of writing. Everything else works fine */
transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
-ms-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-webkit-transition: background 0.2s ease-out;
}

form.agon-ratings-widget button.agon-ratings-less { background-position: 0 0; }
form.agon-ratings-widget button.agon-ratings-current { background-position: 0 0; }
form.agon-ratings-widget button.agon-ratings-more { background-position: 0 -16px; }

form.agon-ratings-widget button.agon-ratings-cancel { background-position: 16px 16px; }
form.agon-ratings-widget button.agon-ratings-cancel:hover { background-position: 16px 0; }

/* Optional extra CSS styles */

/* Sprites that fit in the Bootstrap theme */
form.agon-ratings-widget.agon-ratings-bootstrap button.agon-ratings-button {
background-image: url('../img/bootstrap-sprites.png');
}

/* Larger buttons and sprites */
form.agon-ratings-widget.agon-ratings-big button.agon-ratings-button {
width: 26px;
height: 26px;
border-width: 0px;
background-image: url('../img/ratings-sprite-big.png');
}

form.agon-ratings-widget.agon-ratings-big button.agon-ratings-less { background-position: 0px 0px; }
form.agon-ratings-widget.agon-ratings-big button.agon-ratings-current { background-position: 0px 0px; }
form.agon-ratings-widget.agon-ratings-big button.agon-ratings-more { background-position: 0 -26px; }

form.agon-ratings-widget.agon-ratings-big button.agon-ratings-cancel { background-position: 26px 26px; }
form.agon-ratings-widget.agon-ratings-big button.agon-ratings-cancel:hover { background-position: 26px 0; }
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed agon_ratings/static/agon_ratings/img/background.gif
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/cancel-off.png
Binary file not shown.
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/cancel-on.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/coffee.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/face-a.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/face-b.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/face-c.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/face-d.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/face-off.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed agon_ratings/static/agon_ratings/img/medal-off.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/medal-on.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed agon_ratings/static/agon_ratings/img/star-half-big.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/star-half.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/star-off-big.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/star-off.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/star-on-big.png
Binary file not shown.
Binary file removed agon_ratings/static/agon_ratings/img/star-on.png
Binary file not shown.
25 changes: 0 additions & 25 deletions agon_ratings/static/agon_ratings/js/ajax_csrf.js

This file was deleted.

122 changes: 122 additions & 0 deletions agon_ratings/static/agon_ratings/js/jquery.agon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
!function($) {

var AgonRating = function(form, options) {
options = this.options = $.extend({}, agon.defaults, options);
this.$form = $(form);
this.$buttons = this.$form.find('button');

this.currentRating = options.currentRating || 0;

var self = this;

this.$buttons.each(function(i, button) {
$(button).hover(function(event) {
self.showRating(i);
self.hovering = true;
}, function(event) {
self.hovering = false;
self.showRating(self.currentRating);
});
});

this.$buttons.on('click', function(event) {
event.preventDefault();

var $button = $(this);
var $form = self.$form;
var rating = parseInt($button.attr('value'));

var oldRating = self.currentRating;

var data = $form.serializeArray();
data.push({name: $button.attr('name'), value: rating});

$form.addClass(options.processingClass)

self.currentRating = rating;
self.showRating(rating);

$.ajax({
url: $form.attr('action'),
type: "POST",
data: data,

statusCode: {
200: function(data, textStatus, jqXHR) {
self.currentRating = rating;
self.showRating(rating);
self.$form.triggerHandler('agonratingschange', data);
}
},

error: function() {
self.currentRating = oldRating;
self.showRating(oldRating);
},

complete: function() {
$form.removeClass(options.processingClass);
}

});

});
};

/**
* Options for this instance.
*
* See:
* <agon.defaults>
*/
AgonRating.prototype.options = null;

/**
* The form this instance is connected to
*/
AgonRating.prototype.$form = false;

/**
* Buttons on the form
*/
AgonRating.prototype.$buttons = false;

/**
* The current rating of this instance.
*/
AgonRating.prototype.currentRating = false;

/**
* If we are currently hovering over a button. Stying works slightly differently when we are hovering.
*/
AgonRating.prototype.hovering = false;

AgonRating.prototype.showRating = function(rating) {
if (this.hovering) return;

var classes = this.options.buttonClasses;

this.$form[rating ? 'removeClass' : 'addClass'](this.options.unratedClass);

this.$buttons.each(function(i, button) {
var $button = $(button),
buttonRating = $button.attr('value');

$button.removeClass(classes.join(' '));
$button.addClass(classes[buttonRating < rating ? 0 : buttonRating == rating ? 1 : 2]);
});
};

var agon = $.fn.agon = function(options) {
$(this).each(function(i, el) {
$(el).data("agon-ratings", new AgonRating(el, options));
});
};

agon.defaults = {
processingClass: 'agon-ratings-processing',
unratedClass: 'agon-ratings-unrated',
buttonClasses: ['agon-ratings-less', 'agon-ratings-current', 'agon-ratings-more']
};

}( window.jQuery || window.ender );
Loading