Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

0.1.dev8 - Initial Commit

  • Loading branch information...
commit 75fd9ed30b3720a392583cc6ca9eccda1fd6681a 0 parents
Patrick Altman authored October 19, 2011
27  LICENSE
... ...
@@ -0,0 +1,27 @@
  1
+Copyright (c) 2011, Eldarion, Inc.
  2
+All rights reserved.
  3
+
  4
+Redistribution and use in source and binary forms, with or without modification,
  5
+are permitted provided that the following conditions are met:
  6
+
  7
+    * Redistributions of source code must retain the above copyright notice,
  8
+      this list of conditions and the following disclaimer.
  9
+
  10
+    * Redistributions in binary form must reproduce the above copyright notice,
  11
+      this list of conditions and the following disclaimer in the documentation
  12
+      and/or other materials provided with the distribution.
  13
+
  14
+    * Neither the name of Eldarion, Inc. nor the names of its contributors may
  15
+      be used to endorse or promote products derived from this software without
  16
+      specific prior written permission.
  17
+
  18
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  22
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  25
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2  MANIFEST.in
... ...
@@ -0,0 +1,2 @@
  1
+include README.rst
  2
+recursive-include agon_ratings/templates *.html
11  README.rst
Source Rendered
... ...
@@ -0,0 +1,11 @@
  1
+agon-ratings
  2
+============
  3
+
  4
+Provides user ratings of objects.
  5
+
  6
+
  7
+Documentation
  8
+-------------
  9
+
  10
+Documentation can be found online at http://agon-ratings.readthedocs.org/.
  11
+
1  agon_ratings/__init__.py
... ...
@@ -0,0 +1 @@
  1
+__version__ = "0.1.dev8"
16  agon_ratings/managers.py
... ...
@@ -0,0 +1,16 @@
  1
+from django.db import models
  2
+
  3
+from django.contrib.contenttypes.models import ContentType
  4
+
  5
+
  6
+class OverallRatingManager(models.Manager):
  7
+    
  8
+    def top_rated(self, klass):
  9
+        
  10
+        return self.filter(
  11
+            content_type=ContentType.objects.get_for_model(klass)
  12
+        ).extra(
  13
+            select={
  14
+                "sortable_rating": "COALESCE(rating, 0)"
  15
+            }
  16
+        ).order_by("-sortable_rating")
51  agon_ratings/models.py
... ...
@@ -0,0 +1,51 @@
  1
+import datetime
  2
+
  3
+from decimal import Decimal
  4
+
  5
+from django.db import models
  6
+
  7
+from django.contrib.auth.models import User
  8
+from django.contrib.contenttypes.generic import GenericForeignKey
  9
+from django.contrib.contenttypes.models import ContentType
  10
+
  11
+from agon_ratings.managers import OverallRatingManager
  12
+
  13
+
  14
+class OverallRating(models.Model):
  15
+    
  16
+    object_id = models.IntegerField(db_index=True)
  17
+    content_type = models.ForeignKey(ContentType)
  18
+    content_object = GenericForeignKey()
  19
+    rating = models.DecimalField(decimal_places=1, max_digits=3, null=True)
  20
+    
  21
+    objects = OverallRatingManager()
  22
+    
  23
+    class Meta:
  24
+        unique_together = [
  25
+            ("object_id", "content_type"),
  26
+        ]
  27
+    
  28
+    def update(self):
  29
+        self.rating = Rating.objects.filter(
  30
+            overall_rating = self
  31
+        ).aggregate(r = models.Avg("rating"))["r"]
  32
+        self.rating = Decimal(str(self.rating or "0"))
  33
+        self.save()
  34
+
  35
+
  36
+class Rating(models.Model):
  37
+    overall_rating = models.ForeignKey(OverallRating, null = True, related_name = "ratings")
  38
+    object_id = models.IntegerField(db_index=True)
  39
+    content_type = models.ForeignKey(ContentType)
  40
+    content_object = GenericForeignKey()
  41
+    user = models.ForeignKey(User)
  42
+    rating = models.IntegerField()
  43
+    timestamp = models.DateTimeField(default=datetime.datetime.now)
  44
+    
  45
+    class Meta:
  46
+        unique_together = [
  47
+            ("object_id", "content_type", "user"),
  48
+        ]
  49
+    
  50
+    def __unicode__(self):
  51
+        return unicode(self.rating)
4  agon_ratings/templates/agon_ratings/_rate_form.html
... ...
@@ -0,0 +1,4 @@
  1
+<form action="{% url agon_ratings_rate content_type_id=ct.id object_id=obj.pk %}" method="POST">
  2
