django-voting-0.2/0000755000076500001200000000000012004236013014510 5ustar jezdezadmin00000000000000django-voting-0.2/CHANGELOG.txt0000644000076500001200000000026512004235317016551 0ustar jezdezadmin00000000000000Django Voting Changelog ======================= 0.2 --- * Django 1.4 compatibility (timezone support) * Added a ``time_stamp`` field to ``Vote`` model * Added South migrations. * django-voting-0.2/docs/0000755000076500001200000000000012004236013015440 5ustar jezdezadmin00000000000000django-voting-0.2/docs/overview.txt0000644000076500001200000003003412004221623020050 0ustar jezdezadmin00000000000000============== Django Voting ============== A generic voting application for Django projects, which allows registering of votes for any ``Model`` instance. Installation ============ Google Code recommends doing the Subversion checkout like so:: svn checkout http://django-voting.googlecode.com/svn/trunk/ django-voting But the hyphen in the application name can cause issues installing into a DB, so it's really better to do this:: svn checkout http://django-voting.googlecode.com/svn/trunk/ voting If you've already downloaded, rename the directory before installing. To install django-voting, do the following: 1. Put the ``voting`` folder somewhere on your Python path. 2. Put ``'voting'`` in your ``INSTALLED_APPS`` setting. 3. Run the command ``manage.py syncdb``. The ``syncdb`` command creates the necessary database tables and creates permission objects for all installed apps that need them. That's it! Votes ===== Votes are represented by the ``Vote`` model, which lives in the ``voting.models`` module. API reference ------------- Fields ~~~~~~ ``Vote`` objects have the following fields: * ``user`` -- The user who made the vote. Users are represented by the ``django.contrib.auth.models.User`` model. * ``content_type`` -- The ContentType of the object voted on. * ``object_id`` -- The id of the object voted on. * ``object`` -- The object voted on. * ``vote`` -- The vote which was made: ``+1`` or ``-1``. Methods ~~~~~~~ ``Vote`` objects have the following custom methods: * ``is_upvote`` -- Returns ``True`` if ``vote`` is ``+1``. * ``is_downvote`` -- Returns ``True`` if ``vote`` is ``-1``. Manager functions ~~~~~~~~~~~~~~~~~ The ``Vote`` model has a custom manager that has the following helper functions: * ``record_vote(obj, user, vote)`` -- Record a user's vote on a given object. Only allows a given user to vote once on any given object, but the vote may be changed. ``vote`` must be one of ``1`` (up vote), ``-1`` (down vote) or ``0`` (remove vote). * ``get_score(obj)`` -- Gets the total score for ``obj`` and the total number of votes it's received. Returns a dictionary with ``score`` and ``num_votes`` keys. * ``get_scores_in_bulk(objects)`` -- Gets score and vote count details for all the given objects. Score details consist of a dictionary which has ``score`` and ``num_vote`` keys. Returns a dictionary mapping object ids to score details. * ``get_top(Model, limit=10, reversed=False)`` -- Gets the top ``limit`` scored objects for a given model. If ``reversed`` is ``True``, the bottom ``limit`` scored objects are retrieved instead. Yields ``(object, score)`` tuples. * ``get_bottom(Model, limit=10)`` -- A convenience method which calls ``get_top`` with ``reversed=True``. Gets the bottom (i.e. most negative) ``limit`` scored objects for a given model. Yields ``(object, score)`` tuples. * ``get_for_user(obj, user)`` -- Gets the vote made on the given object by the given user, or ``None`` if no matching vote exists. * ``get_for_user_in_bulk(objects, user)`` -- Gets the votes made on all the given objects by the given user. Returns a dictionary mapping object ids to votes. Basic usage ----------- Recording votes and retrieving scores ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Votes are recorded using the ``record_vote`` helper function:: >>> from django.contrib.auth.models import User >>> from shop.apps.products.models import Widget >>> from voting.models import Vote >>> user = User.objects.get(pk=1) >>> widget = Widget.objects.get(pk=1) >>> Vote.objects.record_vote(widget, user, +1) The score for an object can be retrieved using the ``get_score`` helper function:: >>> Vote.objects.get_score(widget) {'score': 1, 'num_votes': 1} If the same user makes another vote on the same object, their vote is either modified or deleted, as appropriate:: >>> Vote.objects.record_vote(widget, user, -1) >>> Vote.objects.get_score(widget) {'score': -1, 'num_votes': 1} >>> Vote.objects.record_vote(widget, user, 0) >>> Vote.objects.get_score(widget) {'score': 0, 'num_votes': 0} Generic Views ============= The ``voting.views`` module contains views to handle a couple of common cases: displaying a page to confirm a vote when it is requested via ``GET`` and making the vote itself via ``POST``, or voting via XMLHttpRequest ``POST``. The following sample URLconf demonstrates using a generic view for voting on a model, allowing for regular voting and XMLHttpRequest voting at the same URL:: from django.conf.urls.defaults import * from voting.views import vote_on_object from shop.apps.products.models import Widget widget_dict = { 'model': Widget, 'template_object_name': 'widget', 'allow_xmlhttprequest': True, } urlpatterns = patterns('', (r'^widgets/(?P\d+)/(?Pup|down|clear)vote/?$', vote_on_object, widget_dict), ) ``voting.views.vote_on_object`` -------------------------------- **Description:** A view that displays a confirmation page and votes on an object. The given object will only be voted on if the request method is ``POST``. If this view is fetched via ``GET``, it will display a confirmation page that should contain a form that ``POST``\s to the same URL. **Required arguments:** * ``model``: The Django model class of the object that will be voted on. * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required. If you provide ``object_id``, it should be the value of the primary-key field for the object being voted on. Otherwise, ``slug`` should be the slug of the given object, and ``slug_field`` should be the name of the slug field in the ``QuerySet``'s model. * ``direction``: The kind of vote to be made, must be one of ``'up'``, ``'down'`` or ``'clear'``. * Either a ``post_vote_redirect`` argument defining a URL must be supplied, or a ``next`` parameter must supply a URL in the request when the vote is ``POST``\ed, or the object being voted on must define a ``get_absolute_url`` method or property. The view checks for these in the order given above. **Optional arguments:** * ``allow_xmlhttprequest``: A boolean that designates whether this view should also allow votes to be made via XMLHttpRequest. If this is ``True``, the request headers will be check for an ``HTTP_X_REQUESTED_WITH`` header which has a value of ``XMLHttpRequest``. If this header is found, processing of the current request is delegated to ``voting.views.xmlhttprequest_vote_on_object``. * ``template_name``: The full name of a template to use in rendering the page. This lets you override the default template name (see below). * ``template_loader``: The template loader to use when loading the template. By default, it's ``django.template.loader``. * ``extra_context``: A dictionary of values to add to the template context. By default, this is an empty dictionary. If a value in the dictionary is callable, the generic view will call it just before rendering the template. * ``context_processors``: A list of template-context processors to apply to the view's template. * ``template_object_name``: Designates the name of the template variable to use in the template context. By default, this is ``'object'``. **Template name:** If ``template_name`` isn't specified, this view will use the template ``/_confirm_vote.html`` by default. **Template context:** In addition to ``extra_context``, the template's context will be: * ``object``: The original object that's about to be voted on. This variable's name depends on the ``template_object_name`` parameter, which is ``'object'`` by default. If ``template_object_name`` is ``'foo'``, this variable's name will be ``foo``. * ``direction``: The argument which was given for the vote's ``direction`` (see above). ``voting.views.xmlhttprequest_vote_on_object`` ----------------------------------------------- **Description:** A view for use in voting on objects via XMLHttpRequest. The given object will only be voted on if the request method is ``POST``. This view will respond with JSON text instead of rendering a template or redirecting. **Required arguments:** * ``model``: The Django model class of the object that will be voted on. * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required. If you provide ``object_id``, it should be the value of the primary-key field for the object being voted on. Otherwise, ``slug`` should be the slug of the given object, and ``slug_field`` should be the name of the slug field in the ``QuerySet``'s model. * ``direction``: The kind of vote to be made, must be one of ``'up'``, ``'down'`` or ``'clear'``. **JSON text context:** The context provided by the JSON text returned will be: * ``success``: ``true`` if the vote was successfully processed, ``false`` otherwise. * ``score``: an object containing a ``score`` property, which holds the object's updated score, and a ``num_votes`` property, which holds the total number of votes cast for the object. * ``error_message``: if the vote was not successfully processed, this property will contain an error message. Template tags ============= The ``voting.templatetags.voting_tags`` module defines a number of template tags which may be used to retrieve and display voting details. Tag reference ------------- score_for_object ~~~~~~~~~~~~~~~~ Retrieves the total score for an object and the number of votes it's received, storing them in a context variable which has ``score`` and ``num_votes`` properties. Example usage:: {% score_for_object widget as score %} {{ score.score }} point{{ score.score|pluralize }} after {{ score.num_votes }} vote{{ score.num_votes|pluralize }} scores_for_objects ~~~~~~~~~~~~~~~~~~ Retrieves the total scores and number of votes cast for a list of objects as a dictionary keyed with the objects' ids and stores it in a context variable. Example usage:: {% scores_for_objects widget_list as scores %} vote_by_user ~~~~~~~~~~~~ Retrieves the ``Vote`` cast by a user on a particular object and stores it in a context variable. If the user has not voted, the context variable will be ``None``. Example usage:: {% vote_by_user user on widget as vote %} votes_by_user ~~~~~~~~~~~~~ Retrieves the votes cast by a user on a list of objects as a dictionary keyed with object ids and stores it in a context variable. Example usage:: {% votes_by_user user on widget_list as vote_dict %} dict_entry_for_item ~~~~~~~~~~~~~~~~~~~ Given an object and a dictionary keyed with object ids - as returned by the ``votes_by_user`` and ``scores_for_objects`` template tags - retrieves the value for the given object and stores it in a context variable, storing ``None`` if no value exists for the given object. Example usage:: {% dict_entry_for_item widget from vote_dict as vote %} confirm_vote_message ~~~~~~~~~~~~~~~~~~~~ Intended for use in vote confirmatio templates, creates an appropriate message asking the user to confirm the given vote for the given object description. Example usage:: {% confirm_vote_message widget.title direction %} Filter reference ---------------- vote_display ~~~~~~~~~~~~ Given a string mapping values for up and down votes, returns one of the strings according to the given ``Vote``: ========= ===================== ============= Vote type Argument Outputs ========= ===================== ============= ``+1`` ``'Bodacious,Bogus'`` ``Bodacious`` ``-1`` ``'Bodacious,Bogus'`` ``Bogus`` ========= ===================== ============= If no string mapping is given, ``'Up'`` and ``'Down'`` will be used. Example usage:: {{ vote|vote_display:"Bodacious,Bogus" }} django-voting-0.2/INSTALL.txt0000644000076500001200000000103012004220647016357 0ustar jezdezadmin00000000000000Thanks for downloading django-voting. To install it, run the following command inside this directory: python setup.py install Or if you'd prefer you can simply place the included ``voting`` directory somewhere on your Python path, or symlink to it from somewhere on your Python path; this is useful if you're working from a Subversion checkout. Note that this application requires Python 2.3 or later, and Django 0.97-pre or later. You can obtain Python from http://www.python.org/ and Django from http://www.djangoproject.com/.django-voting-0.2/LICENSE.txt0000644000076500001200000000515612004221605016343 0ustar jezdezadmin00000000000000django-voting ------------- Copyright (c) 2007, Jonathan Buchanan Copyright (c) 2012, Jannis Leidel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CheeseRater ----------- Copyright (c) 2007, Jacob Kaplan-Moss All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of Django 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.django-voting-0.2/MANIFEST.in0000644000076500001200000000023712004220647016256 0ustar jezdezadmin00000000000000include CHANGELOG.txt include INSTALL.txt include LICENSE.txt include MANIFEST.in include README.txt recursive-include docs * recursive-include voting/tests * django-voting-0.2/PKG-INFO0000644000076500001200000000112612004236013015605 0ustar jezdezadmin00000000000000Metadata-Version: 1.1 Name: django-voting Version: 0.2 Summary: Generic voting application for Django Home-page: http://github.com/jezdez/django-voting/ Author: Jannis Leidel Author-email: jannis@leidel.info License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Topic :: Utilities django-voting-0.2/README.txt0000644000076500001200000000046112004220647016215 0ustar jezdezadmin00000000000000============= Django Voting ============= This is a generic voting application for Django projects For installation instructions, see the file "INSTALL.txt" in this directory; for instructions on how to use this application, and on what it provides, see the file "overview.txt" in the "docs/" directory.django-voting-0.2/setup.py0000644000076500001200000000204212004235655016233 0ustar jezdezadmin00000000000000from distutils.core import setup # Dynamically calculate the version based on tagging.VERSION. version_tuple = __import__('voting').VERSION if version_tuple[2] is not None: version = "%d.%d_%s" % version_tuple else: version = "%d.%d" % version_tuple[:2] setup( name='django-voting', version=version, description='Generic voting application for Django', author='Jonathan Buchanan', author_email='jonathan.buchanan@gmail.com', maintainer='Jannis Leidel', maintainer_email='jannis@leidel.info', url='http://github.com/jezdez/django-voting/', packages=[ 'voting', 'voting.migrations', 'voting.templatetags', 'voting.tests', ], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Topic :: Utilities', ], ) django-voting-0.2/voting/0000755000076500001200000000000012004236013016016 5ustar jezdezadmin00000000000000django-voting-0.2/voting/__init__.py0000644000076500001200000000002712004221647020135 0ustar jezdezadmin00000000000000VERSION = (0, 2, None) django-voting-0.2/voting/admin.py0000644000076500001200000000013312004220647017463 0ustar jezdezadmin00000000000000from django.contrib import admin from voting.models import Vote admin.site.register(Vote) django-voting-0.2/voting/managers.py0000644000076500001200000001576012004235065020204 0ustar jezdezadmin00000000000000from django.conf import settings from django.db import connection, models try: from django.db.models.sql.aggregates import Aggregate except ImportError: supports_aggregates = False else: supports_aggregates = True from django.contrib.contenttypes.models import ContentType ZERO_VOTES_ALLOWED = getattr(settings, 'VOTING_ZERO_VOTES_ALLOWED', False) if supports_aggregates: class CoalesceWrapper(Aggregate): sql_template = 'COALESCE(%(function)s(%(field)s), %(default)s)' def __init__(self, lookup, **extra): self.lookup = lookup self.extra = extra def _default_alias(self): return '%s__%s' % (self.lookup, self.__class__.__name__.lower()) default_alias = property(_default_alias) def add_to_query(self, query, alias, col, source, is_summary): super(CoalesceWrapper, self).__init__(col, source, is_summary, **self.extra) query.aggregate_select[alias] = self class CoalesceSum(CoalesceWrapper): sql_function = 'SUM' class CoalesceCount(CoalesceWrapper): sql_function = 'COUNT' class VoteManager(models.Manager): def get_score(self, obj): """ Get a dictionary containing the total score for ``obj`` and the number of votes it's received. """ ctype = ContentType.objects.get_for_model(obj) result = self.filter(object_id=obj._get_pk_val(), content_type=ctype).extra( select={ 'score': 'COALESCE(SUM(vote), 0)', 'num_votes': 'COALESCE(COUNT(vote), 0)', }).values_list('score', 'num_votes')[0] return { 'score': int(result[0]), 'num_votes': int(result[1]), } def get_scores_in_bulk(self, objects): """ Get a dictionary mapping object ids to total score and number of votes for each object. """ object_ids = [o._get_pk_val() for o in objects] if not object_ids: return {} ctype = ContentType.objects.get_for_model(objects[0]) if supports_aggregates: queryset = self.filter( object_id__in=object_ids, content_type=ctype, ).values( 'object_id', ).annotate( score=CoalesceSum('vote', default='0'), num_votes=CoalesceCount('vote', default='0'), ) else: queryset = self.filter( object_id__in=object_ids, content_type=ctype, ).extra( select={ 'score': 'COALESCE(SUM(vote), 0)', 'num_votes': 'COALESCE(COUNT(vote), 0)', } ).values('object_id', 'score', 'num_votes') queryset.query.group_by.append('object_id') vote_dict = {} for row in queryset: vote_dict[row['object_id']] = { 'score': int(row['score']), 'num_votes': int(row['num_votes']), } return vote_dict def record_vote(self, obj, user, vote): """ Record a user's vote on a given object. Only allows a given user to vote once, though that vote may be changed. A zero vote indicates that any existing vote should be removed. """ if vote not in (+1, 0, -1): raise ValueError('Invalid vote (must be +1/0/-1)') ctype = ContentType.objects.get_for_model(obj) try: v = self.get(user=user, content_type=ctype, object_id=obj._get_pk_val()) if vote == 0 and not ZERO_VOTES_ALLOWED: v.delete() else: v.vote = vote v.save() except models.ObjectDoesNotExist: if not ZERO_VOTES_ALLOWED and vote == 0: return self.create(user=user, content_type=ctype, object_id=obj._get_pk_val(), vote=vote) def get_top(self, Model, limit=10, reversed=False): """ Get the top N scored objects for a given model. Yields (object, score) tuples. """ ctype = ContentType.objects.get_for_model(Model) query = """ SELECT object_id, SUM(vote) as %s FROM %s WHERE content_type_id = %%s GROUP BY object_id""" % ( connection.ops.quote_name('score'), connection.ops.quote_name(self.model._meta.db_table), ) # MySQL has issues with re-using the aggregate function in the # HAVING clause, so we alias the score and use this alias for # its benefit. if settings.DATABASE_ENGINE == 'mysql': having_score = connection.ops.quote_name('score') else: having_score = 'SUM(vote)' if reversed: having_sql = ' HAVING %(having_score)s < 0 ORDER BY %(having_score)s ASC LIMIT %%s' else: having_sql = ' HAVING %(having_score)s > 0 ORDER BY %(having_score)s DESC LIMIT %%s' query += having_sql % { 'having_score': having_score, } cursor = connection.cursor() cursor.execute(query, [ctype.id, limit]) results = cursor.fetchall() # Use in_bulk() to avoid O(limit) db hits. objects = Model.objects.in_bulk([id for id, score in results]) # Yield each object, score pair. Because of the lazy nature of generic # relations, missing objects are silently ignored. for id, score in results: if id in objects: yield objects[id], int(score) def get_bottom(self, Model, limit=10): """ Get the bottom (i.e. most negative) N scored objects for a given model. Yields (object, score) tuples. """ return self.get_top(Model, limit, True) def get_for_user(self, obj, user): """ Get the vote made on the given object by the given user, or ``None`` if no matching vote exists. """ if not user.is_authenticated(): return None ctype = ContentType.objects.get_for_model(obj) try: vote = self.get(content_type=ctype, object_id=obj._get_pk_val(), user=user) except models.ObjectDoesNotExist: vote = None return vote def get_for_user_in_bulk(self, objects, user): """ Get a dictionary mapping object ids to votes made by the given user on the corresponding objects. """ vote_dict = {} if len(objects) > 0: ctype = ContentType.objects.get_for_model(objects[0]) votes = list(self.filter(content_type__pk=ctype.id, object_id__in=[obj._get_pk_val() \ for obj in objects], user__pk=user.id)) vote_dict = dict([(vote.object_id, vote) for vote in votes]) return vote_dict django-voting-0.2/voting/migrations/0000755000076500001200000000000012004236013020172 5ustar jezdezadmin00000000000000django-voting-0.2/voting/migrations/0001_initial.py0000644000076500001200000001161612004223250022641 0ustar jezdezadmin00000000000000# -*- coding: utf-8 -*- import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): # Adding model 'Vote' db.create_table('votes', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), ('vote', self.gf('django.db.models.fields.SmallIntegerField')()), )) db.send_create_signal('voting', ['Vote']) # Adding unique constraint on 'Vote', fields ['user', 'content_type', 'object_id'] db.create_unique('votes', ['user_id', 'content_type_id', 'object_id']) def backwards(self, orm): # Removing unique constraint on 'Vote', fields ['user', 'content_type', 'object_id'] db.delete_unique('votes', ['user_id', 'content_type_id', 'object_id']) # Deleting model 'Vote' db.delete_table('votes') models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, 'auth.permission': { 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, 'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, 'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, 'voting.vote': { 'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'Vote', 'db_table': "'votes'"}, 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), 'vote': ('django.db.models.fields.SmallIntegerField', [], {}) } } complete_apps = ['voting']django-voting-0.2/voting/migrations/0002_auto__add_field_vote_time_stamp.py0000644000076500001200000001052512004223271027553 0ustar jezdezadmin00000000000000# -*- coding: utf-8 -*- import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): # Adding field 'Vote.time_stamp' db.add_column('votes', 'time_stamp', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now), keep_default=False) def backwards(self, orm): # Deleting field 'Vote.time_stamp' db.delete_column('votes', 'time_stamp') models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, 'auth.permission': { 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, 'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, 'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, 'voting.vote': { 'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'Vote', 'db_table': "'votes'"}, 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 'time_stamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), 'vote': ('django.db.models.fields.SmallIntegerField', [], {}) } } complete_apps = ['voting']django-voting-0.2/voting/migrations/__init__.py0000644000076500001200000000000012004223250022270 0ustar jezdezadmin00000000000000django-voting-0.2/voting/models.py0000644000076500001200000000223312004223257017661 0ustar jezdezadmin00000000000000from datetime import datetime from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import User from django.db import models try: from django.utils.timezone import now except ImportError: now = datetime.now from voting.managers import VoteManager SCORES = ( (+1, u'+1'), (-1, u'-1'), ) class Vote(models.Model): """ A vote on an object by a User. """ user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() object = generic.GenericForeignKey('content_type', 'object_id') vote = models.SmallIntegerField(choices=SCORES) time_stamp = models.DateTimeField(editable=False, default=now) objects = VoteManager() class Meta: db_table = 'votes' # One vote per user per object unique_together = (('user', 'content_type', 'object_id'),) def __unicode__(self): return u'%s: %s on %s' % (self.user, self.vote, self.object) def is_upvote(self): return self.vote == 1 def is_downvote(self): return self.vote == -1 django-voting-0.2/voting/templatetags/0000755000076500001200000000000012004236013020510 5ustar jezdezadmin00000000000000django-voting-0.2/voting/templatetags/__init__.py0000644000076500001200000000000012004220647022615 0ustar jezdezadmin00000000000000django-voting-0.2/voting/templatetags/voting_tags.py0000644000076500001200000001760012004221457023420 0ustar jezdezadmin00000000000000from django import template from django.utils.html import escape from voting.models import Vote register = template.Library() # Tags class ScoreForObjectNode(template.Node): def __init__(self, object, context_var): self.object = object self.context_var = context_var def render(self, context): try: object = template.resolve_variable(self.object, context) except template.VariableDoesNotExist: return '' context[self.context_var] = Vote.objects.get_score(object) return '' class ScoresForObjectsNode(template.Node): def __init__(self, objects, context_var): self.objects = objects self.context_var = context_var def render(self, context): try: objects = template.resolve_variable(self.objects, context) except template.VariableDoesNotExist: return '' context[self.context_var] = Vote.objects.get_scores_in_bulk(objects) return '' class VoteByUserNode(template.Node): def __init__(self, user, object, context_var): self.user = user self.object = object self.context_var = context_var def render(self, context): try: user = template.resolve_variable(self.user, context) object = template.resolve_variable(self.object, context) except template.VariableDoesNotExist: return '' context[self.context_var] = Vote.objects.get_for_user(object, user) return '' class VotesByUserNode(template.Node): def __init__(self, user, objects, context_var): self.user = user self.objects = objects self.context_var = context_var def render(self, context): try: user = template.resolve_variable(self.user, context) objects = template.resolve_variable(self.objects, context) except template.VariableDoesNotExist: return '' context[self.context_var] = Vote.objects.get_for_user_in_bulk(objects, user) return '' class DictEntryForItemNode(template.Node): def __init__(self, item, dictionary, context_var): self.item = item self.dictionary = dictionary self.context_var = context_var def render(self, context): try: dictionary = template.resolve_variable(self.dictionary, context) item = template.resolve_variable(self.item, context) except template.VariableDoesNotExist: return '' context[self.context_var] = dictionary.get(item.id, None) return '' def do_score_for_object(parser, token): """ Retrieves the total score for an object and the number of votes it's received and stores them in a context variable which has ``score`` and ``num_votes`` properties. Example usage:: {% score_for_object widget as score %} {{ score.score }}point{{ score.score|pluralize }} after {{ score.num_votes }} vote{{ score.num_votes|pluralize }} """ bits = token.contents.split() if len(bits) != 4: raise template.TemplateSyntaxError("'%s' tag takes exactly three arguments" % bits[0]) if bits[2] != 'as': raise template.TemplateSyntaxError("second argument to '%s' tag must be 'as'" % bits[0]) return ScoreForObjectNode(bits[1], bits[3]) def do_scores_for_objects(parser, token): """ Retrieves the total scores for a list of objects and the number of votes they have received and stores them in a context variable. Example usage:: {% scores_for_objects widget_list as score_dict %} """ bits = token.contents.split() if len(bits) != 4: raise template.TemplateSyntaxError("'%s' tag takes exactly three arguments" % bits[0]) if bits[2] != 'as': raise template.TemplateSyntaxError("second argument to '%s' tag must be 'as'" % bits[0]) return ScoresForObjectsNode(bits[1], bits[3]) def do_vote_by_user(parser, token): """ Retrieves the ``Vote`` cast by a user on a particular object and stores it in a context variable. If the user has not voted, the context variable will be ``None``. Example usage:: {% vote_by_user user on widget as vote %} """ bits = token.contents.split() if len(bits) != 6: raise template.TemplateSyntaxError("'%s' tag takes exactly five arguments" % bits[0]) if bits[2] != 'on': raise template.TemplateSyntaxError("second argument to '%s' tag must be 'on'" % bits[0]) if bits[4] != 'as': raise template.TemplateSyntaxError("fourth argument to '%s' tag must be 'as'" % bits[0]) return VoteByUserNode(bits[1], bits[3], bits[5]) def do_votes_by_user(parser, token): """ Retrieves the votes cast by a user on a list of objects as a dictionary keyed with object ids and stores it in a context variable. Example usage:: {% votes_by_user user on widget_list as vote_dict %} """ bits = token.contents.split() if len(bits) != 6: raise template.TemplateSyntaxError("'%s' tag takes exactly four arguments" % bits[0]) if bits[2] != 'on': raise template.TemplateSyntaxError("second argument to '%s' tag must be 'on'" % bits[0]) if bits[4] != 'as': raise template.TemplateSyntaxError("fourth argument to '%s' tag must be 'as'" % bits[0]) return VotesByUserNode(bits[1], bits[3], bits[5]) def do_dict_entry_for_item(parser, token): """ Given an object and a dictionary keyed with object ids - as returned by the ``votes_by_user`` and ``scores_for_objects`` template tags - retrieves the value for the given object and stores it in a context variable, storing ``None`` if no value exists for the given object. Example usage:: {% dict_entry_for_item widget from vote_dict as vote %} """ bits = token.contents.split() if len(bits) != 6: raise template.TemplateSyntaxError("'%s' tag takes exactly five arguments" % bits[0]) if bits[2] != 'from': raise template.TemplateSyntaxError("second argument to '%s' tag must be 'from'" % bits[0]) if bits[4] != 'as': raise template.TemplateSyntaxError("fourth argument to '%s' tag must be 'as'" % bits[0]) return DictEntryForItemNode(bits[1], bits[3], bits[5]) register.tag('score_for_object', do_score_for_object) register.tag('scores_for_objects', do_scores_for_objects) register.tag('vote_by_user', do_vote_by_user) register.tag('votes_by_user', do_votes_by_user) register.tag('dict_entry_for_item', do_dict_entry_for_item) # Simple Tags def confirm_vote_message(object_description, vote_direction): """ Creates an appropriate message asking the user to confirm the given vote for the given object description. Example usage:: {% confirm_vote_message widget.title direction %} """ if vote_direction == 'clear': message = 'Confirm clearing your vote for %s.' else: message = 'Confirm %s vote for %%s.' % vote_direction return message % (escape(object_description),) register.simple_tag(confirm_vote_message) # Filters def vote_display(vote, arg=None): """ Given a string mapping values for up and down votes, returns one of the strings according to the given ``Vote``: ========= ===================== ============= Vote type Argument Outputs ========= ===================== ============= ``+1`` ``"Bodacious,Bogus"`` ``Bodacious`` ``-1`` ``"Bodacious,Bogus"`` ``Bogus`` ========= ===================== ============= If no string mapping is given, "Up" and "Down" will be used. Example usage:: {{ vote|vote_display:"Bodacious,Bogus" }} """ if arg is None: arg = 'Up,Down' bits = arg.split(',') if len(bits) != 2: return vote.vote # Invalid arg up, down = bits if vote.vote == 1: return up return down register.filter(vote_display) django-voting-0.2/voting/tests/0000755000076500001200000000000012004236013017160 5ustar jezdezadmin00000000000000django-voting-0.2/voting/tests/__init__.py0000644000076500001200000000000012004220647021265 0ustar jezdezadmin00000000000000django-voting-0.2/voting/tests/models.py0000644000076500001200000000030112004220647021015 0ustar jezdezadmin00000000000000from django.db import models class Item(models.Model): name = models.CharField(max_length=50) def __str__(self): return self.name class Meta: ordering = ['name'] django-voting-0.2/voting/tests/runtests.py0000644000076500001200000000030512004220647021425 0ustar jezdezadmin00000000000000import os, sys os.environ['DJANGO_SETTINGS_MODULE'] = 'voting.tests.settings' from django.test.simple import run_tests failures = run_tests(None, verbosity=9) if failures: sys.exit(failures) django-voting-0.2/voting/tests/settings.py0000644000076500001200000000111312004220647021374 0ustar jezdezadmin00000000000000import os DIRNAME = os.path.dirname(__file__) DATABASE_ENGINE = 'sqlite3' DATABASE_NAME = os.path.join(DIRNAME, 'database.db') #DATABASE_ENGINE = 'mysql' #DATABASE_NAME = 'tagging_test' #DATABASE_USER = 'root' #DATABASE_PASSWORD = '' #DATABASE_HOST = 'localhost' #DATABASE_PORT = '3306' #DATABASE_ENGINE = 'postgresql_psycopg2' #DATABASE_NAME = 'tagging_test' #DATABASE_USER = 'postgres' #DATABASE_PASSWORD = '' #DATABASE_HOST = 'localhost' #DATABASE_PORT = '5432' INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'voting', 'voting.tests', ) django-voting-0.2/voting/tests/tests.py0000644000076500001200000000566312004220647020714 0ustar jezdezadmin00000000000000r""" >>> from django.contrib.auth.models import User >>> from voting.models import Vote >>> from voting.tests.models import Item ########## # Voting # ########## # Basic voting ############################################################### >>> i1 = Item.objects.create(name='test1') >>> users = [] >>> for username in ['u1', 'u2', 'u3', 'u4']: ... users.append(User.objects.create_user(username, '%s@test.com' % username, 'test')) >>> Vote.objects.get_score(i1) {'score': 0, 'num_votes': 0} >>> Vote.objects.record_vote(i1, users[0], +1) >>> Vote.objects.get_score(i1) {'score': 1, 'num_votes': 1} >>> Vote.objects.record_vote(i1, users[0], -1) >>> Vote.objects.get_score(i1) {'score': -1, 'num_votes': 1} >>> Vote.objects.record_vote(i1, users[0], 0) >>> Vote.objects.get_score(i1) {'score': 0, 'num_votes': 0} >>> for user in users: ... Vote.objects.record_vote(i1, user, +1) >>> Vote.objects.get_score(i1) {'score': 4, 'num_votes': 4} >>> for user in users[:2]: ... Vote.objects.record_vote(i1, user, 0) >>> Vote.objects.get_score(i1) {'score': 2, 'num_votes': 2} >>> for user in users[:2]: ... Vote.objects.record_vote(i1, user, -1) >>> Vote.objects.get_score(i1) {'score': 0, 'num_votes': 4} >>> Vote.objects.record_vote(i1, user, -2) Traceback (most recent call last): ... ValueError: Invalid vote (must be +1/0/-1) # Retrieval of votes ######################################################### >>> i2 = Item.objects.create(name='test2') >>> i3 = Item.objects.create(name='test3') >>> i4 = Item.objects.create(name='test4') >>> Vote.objects.record_vote(i2, users[0], +1) >>> Vote.objects.record_vote(i3, users[0], -1) >>> Vote.objects.record_vote(i4, users[0], 0) >>> vote = Vote.objects.get_for_user(i2, users[0]) >>> (vote.vote, vote.is_upvote(), vote.is_downvote()) (1, True, False) >>> vote = Vote.objects.get_for_user(i3, users[0]) >>> (vote.vote, vote.is_upvote(), vote.is_downvote()) (-1, False, True) >>> Vote.objects.get_for_user(i4, users[0]) is None True # In bulk >>> votes = Vote.objects.get_for_user_in_bulk([i1, i2, i3, i4], users[0]) >>> [(id, vote.vote) for id, vote in votes.items()] [(1, -1), (2, 1), (3, -1)] >>> Vote.objects.get_for_user_in_bulk([], users[0]) {} >>> for user in users[1:]: ... Vote.objects.record_vote(i2, user, +1) ... Vote.objects.record_vote(i3, user, +1) ... Vote.objects.record_vote(i4, user, +1) >>> list(Vote.objects.get_top(Item)) [(, 4), (, 3), (, 2)] >>> for user in users[1:]: ... Vote.objects.record_vote(i2, user, -1) ... Vote.objects.record_vote(i3, user, -1) ... Vote.objects.record_vote(i4, user, -1) >>> list(Vote.objects.get_bottom(Item)) [(, -4), (, -3), (, -2)] >>> Vote.objects.get_scores_in_bulk([i1, i2, i3, i4]) {1: {'score': 0, 'num_votes': 4}, 2: {'score': -2, 'num_votes': 4}, 3: {'score': -4, 'num_votes': 4}, 4: {'score': -3, 'num_votes': 3}} >>> Vote.objects.get_scores_in_bulk([]) {} """ django-voting-0.2/voting/urls.py0000644000076500001200000000052312004221234017354 0ustar jezdezadmin00000000000000from django.conf.urls.defaults import * urlpatterns = patterns('', url(r"^vote/(?P[\w\.-]+)/(?P\w+)/"\ "(?P\d+)/(?Pup|down|clear)/$", "voting.views.vote_on_object_with_lazy_model", { "allow_xmlhttprequest": True, }, name="voting_vote" ), ) django-voting-0.2/voting/views.py0000644000076500001200000001515312004221326017533 0ustar jezdezadmin00000000000000from django.core.exceptions import ObjectDoesNotExist from django.db.models import get_model from django.http import Http404, HttpResponse, HttpResponseBadRequest, \ HttpResponseRedirect from django.contrib.auth.views import redirect_to_login from django.template import loader, RequestContext from django.utils import simplejson from voting.models import Vote VOTE_DIRECTIONS = (('up', 1), ('down', -1), ('clear', 0)) def vote_on_object(request, model, direction, post_vote_redirect=None, object_id=None, slug=None, slug_field=None, template_name=None, template_loader=loader, extra_context=None, context_processors=None, template_object_name='object', allow_xmlhttprequest=False): """ Generic object vote function. The given template will be used to confirm the vote if this view is fetched using GET; vote registration will only be performed if this view is POSTed. If ``allow_xmlhttprequest`` is ``True`` and an XMLHttpRequest is detected by examining the ``HTTP_X_REQUESTED_WITH`` header, the ``xmlhttp_vote_on_object`` view will be used to process the request - this makes it trivial to implement voting via XMLHttpRequest with a fallback for users who don't have JavaScript enabled. Templates:``/_confirm_vote.html`` Context: object The object being voted on. direction The type of vote which will be registered for the object. """ if allow_xmlhttprequest and request.is_ajax(): return xmlhttprequest_vote_on_object(request, model, direction, object_id=object_id, slug=slug, slug_field=slug_field) if extra_context is None: extra_context = {} if not request.user.is_authenticated(): return redirect_to_login(request.path) try: vote = dict(VOTE_DIRECTIONS)[direction] except KeyError: raise AttributeError("'%s' is not a valid vote type." % direction) # Look up the object to be voted on lookup_kwargs = {} if object_id: lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id elif slug and slug_field: lookup_kwargs['%s__exact' % slug_field] = slug else: raise AttributeError('Generic vote view must be called with either ' 'object_id or slug and slug_field.') try: obj = model._default_manager.get(**lookup_kwargs) except ObjectDoesNotExist: raise Http404('No %s found for %s.' % (model._meta.app_label, lookup_kwargs)) if request.method == 'POST': if post_vote_redirect is not None: next = post_vote_redirect elif 'next' in request.REQUEST: next = request.REQUEST['next'] elif hasattr(obj, 'get_absolute_url'): if callable(getattr(obj, 'get_absolute_url')): next = obj.get_absolute_url() else: next = obj.get_absolute_url else: raise AttributeError('Generic vote view must be called with either ' 'post_vote_redirect, a "next" parameter in ' 'the request, or the object being voted on ' 'must define a get_absolute_url method or ' 'property.') Vote.objects.record_vote(obj, request.user, vote) return HttpResponseRedirect(next) else: if not template_name: template_name = '%s/%s_confirm_vote.html' % ( model._meta.app_label, model._meta.object_name.lower()) t = template_loader.get_template(template_name) c = RequestContext(request, { template_object_name: obj, 'direction': direction, }, context_processors) for key, value in extra_context.items(): if callable(value): c[key] = value() else: c[key] = value response = HttpResponse(t.render(c)) return response def vote_on_object_with_lazy_model(request, app_label, model_name, *args, **kwargs): """ Generic object vote view that takes app_label and model_name instead of a model class and calls ``vote_on_object`` view. Returns HTTP 400 (Bad Request) if there is no model matching the app_label and model_name. """ model = get_model(app_label, model_name) if not model: return HttpResponseBadRequest('Model %s.%s does not exist' % ( app_label, model_name)) return vote_on_object(request, model=model, *args, **kwargs) def json_error_response(error_message): return HttpResponse(simplejson.dumps(dict(success=False, error_message=error_message))) def xmlhttprequest_vote_on_object(request, model, direction, object_id=None, slug=None, slug_field=None): """ Generic object vote function for use via XMLHttpRequest. Properties of the resulting JSON object: success ``true`` if the vote was successfully processed, ``false`` otherwise. score The object's updated score and number of votes if the vote was successfully processed. error_message Contains an error message if the vote was not successfully processed. """ if request.method == 'GET': return json_error_response( 'XMLHttpRequest votes can only be made using POST.') if not request.user.is_authenticated(): return json_error_response('Not authenticated.') try: vote = dict(VOTE_DIRECTIONS)[direction] except KeyError: return json_error_response( '\'%s\' is not a valid vote type.' % direction) # Look up the object to be voted on lookup_kwargs = {} if object_id: lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id elif slug and slug_field: lookup_kwargs['%s__exact' % slug_field] = slug else: return json_error_response('Generic XMLHttpRequest vote view must be ' 'called with either object_id or slug and ' 'slug_field.') try: obj = model._default_manager.get(**lookup_kwargs) except ObjectDoesNotExist: return json_error_response( 'No %s found for %s.' % (model._meta.verbose_name, lookup_kwargs)) # Vote and respond Vote.objects.record_vote(obj, request.user, vote) return HttpResponse(simplejson.dumps({ 'success': True, 'score': Vote.objects.get_score(obj), }))