=============
Django Voting
=============
.. image:: https://jazzband.co/static/img/badge.svg
:target: https://jazzband.co/
:alt: Jazzband
.. image:: https://github.com/jazzband/django-voting/workflows/Test/badge.svg
:target: https://github.com/jazzband/django-voting/actions
:alt: GitHub Actions
.. image:: https://codecov.io/gh/jazzband/django-voting/branch/master/graph/badge.svg
:target: https://codecov.io/gh/jazzband/django-voting
:alt: Code coverage
.. image:: https://img.shields.io/pypi/pyversions/django-voting.svg
:target: https://pypi.python.org/pypi/django-voting
:alt: Supported Python versions
.. image:: https://img.shields.io/pypi/djversions/django-voting.svg
:target: https://pypi.org/project/django-voting
:alt: Supported Django versions
A generic voting application for Django projects, which allows
registering of votes for any ``Model`` instance.
Installation
============
1. Install the ``django-voting`` distribution
2. Add ``voting`` to your ``INSTALLED_APPS``::
INSTALLED_APPS = [
…
'voting',
…
]
3. Run ``django-admin migrate``
Usage
=====
Votes
-----
Votes are represented by the ``Vote`` model, which lives in the
``voting.models`` module.
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}
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.
* ``get_voted_users(object)`` -- Gets all users
voted on the given object.
Returns a list of objects contains user ids.
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.urls import re_path
from voting.views import vote_on_object
from shop.apps.products.models import Widget
widget_kwargs = {
'model': Widget,
'template_object_name': 'widget',
'allow_xmlhttprequest': True,
}
urlpatterns = [
re_path(
r"^widgets/(?P<object_id>\d+)/(?P<direction>up|down|clear)vote/?$",
vote_on_object,
kwargs=widget_kwargs,
),
]
``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
``<app_label>/<model_name>_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" }}