+    {% csrf_token %}
  3
+    <input type="hidden" name="rating" id="id_rating" />
  4
+</form>
0  agon_ratings/templatetags/__init__.py
No changes.
0  agon_ratings/templatetags/__init__.py b/build/lib/agon_ratings/templatetags/__init__.py
No changes.
96  agon_ratings/templatetags/agon_ratings_tags.py
... ...
@@ -0,0 +1,96 @@
  1
+from django import template
  2
+
  3
+from django.contrib.contenttypes.models import ContentType
  4
+
  5
+from agon_ratings.models import Rating, OverallRating
  6
+
  7
+
  8
+register = template.Library()
  9
+
  10
+
  11
+class UserRatingNode(template.Node):
  12
+    
  13
+    @classmethod
  14
+    def handle_token(cls, parser, token):
  15
+        bits = token.split_contents()
  16
+        if len(bits) != 7:
  17
+            raise template.TemplateSyntaxError()
  18
+        return cls(
  19
+            user = parser.compile_filter(bits[2]),
  20
+            obj = parser.compile_filter(bits[4]),
  21
+            as_var = bits[6]
  22
+        )
  23
+    
  24
+    def __init__(self, user, obj, as_var):
  25
+        self.user = user
  26
+        self.obj = obj
  27
+        self.as_var = as_var
  28
+    
  29
+    def render(self, context):
  30
+        user = self.user.resolve(context)
  31
+        obj = self.obj.resolve(context)
  32
+        try:
  33
+            ct = ContentType.objects.get_for_model(obj)
  34
+            rating = Rating.objects.get(
  35
+                object_id = obj.pk,
  36
+                content_type = ct,
  37
+                user = user
  38
+            ).rating
  39
+        except Rating.DoesNotExist:
  40
+            rating = 0
  41
+        context[self.as_var] = rating
  42
+        return ""
  43
+
  44
+
  45
+@register.tag
  46
+def user_rating(parser, token):
  47
+    """
  48
+    Usage:
  49
+        {% user_rating for user and obj as var %}
  50
+    """
  51
+    return UserRatingNode.handle_token(parser, token)
  52
+
  53
+
  54
+class OverallRatingNode(template.Node):
  55
+    
  56
+    @classmethod
  57
+    def handle_token(cls, parser, token):
  58
+        bits = token.split_contents()
  59
+        if len(bits) != 5:
  60
+            raise template.TemplateSyntaxError()
  61
+        return cls(
  62
+            obj = parser.compile_filter(bits[2]),
  63
+            as_var = bits[4]
  64
+        )
  65
+    
  66
+    def __init__(self, obj, as_var):
  67
+        self.obj = obj
  68
+        self.as_var = as_var
  69
+    
  70
+    def render(self, context):
  71
+        obj = self.obj.resolve(context)
  72
+        try:
  73
+            ct = ContentType.objects.get_for_model(obj)
  74
+            rating = OverallRating.objects.get(
  75
+                object_id=obj.pk,
  76
+                content_type=ct
  77
+            ).rating or 0
  78
+        except OverallRating.DoesNotExist:
  79
+            rating = 0
  80
+        context[self.as_var] = rating
  81
+        return ""
  82
+
  83
+
  84
+@register.tag
  85
+def overall_rating(parser, token):
  86
+    """
  87
+    Usage:
  88
+        {% overall_rating for obj as var %}
  89
+    """
  90
+    return OverallRatingNode.handle_token(parser, token)
  91
+
  92
+
  93
+@register.inclusion_tag("agon_ratings/_rate_form.html")
  94
+def user_rate_form(obj):
  95
+    ct = ContentType.objects.get_for_model(obj)
  96
+    return {"ct": ct, "obj": obj}
6  agon_ratings/urls.py
... ...
@@ -0,0 +1,6 @@
  1
+from django.conf.urls.defaults import *
  2
+
  3
+
  4
+urlpatterns = patterns("agon_ratings.views",
  5
+    url(r"^(?P<content_type_id>\d+)/(?P<object_id>\d+)/rate/$", "rate", name="agon_ratings_rate"),
  6
+)
66  agon_ratings/views.py
... ...
@@ -0,0 +1,66 @@
  1
+from django.conf import settings
  2
+from django.http import HttpResponse, HttpResponseForbidden
  3
+from django.shortcuts import get_object_or_404
  4
+from django.utils import simplejson as json
  5
+from django.views.decorators.http import require_POST
  6
+
  7
+from django.contrib.auth.decorators import login_required
  8
