Permalink
Browse files

0.1.dev8 - Initial Commit

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

0 comments on commit 75fd9ed

Please sign in to comment.