@@ -160,5 +162,6 @@
{{extra_html|safe}}
+
{% endif %}
{% endblock %}
diff --git a/scipy_central/submission/templatetags/core_tags.py b/scipy_central/submission/templatetags/core_tags.py
index 0ecfa34..cb2c1a6 100644
--- a/scipy_central/submission/templatetags/core_tags.py
+++ b/scipy_central/submission/templatetags/core_tags.py
@@ -118,6 +118,29 @@ def latest(model_or_obj, num=5):
return [sub.last_revision for sub in subs \
if sub.last_revision.is_displayed][:num]
+
+@register.filter
+def top_rated(model_or_obj, num=5):
+ # load up the model if we were given a string
+ if isinstance(model_or_obj, basestring):
+ model_or_obj = get_model(*model_or_obj.split('.'))
+
+ # figure out the manager to query
+ if isinstance(model_or_obj, QuerySet):
+ manager = model_or_obj
+ model_or_obj = model_or_obj.model
+ else:
+ manager = model_or_obj._default_manager
+
+ subs = manager.all().order_by('-date_created')
+ subs = [sub.last_revision for sub in subs \
+ if sub.last_revision.is_displayed][:num]
+
+ # use python sort methods as `subs` is not QuerySet
+ # sorting is done based on wilson score `score` field in Revision object
+ subs.sort(key=lambda sub: (sub.score, sub.reputation), reverse=True)
+ return subs
+
@register.filter
def call_manager(model_or_obj, method):
# load up the model if we were given a string
diff --git a/scipy_central/submission/views.py b/scipy_central/submission/views.py
index c519996..06aed4c 100644
--- a/scipy_central/submission/views.py
+++ b/scipy_central/submission/views.py
@@ -980,8 +980,9 @@ def show_items(request, what_view='', extra_info=''):
page_title = ''
template_name = 'submission/show-entries.html'
if what_view == 'tag':
- all_revs = models.Revision.objects.most_recent().\
- filter(tags__slug=slugify(extra_info))
+ all_revs = models.Revision.objects.most_recent().filter(
+ tags__slug=slugify(extra_info)
+ ).order_by('-score', '-reputation', '-date_created')
page_title = 'All entries tagged'
entry_order = list(all_revs)
elif what_view == 'show' and extra_info == 'all-tags':
@@ -994,7 +995,7 @@ def show_items(request, what_view='', extra_info=''):
extra_info = ''
entry_order = list(all_revs)
elif what_view == 'show' and extra_info == 'all-unique-revisions':
- all_subs = models.Submission.objects.all().order_by('-date_created')
+ all_subs = models.Submission.objects.all().order_by('-score', '-reputation', '-date_created')
page_title = 'All submissions'
extra_info = ' (only showing the latest revision)'
entry_order = [sub.last_revision for sub in all_subs if sub.last_revision.is_displayed]
@@ -1010,6 +1011,14 @@ def show_items(request, what_view='', extra_info=''):
page_title = 'Top contributors'
extra_info = ''
entry_order = top_authors('', 0)
+ elif what_view == 'show' and extra_info == 'top-rated':
+ all_subs = models.Submission.objects.all().order_by('-date_created')
+ page_title = 'All submissions in order of rating'
+ extra_info = ''
+ # entry_order contains top revision of all submissions
+ entry_order = [sub.last_revision for sub in all_subs if sub.last_revision.is_displayed]
+ # sort entry_order based on revision's `score` attribute
+ entry_order.sort(key=lambda rev: (rev.score, rev.reputation), reverse=True)
elif what_view == 'validate':
return validate_submission(request, code=extra_info)
diff --git a/scipy_central/thumbs/__init__.py b/scipy_central/thumbs/__init__.py
index e69de29..315f7ef 100644
--- a/scipy_central/thumbs/__init__.py
+++ b/scipy_central/thumbs/__init__.py
@@ -0,0 +1 @@
+from scipy_central.thumbs import signals
diff --git a/scipy_central/thumbs/admin.py b/scipy_central/thumbs/admin.py
new file mode 100644
index 0000000..874be2a
--- /dev/null
+++ b/scipy_central/thumbs/admin.py
@@ -0,0 +1,27 @@
+from django.utils.translation import ugettext_lazy as _
+from django.contrib import admin
+from scipy_central.thumbs.models import Thumbs
+
+class ThumbsAdmin(admin.ModelAdmin):
+ field_sets = (
+ (None,
+ {'fields': ('content_type', 'object_pk')}
+ ),
+ (_('Content'),
+ {'fields': ('person', 'vote', 'is_valid')}
+ ),
+ (_('Meta'),
+ {'fields': ('submit_date', 'ip_address', 'user_agent')}
+ ),
+ )
+ list_display = ('vote', 'is_valid', 'person', 'content_type', 'object_pk', 'ip_address', 'submit_date')
+ list_filter = ('submit_date', 'is_valid')
+ ordering = ('-submit_date',)
+ search_fields = ('person__username', 'person__email', 'ip_address')
+
+ def save_model(self, request, obj, form, change):
+ if not obj.is_valid:
+ obj.vote = None
+ super(ThumbsAdmin, self).save_model(request, obj, form, change)
+
+admin.site.register(Thumbs, ThumbsAdmin)
diff --git a/scipy_central/thumbs/forms.py b/scipy_central/thumbs/forms.py
new file mode 100644
index 0000000..4562ab4
--- /dev/null
+++ b/scipy_central/thumbs/forms.py
@@ -0,0 +1,17 @@
+from django import forms
+
+# to identify objects
+thumb_choices = (
+ ('r', 'revision'),
+ ('c', 'comment'),
+)
+
+thumb_types = (
+ ('up', 'up_vote'),
+ ('down', 'down_vote'),
+)
+
+class ThumbsForm(forms.Form):
+ thumb_for = forms.ChoiceField(choices=thumb_choices)
+ thumb_as = forms.ChoiceField(choices=thumb_types)
+ object_pk = forms.IntegerField()
diff --git a/scipy_central/thumbs/models.py b/scipy_central/thumbs/models.py
index 6b690cd..cb99479 100644
--- a/scipy_central/thumbs/models.py
+++ b/scipy_central/thumbs/models.py
@@ -1,3 +1,8 @@
+# django imports
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext_lazy as _
from django.db import models
class Thumbs(models.Model):
@@ -6,18 +11,21 @@ class Thumbs(models.Model):
"""
# Who is voting? Null for anonymous users.
- person = models.ForeignKey('scipy_central.Person', null=True, blank=True)
-
- # submission: if voting for a submission, otherwise Null.
- submission = models.ForeignKey('scipy_central.Submission', null=True,
- blank=True)
-
- # Which comment is being voted on. Can be null.
- comment = models.ForeignKey('scipy_central.Comment', null=True,
- blank=True)
+ person = models.ForeignKey(User)
+
+ # Vote for object - Which object to vote?
+ content_type = models.ForeignKey(ContentType,
+ verbose_name=_('content type'),
+ related_name="content_type_set_for_%(class)s"
+ )
+ object_pk = models.TextField(_('object ID'))
+ content_object = generic.GenericForeignKey(
+ ct_field="content_type",
+ fk_field="object_pk"
+ )
# When the vote was cast
- date_time = models.DateField(auto_now=True)
+ submit_date = models.DateField(auto_now=True)
# IP_address: for abuse prevention
ip_address = models.IPAddressField()
@@ -25,5 +33,14 @@ class Thumbs(models.Model):
# user_agent: web browser's user agent: for abuse prevention
user_agent = models.CharField(max_length=255)
- # vote: ``True`` is thumbs up and ``False`` is thumbs down
- vote = models.BooleanField()
+ # vote: ``True`` is thumbs up, ``False`` is thumbs down
+ vote = models.NullBooleanField(default=None)
+
+ # vote is valid?
+ is_valid = models.BooleanField(default=True)
+
+ class Meta:
+ unique_together = ( ("person", "content_type", "object_pk"), )
+
+ def __unicode__(self):
+ return "%s: %s" % (self.person.username, self.vote)
diff --git a/scipy_central/thumbs/scale.py b/scipy_central/thumbs/scale.py
new file mode 100644
index 0000000..ef4d192
--- /dev/null
+++ b/scipy_central/thumbs/scale.py
@@ -0,0 +1,36 @@
+"""
+Scale for thumbs objects. The magnitude of up-vote or down-vote
+is set here. Any changes in these settings also require following settings
+
+1. Manual changes of `reputation` fields in `Revision` objects in database
+2. Scale settings in `static/thumbs/thumb-actions.js`
+
+For instance, the following code has to be run for [1]
+
+```
+from scipy_central.submission.models import Revision
+for aObj in Revision.objects.all():
+ aObj.reputation = aObj.set_reputation()
+```
+
+"""
+REVISION_VOTE = {
+ # reputation scale for revision object
+ 'thumb': {
+ 'up': 1,
+ 'down': 1
+ },
+ 'user': {
+ 'up': 5,
+ 'down': 2
+ }
+}
+
+COMMENT_VOTE = {
+ 'thumb': {
+ 'up': 1,
+ },
+ 'user': {
+ 'up': 2,
+ }
+}
diff --git a/scipy_central/thumbs/signals.py b/scipy_central/thumbs/signals.py
new file mode 100644
index 0000000..e4c89a0
--- /dev/null
+++ b/scipy_central/thumbs/signals.py
@@ -0,0 +1,79 @@
+from scipy_central.thumbs.models import Thumbs
+from django.db.models.signals import pre_save, post_save, pre_delete, post_delete
+from django.dispatch import receiver
+import logging
+
+logger = logging.getLogger("scipycentral")
+logger.debug("Initializing Thumbs::signals.py")
+
+def perform_score(thumb_obj):
+ ct_obj = thumb_obj.content_object
+ if hasattr(ct_obj, "score"):
+ ct_obj.score = ct_obj.set_score()
+ ct_obj.save()
+
+def perform_reputation(thumb_obj, prev_vote, created):
+ ct_obj = thumb_obj.content_object
+ # update `reputation` field
+ ct_obj.reputation = ct_obj.calculate_reputation(
+ vote=thumb_obj.vote,
+ prev_vote=prev_vote
+ )
+ ct_obj.save()
+
+ # update user profile `reputation` fields
+ ct_user = None
+ if hasattr(ct_obj, "created_by"):
+ ct_user = ct_obj.created_by
+ elif hasattr(ct_obj, "user"):
+ ct_user = ct_obj.user
+ try:
+ ct_user.profile.reputation = ct_user.profile.calculate_reputation(
+ vote=thumb_obj.vote,
+ prev_vote=prev_vote,
+ vote_for=ct_obj._meta.module_name
+ )
+ ct_user.profile.save()
+ except AttributeError, e:
+ logger.debug("Unable to find `User` profile for content object" + e)
+
+@receiver(pre_save, sender=Thumbs)
+def update_reputation(sender, instance, **kwargs):
+ """
+ Signal receiver function to update `reputation`
+ field in Models.
+
+ `thumb_obj` is valid by default. May be we might want to
+ allow admins to approve newbie votes via email where is_valid=False
+ for them. This value has to be set to True in this receiver function
+ """
+ created, prev_vote = True, None
+ if instance.pk:
+ prev_vote = Thumbs.objects.get(pk=instance.pk).vote
+ created = False
+ if instance.vote == prev_vote:
+ instance.vote = None
+ perform_reputation(instance, prev_vote, created)
+
+@receiver(post_save, sender=Thumbs)
+def update_wilson_score(sender, instance, **kwargs):
+ perform_score(instance)
+
+@receiver(pre_delete, sender=Thumbs)
+def remove_reputation(sender, instance, using, **kwargs):
+ """
+ The method is triggered when `Thumbs` object is deleted.
+ The reputation field of content_object is updated
+ once the object is set to delete
+ """
+ created = False
+ prev_vote = instance.vote
+ instance.vote = None
+ perform_reputation(instance, prev_vote, created)
+
+@receiver(post_delete, sender=Thumbs)
+def remove_wilson_score(sender, instance, **kwargs):
+ """
+ Update wilson score after deleting object
+ """
+ perform_score(instance)
diff --git a/scipy_central/thumbs/static/thumbs/thumb-actions.js b/scipy_central/thumbs/static/thumbs/thumb-actions.js
new file mode 100644
index 0000000..877acd1
--- /dev/null
+++ b/scipy_central/thumbs/static/thumbs/thumb-actions.js
@@ -0,0 +1,100 @@
+/*
+JavaScript definitons to handle reputation system on site
+*/
+
+(function($) {
+$(document).ready(function() {
+
+ var $SPC_POPOVER = $('.spc-popover');
+
+ function show_vote(thumb_as, thumb_for, target, other, vote_count) {
+ var fact = -1;
+ if (thumb_as === 'up')
+ fact = 1;
+ if (target.hasClass('active')) {
+ vote_count -= fact * 1;
+ target.removeClass('active');
+ } else {
+ vote_count += fact * 1;
+ if (other.hasClass('active'))
+ vote_count += fact * 1;
+ target.addClass('active');
+ if (thumb_for === 'r')
+ other.removeClass('active');
+ }
+ return vote_count;
+ }
+
+ function hide_vote(parent, other, vote_as, count_obj, count) {
+ parent.removeClass('active');
+ other.removeClass('active');
+ if (vote_as == true)
+ parent.addClass('active');
+ if (vote_as == false)
+ other.addClass('active');
+ count_obj.html(count);
+ return false;
+ }
+
+ function submit_thumb(event) {
+ var $target = $(event.target);
+ var $parent = $target.parent();
+ var $vote = $parent.find('.vote-count');
+ var thumb_for = $target.data('thumb-for');
+ var thumb_as = $target.data('thumb-as');
+ var object_pk = $target.data('object-id');
+
+ var vote_count = parseInt($vote.html().trim(), 10);
+
+ if (thumb_as === 'up')
+ var $other = $parent.find('.down-arrow');
+ else if (thumb_as === 'down')
+ var $other = $parent.find('.up-arrow');
+
+ // original vals
+ var prev_count = vote_count;
+ var prev_thumb;
+ if ($target.hasClass('active'))
+ prev_thumb = true;
+ if ($other.hasClass('active'))
+ prev_thumb = false;
+
+ vote_count = show_vote(thumb_as, thumb_for, $target, $other, vote_count);
+ $vote.html(vote_count);
+ $.ajax({
+ type: 'post',
+ url: '/thumbs/post/',
+ data: {
+ 'thumb_for': thumb_for,
+ 'thumb_as': thumb_as,
+ 'object_pk': object_pk
+ },
+ success: function(response) {
+ if (!response.success) {
+ hide_vote($target, $other, prev_thumb, $vote, prev_count);
+ }
+ },
+ error: function(XMLHttpRequest, textStatus, errorThrown) {
+ hide_vote($target, $other, prev_thumb, $vote, prev_count);
+ }
+
+ });
+ }
+
+ $('body').on('click', "[data-submit='thumb']", function(event) {
+ event.stopPropagation();
+ $.when(submit_thumb(event)).done();
+ });
+
+ $('body').on('click', "[data-submit='auth']", function(event) {
+ event.stopPropagation();
+ var $target = $(event.target);
+ var $parent = $target.parent();
+
+ $SPC_POPOVER.popover('destroy');
+ $parent.find("[data-resp='error-info']").popover('show');
+
+ });
+
+});
+})(window.jQuery);
diff --git a/scipy_central/thumbs/templates/thumbs/comment-form.html b/scipy_central/thumbs/templates/thumbs/comment-form.html
new file mode 100644
index 0000000..581a5e6
--- /dev/null
+++ b/scipy_central/thumbs/templates/thumbs/comment-form.html
@@ -0,0 +1,23 @@
+{% load thumbs %}
+
+
+ {{comment.reputation}}
+
+ {% if request.user.is_authenticated and request.user != comment.user %}
+
+
+
+
+ {% else %}
+
+ {% if request.user == comment.user %}
+
+ {% else %}
+
+ {% endif %}
+ {% endif %}
+
diff --git a/scipy_central/thumbs/templates/thumbs/form.html b/scipy_central/thumbs/templates/thumbs/form.html
new file mode 100644
index 0000000..a612b55
--- /dev/null
+++ b/scipy_central/thumbs/templates/thumbs/form.html
@@ -0,0 +1,35 @@
+{% load thumbs %}
+
+
+
+{% if request.user.is_authenticated and request.user != item.created_by and item.enable_reputation %}
+
+
+
+
{{item.reputation}}
+
+
+
+
+
+{% elif not item.enable_reputation %}
+
{{item.reputation}}
+
+{% else %}
+
+
+
+
{{item.reputation}}
+
+
+ {% if request.user == item.created_by %}
+
+ {% else %}
+
+ {% endif %}
+
+{% endif %}
diff --git a/scipy_central/thumbs/templates/thumbs/thumb-entry.html b/scipy_central/thumbs/templates/thumbs/thumb-entry.html
new file mode 100644
index 0000000..5eda52f
--- /dev/null
+++ b/scipy_central/thumbs/templates/thumbs/thumb-entry.html
@@ -0,0 +1,20 @@
+
+
diff --git a/scipy_central/thumbs/templatetags/__init__.py b/scipy_central/thumbs/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/scipy_central/thumbs/templatetags/thumbs.py b/scipy_central/thumbs/templatetags/thumbs.py
new file mode 100644
index 0000000..5d50de6
--- /dev/null
+++ b/scipy_central/thumbs/templatetags/thumbs.py
@@ -0,0 +1,108 @@
+from django import template
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.loading import get_model
+from django.contrib.auth.models import User
+from django.contrib import comments
+from scipy_central.submission.models import Revision
+from scipy_central.thumbs.models import Thumbs
+from scipy_central.thumbs.scale import REVISION_VOTE, COMMENT_VOTE
+
+
+register = template.Library()
+
+@register.filter
+def vote(userObj, content_obj):
+ """
+ args:
+ userObj - django.contrib.auth.models.User object
+ revisionObj - scipy_central.submission.models.Revision
+ return:
+ scipy_central.submission.models.Thumbs.vote if object present (or)
+ None
+
+ return type: string
+
+ Exceptions: closes silently!
+
+ Example:
+ {{request.user|vote:item}}
+ {% ifequal request.user|vote:item "True" %}active{% endifequal %}
+
+ """
+ try:
+ app_name = content_obj._meta.app_label
+ module_name = content_obj._meta.module_name
+ content_model = get_model(app_name, module_name)
+ except (AttributeError, ObjectDoesNotExist):
+ return str(None)
+
+ if isinstance(userObj, User):
+ try:
+ thumb = content_obj.thumbs.get(person=userObj)
+ return str(thumb.vote)
+ except:
+ pass
+ return str(None)
+
+@register.filter
+def get_reputation(user_obj):
+ """
+ NOTE: This filter is deprecated, not used anywhere now.
+ Instead `reputation` is stored in field than calculated
+ everytime.
+
+ args:
+ `django.contrib.auth.models.User` object
+ returns:
+ total user's reputation
+ Example:
+ `{{user_obj|get_reputation}}
+
+ Description:
+ Reputation is calculated based on the scale
+ `scipy_central.thumbs.scale`
+
+ All user created Revision, SpcComment objects votes
+ are taken and calculated dynamically!
+ For calculation of reputation, we are not using `reputation`
+ field in `scipy_central.person.models.UserProfile`.
+ This helps us to dynamically scale the reputation value. For instance
+ if we change magnitude in scale.py (as mentioned above), the
+ whole reputation gets automatically scaled
+ """
+ score = 0
+ revs = Revision.objects.filter(
+ is_displayed=True,
+ created_by=user_obj)
+ all_comments = comments.get_model().objects.filter(
+ user=user_obj,
+ is_public=True,
+ is_removed=False
+ )
+
+ for obj in revs:
+ all_votes = obj.allvotes_count()
+ up_votes = obj.upvotes_count()
+ down_votes = all_votes - up_votes
+ score += (REVISION_VOTE["user"]["up"] * up_votes) - \
+ (REVISION_VOTE["user"]["down"] * down_votes)
+
+ for obj in all_comments:
+ all_votes = obj.thumbs.filter(is_valid=True, vote=True).count()
+ score += COMMENT_VOTE["user"]["up"] * all_votes
+
+ return score
+
+@register.assignment_tag
+def my_votes(user_object, count=20):
+ """
+ Returns list of recent user votes
+ """
+ if isinstance(user_object, User):
+ thumbs_list = Thumbs.objects.filter(
+ person=user_object,
+ is_valid=True,
+ ).exclude(vote=None)
+ return thumbs_list.order_by('-submit_date')[:count]
+ else:
+ raise template.TemplateSyntaxError("Invalid argument")
diff --git a/scipy_central/thumbs/urls.py b/scipy_central/thumbs/urls.py
new file mode 100644
index 0000000..0d783a6
--- /dev/null
+++ b/scipy_central/thumbs/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import patterns, url
+
+urlpatterns = patterns('scipy_central.thumbs.views',
+
+ # thumb post request
+ url(r'^post/$', 'post_thumbs', name='spc-post-thumbs'),
+)
diff --git a/scipy_central/thumbs/views.py b/scipy_central/thumbs/views.py
index e69de29..27be355 100644
--- a/scipy_central/thumbs/views.py
+++ b/scipy_central/thumbs/views.py
@@ -0,0 +1,99 @@
+# python, django imports
+import simplejson, datetime
+from django.contrib import comments
+from django.contrib.contenttypes.models import ContentType
+from django.views.decorators.csrf import csrf_protect
+from django.views.decorators.http import require_POST
+from django.shortcuts import Http404, get_object_or_404
+from django.http import HttpResponse
+
+# scipy central imports
+from scipy_central.submission.models import Revision
+from scipy_central import submission, utils
+from scipy_central.thumbs.forms import ThumbsForm
+from scipy_central.thumbs.models import Thumbs
+
+@csrf_protect
+@require_POST
+def post_thumbs(request):
+ # If user is authenticated and using ajax call
+ if request.user.is_authenticated() and request.is_ajax():
+ data = {'success': False, 'removed': False}
+ form = ThumbsForm(request.POST)
+ if form.is_valid():
+ form = form.cleaned_data
+ # if voting for (r) Revision object
+ if form['thumb_for'] == 'r':
+ thumb_obj = get_object_or_404(Revision, pk=form['object_pk'])
+ ct_type = get_object_or_404(ContentType,
+ app_label='submission',
+ model='revision')
+ # if voting for (c) SpcComment object
+ elif form['thumb_for'] == 'c':
+ thumb_obj = get_object_or_404(comments.get_model(), pk=form['object_pk'])
+ ct_type = get_object_or_404(ContentType,
+ app_label='comments',
+ model='spccomment')
+ else:
+ raise Http404
+
+ vote = None
+ if form['thumb_as'] == 'up':
+ vote = True
+ elif form['thumb_as'] == 'down' and form['thumb_for'] != 'c':
+ vote = False
+ else:
+ raise Http404
+
+ ct_obj = ct_type.get_object_for_this_type(pk=form['object_pk'])
+ if form['thumb_for'] == 'r':
+ if ct_obj.created_by == request.user:
+ raise Http404
+ elif form['thumb_for'] == 'c':
+ if ct_obj.user == request.user:
+ raise Http404
+
+ # check moderation settings
+ if not ct_obj.enable_reputation:
+ raise Http404
+
+ ip_address = utils.get_IP_address(request)
+ submit_date = datetime.datetime.now()
+ user_agent = request.META['HTTP_USER_AGENT']
+
+ # get or create Thumbs object
+ thumb_obj, created = Thumbs.objects.get_or_create(
+ person = request.user,
+ content_type = ct_type,
+ object_pk = form['object_pk'],
+ defaults = {
+ 'submit_date': submit_date,
+ 'ip_address': ip_address,
+ 'user_agent': user_agent,
+ 'vote': vote,
+ }
+ )
+
+ # if User vote already present, change it
+ if not created:
+ # is_vaid has to be explicitly made True
+ # If `is_valid` might be made False from admin
+ thumb_obj.is_valid = True
+ if vote == thumb_obj.vote:
+ thumb_obj.vote = None
+ data['removed'] = True
+ else:
+ thumb_obj.vote = vote
+
+ thumb_obj.ip_address = ip_address
+ thumb_obj.submit_date = submit_date
+ thumb_obj.save()
+
+ ct_obj = ct_type.get_object_for_this_type(pk=form['object_pk'])
+ data['votes'] = ct_obj.reputation
+ data['success'] = True
+
+ return HttpResponse(simplejson.dumps(data), mimetype="application/json")
+
+ else:
+ raise Http404
diff --git a/scipy_central/urls.py b/scipy_central/urls.py
index 62c5889..6672667 100644
--- a/scipy_central/urls.py
+++ b/scipy_central/urls.py
@@ -32,6 +32,9 @@
# comments
(r'^comments/', include('scipy_central.comments.urls')),
+
+ # reputation
+ (r'^thumbs/', include('scipy_central.thumbs.urls')),
)
from django.conf import settings
settings.LOGIN_REDIRECT_URL = '/user/profile/'