+from django.contrib.contenttypes.models import ContentType
  9
+
  10
+from agon_ratings.models import Rating, OverallRating
  11
+
  12
+
  13
+NUM_OF_RATINGS = getattr(settings, "AGON_NUM_OF_RATINGS", 5)
  14
+
  15
+
  16
+@require_POST
  17
+@login_required
  18
+def rate(request, content_type_id, object_id):
  19
+    ct = get_object_or_404(ContentType, pk=content_type_id)
  20
+    obj = get_object_or_404(ct.model_class(), pk=object_id)
  21
+    rating_input = int(request.POST.get("rating"))
  22
+    
  23
+    data = {
  24
+        "user_rating": rating_input,
  25
+        "overall_rating": 0
  26
+    }
  27
+    
  28
+    # @@@ Seems like this could be much more DRY with a model method or something
  29
+    if rating_input == 0: # clear the rating
  30
+        try:
  31
+            rating = Rating.objects.get(
  32
+                object_id = object_id,
  33
+                content_type = ct,
  34
+                user = request.user
  35
+            )
  36
+            overall = rating.overall_rating
  37
+            rating.delete()
  38
+            overall.update()
  39
+            data["overall_rating"] = str(overall.rating)
  40
+        except Rating.DoesNotExist:
  41
+            pass
  42
+    elif 1 <= rating_input <= NUM_OF_RATINGS: # set the rating
  43
+        print rating_input
  44
+        rating, created = Rating.objects.get_or_create(
  45
+            object_id = obj.pk,
  46
+            content_type = ct,
  47
+            user = request.user,
  48
+            defaults = {
  49
+                "rating": rating_input
  50
+            }
  51
+        )
  52
+        overall, created = OverallRating.objects.get_or_create(
  53
+            object_id = obj.pk,
  54
+            content_type = ct
  55
+        )
  56
+        rating.overall_rating = overall
  57
+        rating.rating = rating_input
  58
+        rating.save()
  59
+        overall.update()
  60
+        data["overall_rating"] = str(overall.rating)
  61
+    else: # whoops
  62
+        return HttpResponseForbidden(
  63
+            "Invalid rating. It must be a value between 0 and %s" % NUM_OF_RATINGS
  64
+        )
  65
+    
  66
+    return HttpResponse(json.dumps(data), mimetype="application/json")
1  build/lib/agon_ratings/__init__.py
... ...
@@ -0,0 +1 @@
  1
+__version__ = "0.1.dev1"
16  build/lib/agon_ratings/managers.py
... ...
@@ -0,0 +1,16 @@
  1
+from django.db import models
  2
+
  3
+from django.contrib.contenttypes.models import ContentType
  4
+
  5
+
  6
+class OverallRatingManager(models.Manager):
  7
+    
  8
+    def top_rated(self, klass):
  9
+        
  10
+        return self.filter(
  11
+            content_type=ContentType.objects.get_for_model(klass)
  12
+        ).extra(
  13
+            select={
  14
+                "sortable_rating": "COALESCE(rating, 0)"
  15
+            }
  16
+        ).order_by("-sortable_rating")
48  build/lib/agon_ratings/models.py
... ...
@@ -0,0 +1,48 @@
  1
+import datetime
  2
+
  3
+from decimal import Decimal
  4
+
  5
+from django.db import models
  6
+
  7
+from django.contrib.auth.models import User
  8
+from django.contrib.contenttypes.generic import GenericForeignKey
  9
+from django.contrib.contenttypes.models import ContentType
  10
+
  11
+from agon_ratings.managers import OverallRatingManager
  12
+
  13
+
  14
+class OverallRating(models.Model):
  15
+    
  16
+    object_id = models.IntegerField(db_index=True)
  17
+    content_type = models.ForeignKey(ContentType)
  18
+    content_object = GenericForeignKey()
  19
+    rating = models.DecimalField(decimal_places=1, max_digits=3, null=True)
  20
+    
  21
+    objects = OverallRatingManager()
  22
+    
  23
+    class Meta:
  24
+        unique_together = [
  25
+            ("object_id", "content_type"),
  26
+        ]
  27
+    
  28
+    def update(self):
  29
+        self.rating = Rating.objects.filter(
  30
+            overall_rating = self
  31
+        ).aggregate(r = models.Avg("rating"))["r"]
  32
+        self.rating = Decimal(str(self.rating or "0"))
  33
+        self.save()
  34
+
  35
+
  36
+class Rating(models.Model):
  37
+    overall_rating = models.ForeignKey(OverallRating, null = True, related_name = "ratings")
  38
