Skip to content
This repository was archived by the owner on Jun 17, 2018. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
99a8c99
not erasing zero votes since in my system it has to remember who vote…
Oct 14, 2010
a10a9f4
vote_on_object_with_lazy_model view
fabiant7t Apr 18, 2010
84e233f
import get_model
fabiant7t Apr 18, 2010
45a0cc9
voting_vote url for generic votes
fabiant7t Apr 18, 2010
d4e8352
regex supporting dots in app_label (namespaces)
fabiant7t Apr 18, 2010
f377df5
fabiant7t Apr 18, 2010
f3a599f
import HttpResponseBadRequest
fabiant7t Apr 18, 2010
b506bda
set docs overview file to restructured text extension
ghickman Feb 10, 2011
eaaacc3
SCORES tuples item order was wrong ..
h3 Sep 8, 2011
a7b6ae2
django 1.4 timestamp fix
Jan 30, 2012
35d91ea
Merge remote-tracking branch 'h3/patch-1'
jezdez Jul 26, 2012
8205625
Cosmetic changes.
jezdez Jul 26, 2012
05fc7d8
Added VOTING_ZERO_VOTES_ALLOWED setting to decide whether votes with …
jezdez Jul 26, 2012
c853ab5
Added South migrations.
jezdez Jul 26, 2012
9e2d62a
Updated license, changelog and version.
jezdez Jul 26, 2012
43df260
Ignore *.pyc
kaleissin Nov 30, 2013
ee2f19c
Switch to unittests and test with tox
kaleissin Dec 5, 2013
f6aace7
Use aggregate() and annotate() instead of raw sql
kaleissin Dec 5, 2013
ebbc140
Prettified get_score()
kaleissin Dec 6, 2013
5809fc8
Allow older django to find the tests
kaleissin Apr 17, 2014
66fc2ad
Make tox work with Django>=1.6
kaleissin Apr 17, 2014
1a1c6b7
Django 1.7 will soon be out
kaleissin Apr 17, 2014
6aa56cd
Turn on testing for python 3.3
kaleissin Apr 17, 2014
05d5a6a
Test-coverage
kaleissin Apr 17, 2014
55fa073
Check that unicode(Vote) works for python < 3
kaleissin Apr 17, 2014
358f2c8
print(vote) now works on both python2.x and 3.x
kaleissin Apr 17, 2014
53fdb4e
Passes 2to3
kaleissin Apr 17, 2014
0bdf795
Set up Travis.CI to use tox
kaleissin Apr 17, 2014
81573cb
What, me typo?
kaleissin Apr 17, 2014
1b81d57
Travis is fiddly. Will this work?
kaleissin Apr 17, 2014
47bf5c6
Don't create .travis.yml by sedding tox.ini
kaleissin Apr 17, 2014
7929d08
Cargo-cult programming
kaleissin Apr 17, 2014
1760427
... These things do happen this late
kaleissin Apr 17, 2014
296e94c
Merge pull request #1 from kaleissin/modernization
jezdez Oct 13, 2014
933bbe6
Update views.py
karthikbgl Dec 19, 2014
6882a6c
Update url to new repository location
koobs Jun 11, 2015
1a564f3
Merge pull request #4 from koobs/patch-1
PiDelport Jun 17, 2015
a4bc36f
Merge pull request #3 from wnyc/master
PiDelport Aug 23, 2015
18ea781
Merge remote-tracking branch 'upstream/master'
Aug 24, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.egg-info
*.pyc
build
htmlcov
MANIFEST
.tox
.coverage
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
language: python
python: 2.7
install:
- pip install tox
env:
- TOXENV=py26-django1.4
- TOXENV=py26-django1.5
- TOXENV=py26-django1.6
- TOXENV=py27-django1.4
- TOXENV=py27-django1.5
- TOXENV=py27-django1.6
- TOXENV=py33-django1.5
- TOXENV=py33-django1.6
- TOXENV=coverage
script:
- tox -e $TOXENV
11 changes: 9 additions & 2 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
=======================
Django Voting Changelog
=======================
=======================

0.2
---

* Django 1.4 compatibility (timezone support)
* Added a ``time_stamp`` field to ``Vote`` model
* Added South migrations.
*
1 change: 1 addition & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ django-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
Expand Down
2 changes: 1 addition & 1 deletion docs/overview.txt → docs/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,4 @@ If no string mapping is given, ``'Up'`` and ``'Down'`` will be used.

Example usage::

{{ vote|vote_display:"Bodacious,Bogus" }}
{{ vote|vote_display:"Bodacious,Bogus" }}
48 changes: 25 additions & 23 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
from distutils.core import setup
from distutils.command.install import INSTALL_SCHEMES

# Tell distutils to put the data_files in platform-specific installation
# locations. See here for an explanation:
# http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb
for scheme in INSTALL_SCHEMES.values():
scheme['data'] = scheme['purelib']

# Dynamically calculate the version based on tagging.VERSION.
version_tuple = __import__('voting').VERSION
Expand All @@ -15,19 +8,28 @@
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',
url = 'http://code.google.com/p/django-voting/',
packages = ['voting', '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'],
)
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='https://github.com/pjdelport/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',
],
)
66 changes: 66 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[tox]
envlist =
py26-django1.4,
py26-django1.5,
py26-django1.6,
py27-django1.4,
py27-django1.5,
py27-django1.6,
py33-django1.5,
py33-django1.6,
coverage

[testenv]
setenv =
DJANGO_SETTINGS_MODULE = voting.tests.settings
PYTHONPATH = {toxinidir}
commands =
django-admin.py test voting

[testenv:coverage]
basepython = python2.7
commands =
coverage run --branch --omit=.tox/*,*/tests/*.py,*/migrations/*.py {envbindir}/django-admin.py test voting
deps =
coverage>=3.7,
Django>=1.6,<1.7

[testenv:py26-django1.4]
basepython = python2.6
deps =
Django>=1.4.2,<1.5

[testenv:py26-django1.5]
basepython = python2.6
deps =
Django>=1.5,<1.6

[testenv:py26-django1.6]
basepython = python2.6
deps =
Django>=1.6,<1.7

[testenv:py27-django1.4]
basepython = python2.7
deps =
Django>=1.4.2,<1.5

[testenv:py27-django1.5]
basepython = python2.7
deps =
Django>=1.5,<1.6

[testenv:py27-django1.6]
basepython = python2.7
deps =
Django>=1.6,<1.7

[testenv:py33-django1.5]
basepython = python3.3
deps =
Django>=1.5,<1.6

[testenv:py33-django1.6]
basepython = python3.3
deps =
Django>=1.6,<1.7
2 changes: 1 addition & 1 deletion voting/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = (0, 1, None)
VERSION = (0, 2, None)
146 changes: 45 additions & 101 deletions voting/managers.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,13 @@
from django.conf import settings
from django.db import connection, models
# coding: utf-8

try:
from django.db.models.sql.aggregates import Aggregate
except ImportError:
supports_aggregates = False
else:
supports_aggregates = True
from __future__ import unicode_literals

from django.conf import settings
from django.db import models
from django.db.models import Sum, Count
from django.contrib.contenttypes.models import ContentType

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'

ZERO_VOTES_ALLOWED = getattr(settings, 'VOTING_ZERO_VOTES_ALLOWED', False)

class VoteManager(models.Manager):
def get_score(self, obj):
Expand All @@ -42,17 +16,17 @@ def get_score(self, obj):
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]),
}
result = self.filter(
object_id=obj._get_pk_val(),
content_type=ctype
).aggregate(
score=Sum('vote'),
num_votes=Count('vote')
)

if result['score'] is None:
result['score'] = 0
return result

def get_scores_in_bulk(self, objects):
"""
Expand All @@ -62,38 +36,26 @@ def get_scores_in_bulk(self, objects):
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')


queryset = self.filter(
object_id__in=object_ids,
content_type=ctype,
).values(
'object_id',
).annotate(
score=Sum('vote'),
num_votes=Count('vote')
)

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):
Expand All @@ -109,57 +71,39 @@ def record_vote(self, obj, user, vote):
try:
v = self.get(user=user, content_type=ctype,
object_id=obj._get_pk_val())
if vote == 0:
if vote == 0 and not ZERO_VOTES_ALLOWED:
v.delete()
else:
v.vote = vote
v.save()
except models.ObjectDoesNotExist:
if vote != 0:
self.create(user=user, content_type=ctype,
object_id=obj._get_pk_val(), vote=vote)
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):
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)'
ctype = ContentType.objects.get_for_model(model)
results = self.filter(content_type=ctype).values('object_id').annotate(score=Sum('vote'))
if reversed:
having_sql = ' HAVING %(having_score)s < 0 ORDER BY %(having_score)s ASC LIMIT %%s'
results = results.order_by('score')
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()
results = results.order_by('-score')

# Use in_bulk() to avoid O(limit) db hits.
objects = Model.objects.in_bulk([id for id, score in results])
objects = model.objects.in_bulk([item['object_id'] for item in results[:limit]])

# Yield each object, score pair. Because of the lazy nature of generic
# relations, missing objects are silently ignored.
for id, score in results:
for item in results[:limit]:
id, score = item['object_id'], item['score']
if not score:
continue
if id in objects:
yield objects[id], int(score)

Expand Down
Loading