+    object_id = models.IntegerField(db_index=True)
  39
+    content_type = models.ForeignKey(ContentType)
  40
+    content_object = GenericForeignKey()
  41
+    user = models.ForeignKey(User)
  42
+    rating = models.IntegerField()
  43
+    timestamp = models.DateTimeField(default=datetime.datetime.now)
  44
+    
  45
+    class Meta:
  46
+        unique_together = [
  47
+            ("object_id", "content_type", "user"),
  48
+        ]
4  build/lib/agon_ratings/templates/_rate_form.html
... ...
@@ -0,0 +1,4 @@
  1
+<form action="{% url agon_ratings_rate content_type_id=ct.id object_id=obj.pk %}" method="POST">
  2
+    {% csrf_token %
  3
+    <input type="hidden" name="rating" id="id_rating" />
  4
+</form>
94  build/lib/agon_ratings/templatetags/agon_ratings_tags.py
... ...
@@ -0,0 +1,94 @@
  1
+from django import template
  2
+
  3
+from django.contrib.contenttypes.models import ContentType
  4
+
  5
+from agon_ratings.models import Rating, OverallRating
  6
+
  7
+
  8
+register = template.Library()
  9
+
  10
+
  11
+class UserRatingNode(template.Node):
  12
+    
  13
+    @classmethod
  14
+    def handle_token(cls, parser, token):
  15
+        bits = token.split_contents()
  16
+        if len(bits) != 7:
  17
+            raise template.TemplateSyntaxError()
  18
+        return cls(
  19
+            user = parser.compile_filter(bits[2]),
  20
+            obj = parser.compile_filter(bits[4]),
  21
+            as_var = bits[6]
  22
+        )
  23
+    
  24
+    def __init__(self, user, obj, as_var):
  25
+        self.user = user
  26
+        self.obj = obj
  27
+        self.as_var = as_var
  28
+    
  29
+    def render(self, context):
  30
+        user = self.user.resolve(context)
  31
+        obj = self.obj.resolve(context)
  32
+        try:
  33
+            ct = ContentType.objects.get_for_model(obj)
  34
+            rating = Rating.objects.get(
  35
+                object_id = obj.pk,
  36
+                content_type = ct,
  37
+                user = user
  38
+            ).rating
  39
+        except Rating.DoesNotExist:
  40
+            rating = 0
  41
+        context[self.as_var] = rating
  42
+
  43
+
  44
+@register.tag
  45
+def user_rating(parser, token):
  46
+    """
  47
+    Usage:
  48
+        {% user_rating for user and obj as var %}
  49
+    """
  50
+    return UserRatingNode.handle_token(parser, token)
  51
+
  52
+
  53
+class OverallRatingNode(template.Node):
  54
+    
  55
+    @classmethod
  56
+    def handle_token(cls, parser, token):
  57
+        bits = token.split_contents()
  58
+        if len(bits) != 5:
  59
+            raise template.TemplateSyntaxError()
  60
+        return cls(
  61
+            obj = parser.compile_filter(bits[2]),
  62
+            as_var = bits[4]
  63
+        )
  64
+    
  65
+    def __init__(self, obj, as_var):
  66
+        self.obj = obj
  67
+        self.as_var = as_var
  68
+    
  69
+    def render(self, context):
  70
+        obj = self.obj.resolve(context)
  71
+        try:
  72
+            ct = ContentType.objects.get_for_model(obj)
  73
+            rating = OverallRating.objects.get(
  74
+                object_id=obj.pk,
  75
+                content_type=ct
  76
+            ).rating or 0
  77
+        except Rating.DoesNotExist:
  78
+            rating = 0
  79
+        context[self.as_var] = rating
  80
+
  81
+
  82
+@register.tag
  83
+def overall_rating(parser, token):
  84
+    """
  85
+    Usage:
  86
+        {% overall_rating for obj as var %}
  87
+    """
  88
+    return OverallRatingNode.handle_token(parser, token)
  89
+
  90
+
  91
+@register.inclusion_tag("agon_ratings/_rate_form.html")
  92
+def user_rate_form(obj):
  93
+    ct = ContentType.objects.get_for_model(obj)
  94
+    return {"ct": ct, "obj": obj}
6  build/lib/agon_ratings/urls.py
... ...
@@ -0,0 +1,6 @@
  1
+from django.conf.urls.defaults import *
  2
+
  3
+
  4
+urlpatterns = patterns("agon_ratings.views",
  5
+    url(r"^(?P<content_type_id>\d+)/(?P<object_id>\d+)/rate/$", "rate", name="agon_ratings_rate"),
  6
+)
64  build/lib/agon_ratings/views.py
... ...
@@ -0,0 +1,64 @@
  1
+from django.conf import settings
  2
+from django.http import HttpResponse, HttpResponseForbidden
  3
+from django.shortcuts import get_object_or_404
  4
+from django.utils import simplejson as json
  5
+from django.views.decorators.http import require_POST
  6
+
  7
+from django.contrib.auth.decorators import login_required
  8
+from django.contrib.contenttypes.models import ContentType
  9
+
  10
+from agon_ratings.models import Rating, OverallRating
  11
+
  12
+
  13
+NUM_OF_RATINGS = getattr(settings, "AGON_NUM_OF_RATINGS", 5)
  14
+
  15
+
  16
+@require_POST
  17
+@login_required
  18
+def rate(request, content_type_id, object_id):
  19
+    ct = get_object_or_404(ContentType, pk=content_type_id)
  20
+    obj = get_object_or_404(ct.model_class(), pk=object_id)
  21
+    vote = int(request.POST.get("vote"))
  22
+    
  23
+    data = {
  24
+        "user_rating": vote,
  25
+        "overall_rating": 0
  26
+    }
  27
+    
  28
+    # @@@ Seems like this could be much more DRY with a model method or something
  29
+    if vote == 0: # clear the rating
  30
+        try:
  31
+            rating = Rating.objects.get(
  32
+                object_id = object_id,
  33
+                content_type = ct,
  34
+                user = request.user
  35
+            )
  36
+            overall = rating.overall_rating
  37
+            rating.delete()
  38
+            overall.update()
  39
+            data["overall_rating"] = overall.rating
  40
+        except Rating.DoesNotExist:
  41
+            pass
  42
+    elif 1 <= vote <= NUM_OF_RATINGS: # set the rating
  43
+        rating, created = Rating.objects.get_or_create(
  44
+            object_id = obj.pk,
  45
+            content_type = ct,
  46
+            user = request.user,
  47
+            defaults = {
  48
+                "rating": vote
  49
+            }
  50
+        )
  51
+        overall, created = OverallRating.objects.get_or_create(
  52
+            object_id = obj.pk,
  53
+            content_type = ct
  54
+        )
  55
+        rating.overall_rating = overall
  56
+        rating.save()
  57
+        overall.update()
  58
+        data["overall_rating"] = overall.rating
  59
+    else: # whoops
  60
+        return HttpResponseForbidden(
  61
+            "Invalid rating. It must be a value between 0 and %s" % NUM_OF_RATINGS
  62
+        )
  63
+    
  64
+    return HttpResponse(json.dumps(data))
130  docs/Makefile
... ...
@@ -0,0 +1,130 @@
  1
+# Makefile for Sphinx documentation
  2
+#
  3
+
  4
+# You can set these variables from the command line.
  5
+SPHINXOPTS    =
  6
+SPHINXBUILD   = sphinx-build
  7
+PAPER         =
  8
+BUILDDIR      = _build
  9
+
  10
+# Internal variables.
  11
+PAPEROPT_a4     = -D latex_paper_size=a4
  12
+PAPEROPT_letter = -D latex_paper_size=letter
  13
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
  14
+
  15
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
  16
+
  17
+help:
  18
+	@echo "Please use \`make <target>' where <target> is one of"
  19
+	@echo "  html       to make standalone HTML files"
  20
+	@echo "  dirhtml    to make HTML files named index.html in directories"
  21
+	@echo "  singlehtml to make a single large HTML file"
  22
+	@echo "  pickle     to make pickle files"
  23
+	@echo "  json       to make JSON files"
  24
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
  25
+	@echo "  qthelp     to make HTML files and a qthelp project"
  26
+	@echo "  devhelp    to make HTML files and a Devhelp project"
  27
+	@echo "  epub       to make an epub"
  28
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
  29
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
  30
+	@echo "  text       to make text files"
  31
+	@echo "  man        to make manual pages"
  32
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
  33
+	@echo "  linkcheck  to check all external links for integrity"
  34
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
  35
+
  36
+clean:
  37
+	-rm -rf $(BUILDDIR)/*
  38
+
  39
+html:
  40
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
  41
+	@echo
  42
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
  43
+
  44
+dirhtml:
  45
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
  46
+	@echo
  47
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
  48
+
  49
+singlehtml:
  50
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
  51
+	@echo
  52
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
  53
+
  54
+pickle:
  55
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
  56
+	@echo
  57
+	@echo "Build finished; now you can process the pickle files."
  58
+
  59
+json:
  60
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
  61
+	@echo
  62
+	@echo "Build finished; now you can process the JSON files."
  63
+
  64
+htmlhelp:
  65
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
  66
+	@echo
  67
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
  68
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
  69
+
  70
+qthelp:
  71
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
  72
+	@echo
  73
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
  74
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
  75
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/agon_ratings.qhcp"
  76
+	@echo "To view the help file:"
  77
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/agon_ratings.qhc"
  78
+
  79
+devhelp:
  80
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
  81
+	@echo
  82
+	@echo "Build finished."
  83
+	@echo "To view the help file:"
  84
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/agon_ratings"
  85
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/agon_ratings"
  86
+	@echo "# devhelp"
  87
+
  88
+epub:
  89
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
  90
+	@echo
  91
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
  92
+
  93
+latex:
  94
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
  95
+	@echo
  96
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
  97
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
  98
+	      "(use \`make latexpdf' here to do that automatically)."
  99
+
  100
+latexpdf:
  101
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
  102
+	@echo "Running LaTeX files through pdflatex..."
  103
+	make -C $(BUILDDIR)/latex all-pdf
  104
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
  105
+
  106
+text:
  107
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
  108
+	@echo
  109
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
  110
+
  111
+man:
  112
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
  113
+	@echo
  114
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
  115
+
  116
+changes:
  117
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
  118
+	@echo
  119
+	@echo "The overview file is in $(BUILDDIR)/changes."
  120
+
  121
+linkcheck:
  122
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
  123
+	@echo
  124
+	@echo "Link check complete; look for any errors in the above output " \
  125
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
  126
+
  127
+doctest:
  128
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
  129
+	@echo "Testing of doctests in the sources finished, look at the " \
  130
+	      "results in $(BUILDDIR)/doctest/output.txt."
9  docs/changelog.rst
Source Rendered
... ...
@@ -0,0 +1,9 @@
  1
+.. _changelog:
  2
+
  3
+ChangeLog
  4
+=========
  5
+
  6
+0.1
  7
+---
  8
+
  9
+- initial release
21  docs/conf.py
... ...
@@ -0,0 +1,21 @@
  1
+extensions = []
  2
+templates_path = []
  3
+source_suffix = '.rst'
  4
+master_doc = 'index'
  5
+project = u'agon_ratings'
  6
+copyright = u'2011, Eldarion'
  7
+version = '0.1'
  8
+release = '0.1'
  9
+exclude_patterns = ['_build']
  10
+pygments_style = 'sphinx'
  11
+html_theme = 'default'
  12
+html_static_path = []
  13
+htmlhelp_basename = 'agon_ratingsdoc'
  14
+latex_documents = [
  15
+  ('index', 'agon_ratings.tex', u'agon_ratings Documentation',
  16
+   u'Eldarion', 'manual'),
  17
+]
  18
+man_pages = [
  19
+    ('index', 'agon_ratings', u'agon_ratings Documentation',
  20
+     [u'Eldarion'], 1)
  21
+]
24  docs/index.rst
Source Rendered
... ...
@@ -0,0 +1,24 @@
  1
+============
  2
+agon_ratings
  3
+============
  4
+
  5
+Provides a site with user ratings of objects.
  6
+
  7
+
  8
+Development
  9
+-----------
  10
+
  11
+The source repository can be found at https://github.com/eldarion/agon_ratings
  12
+
  13
+
  14
+Contents
  15
+========
  16
+
  17
+.. toctree::
  18
+ :maxdepth: 1
  19
+
  20
+ installation
  21
+ usage
  22
+ settings
  23
+ templates
  24
+ changelog
24  docs/installation.rst
Source Rendered
... ...
@@ -0,0 +1,24 @@
  1
+.. _installation:
  2
+
  3
+Installation
  4
+============
  5
+
  6
+* To install agon_ratings::
  7
+
  8
+    pip install agon_ratings
  9
+
  10
+* Add ``kaleo`` to your ``INSTALLED_APPS`` setting::
  11
+
  12
+    INSTALLED_APPS = (
  13
+        # other apps
  14
+        "agon_ratings",
  15
+    )
  16
+
  17
+* See the list of :ref:`settings` to modify agon_ratings's
  18
+  default behavior and make adjustments for your website.
  19
+
  20
+* Lastly you will want to add `agon_ratings.urls` to your urls definition::
  21
+
  22
+    ...
  23
+    url(r"^ratings/", include("agon_ratings.urls")),
  24
+    ...
13  docs/settings.rst
Source Rendered
... ...
@@ -0,0 +1,13 @@
  1
+.. _settings:
  2
+
  3
+Settings
  4
+========
  5
+
  6
+.. _agon_num_of_ratings:
  7
+
  8
+AGON_NUM_OF_RATINGS
  9
+^^^^^^^^^^^^^^^^^^^
  10
+
  11
+:Default: 5
  12
+
  13
+Defines the number of different rating choices there will be.
14  docs/templates.rst
Source Rendered
... ...
@@ -0,0 +1,14 @@
  1
+.. _templates:
  2
+
  3
+Templates
  4
+=========
  5
+
  6
+`agon_ratings` comes with one template that is a minimal snippet that gets rendered
  7
+from the template tags for displaying the rating form.
  8
+
  9
+
  10
+_rate_form.html
  11
+---------------
  12
+
  13
+This is a snippet that renders the form that is submitted via AJAX to clear, update,
  14
+or set a rating.
61  docs/usage.rst
Source Rendered
... ...
@@ -0,0 +1,61 @@
  1
+.. _usage:
  2
+
  3
+Usage
  4
+=====
  5
+
  6
+Integrating `agon_ratings` into your project is just a matter of using a couple of
  7
+template tags and wiring up a bit of javascript. The rating form is intended
  8
+to function via AJAX and as such returns JSON.
  9
+
  10
+Firstly, you will want to add the following blocks in your templates where
  11
+you want to expose the rating form::
  12
+
  13
+    {% load agon_ratings_tags %}
  14
+    
  15
+    <div class="rating">
  16
+        {% user_rate_form some_object %}
  17
+        
  18
+        <div class="user_rating">
  19
+            {% user_rating for request.user and some_object as the_user_rating %}
  20
+            <span class="rating-{{ the_user_rating }}">{{ the_user_rating }}</span>
  21
+        </div>
  22
+        
  23
+        <div class="overall_rating">
  24
+            {% overall_rating for some_object as the_overall_rating %}
  25
+            <span class="rating-{{ the_overall_rating }}">{{ the_overall_rating }}</span>
  26
+        </div>
  27
+    </div>
  28
+
  29
+
  30
+And then a bit of jQuery (this assumes use of the jquery.form plugin)::
  31
+
  32
+    $('.rating form').ajaxForm(function(data) {
  33
+        var user_r = parseInt(data["user_rating"]);
  34
+        var over_r = parseFloat(data["overall_rating"]);
  35
+        var over_class = parseInt(over_r * 10);
  36
+        var user_class = user_r * 10;
  37
+        $(".rating .user_rating span").attr("class", "rating-" + user_class).text(user_r);
  38
+        $(".rating .overall_rating span").attr("class", "rating-" + over_class).text(over_r);
  39
+    });
  40
+
  41
+
  42
+Wiring up the interface is up to you the site developer. One approach that seems to
  43
+work nicely is integrating with _raty:Raty: like so::
  44
+
  45
+    <div id="user_rating"></div>
  46
+    <div id="overall_rating"></div>
  47
+    
  48
+    <script type="text/javascript" src="jquery.raty.min.js" />
  49
+    <script type="text/javascript">
  50
+        $("#user_rating").raty({
  51
+            start = {{ the_user_rating }},
  52
+            click = function(score, evt) {
  53
+                $(".rating form input[name]").val(score);
  54
+                $(".rating form").submit();
  55
+            },
  56
+            cancel = true
  57
+        })
  58
+    </script>
  59
+
  60
+
  61
+_raty:Raty: http://www.wbotelhos.com/raty/
137  setup.py
... ...
@@ -0,0 +1,137 @@
  1
+import codecs
  2
+import os
  3
+import sys
  4
+
  5
+from distutils.util import convert_path
  6
+from fnmatch import fnmatchcase
  7
+from setuptools import setup, find_packages
  8
+
  9
+
  10
+def read(fname):
  11
+    return codecs.open(os.path.join(os.path.dirname(__file__), fname)).read()
  12
+
  13
+
  14
+# Provided as an attribute, so you can append to these instead
  15
+# of replicating them:
  16
+standard_exclude = ["*.py", "*.pyc", "*$py.class", "*~", ".*", "*.bak"]
  17
+standard_exclude_directories = [
  18
+    ".*", "CVS", "_darcs", "./build", "./dist", "EGG-INFO", "*.egg-info"
  19
+]
  20
+
  21
+
  22
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
  23
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
  24
+# Note: you may want to copy this into your setup.py file verbatim, as
  25
+# you can't import this from another package, when you don't know if
  26
+# that package is installed yet.
  27
+def find_package_data(
  28
+    where=".",
  29
+    package="",
  30
+    exclude=standard_exclude,
  31
+    exclude_directories=standard_exclude_directories,
  32
+    only_in_packages=True,
  33
+    show_ignored=False):
  34
+    """
  35
+    Return a dictionary suitable for use in ``package_data``
  36
+    in a distutils ``setup.py`` file.
  37
+
  38
+    The dictionary looks like::
  39
+
  40
+        {"package": [files]}
  41
+
  42
+    Where ``files`` is a list of all the files in that package that
  43
+    don"t match anything in ``exclude``.
  44
+
  45
+    If ``only_in_packages`` is true, then top-level directories that
  46
+    are not packages won"t be included (but directories under packages
  47
+    will).
  48
+
  49
+    Directories matching any pattern in ``exclude_directories`` will
  50
+    be ignored; by default directories with leading ``.``, ``CVS``,
  51
+    and ``_darcs`` will be ignored.
  52
+
  53
+    If ``show_ignored`` is true, then all the files that aren"t
  54
+    included in package data are shown on stderr (for debugging
  55
+    purposes).
  56
+
  57
+    Note patterns use wildcards, or can be exact paths (including
  58
+    leading ``./``), and all searching is case-insensitive.
  59
+    """
  60
+    out = {}
  61
+    stack = [(convert_path(where), "", package, only_in_packages)]
  62
+    while stack:
  63
+        where, prefix, package, only_in_packages = stack.pop(0)
  64
+        for name in os.listdir(where):
  65
+            fn = os.path.join(where, name)
  66
+            if os.path.isdir(fn):
  67
+                bad_name = False
  68
+                for pattern in exclude_directories:
  69
+                    if (fnmatchcase(name, pattern)
  70
+                        or fn.lower() == pattern.lower()):
  71
+                        bad_name = True
  72
+                        if show_ignored:
  73
+                            print >> sys.stderr, (
  74
+                                "Directory %s ignored by pattern %s"
  75
+                                % (fn, pattern))
  76
+                        break
  77
+                if bad_name:
  78
+                    continue
  79
+                if (os.path.isfile(os.path.join(fn, "__init__.py"))
  80
+                    and not prefix):
  81
+                    if not package:
  82
+                        new_package = name
  83
+                    else:
  84
+                        new_package = package + "." + name
  85
+                    stack.append((fn, "", new_package, False))
  86
+                else:
  87
+                    stack.append((fn, prefix + name + "/", package, only_in_packages))
  88
+            elif package or not only_in_packages:
  89
+                # is a file
  90
+                bad_name = False
  91
+                for pattern in exclude:
  92
+                    if (fnmatchcase(name, pattern)
  93
+                        or fn.lower() == pattern.lower()):
  94
+                        bad_name = True
  95
+                        if show_ignored:
  96
+                            print >> sys.stderr, (
  97
+                                "File %s ignored by pattern %s"
  98
+                                % (fn, pattern))
  99
+                        break
  100
+                if bad_name:
  101
+                    continue
  102
+                out.setdefault(package, []).append(prefix+name)
  103
+    return out
  104
+
  105
+
  106
+PACKAGE = "agon_ratings"
  107
+NAME = "agon-ratings"
  108
+DESCRIPTION = "a user ratings app"
  109
+AUTHOR = "Eldarion"
  110
+AUTHOR_EMAIL = "developers@eldarion.com"
  111
+URL = "http://github.com/eldarion/agon-ratings"
  112
+VERSION = __import__(PACKAGE).__version__
  113
+
  114
+
  115
+setup(
  116
+    name=NAME,
  117
+    version=VERSION,
  118
+    description=DESCRIPTION,
  119
+    long_description=read("README.rst"),
  120
+    author=AUTHOR,
  121
+    author_email=AUTHOR_EMAIL,
  122
+    license="BSD",
  123
+    url=URL,
  124
+    packages=find_packages(exclude=["tests.*", "tests"]),
  125
+    package_data=find_package_data(PACKAGE, only_in_packages=False),
  126
+    classifiers=[
  127
+        "Development Status :: 3 - Alpha",
  128
+        "Environment :: Web Environment",
  129
+        "Intended Audience :: Developers",
  130
+        "License :: OSI Approved :: BSD License",
  131
+        "Operating System :: OS Independent",
  132
+        "Programming Language :: Python",
  133
+        "Framework :: Django",
  134
+    ],
  135
+    zip_safe=False
  136
+)
  137
+

0 notes on commit 75fd9ed

Please sign in to comment.
Something went wrong with that request. Please try again.