Mercurial > repos > other > FiveStarVotePlugin
changeset 0:2aa25c11dc8a
FiveStarVote plugin from http://trac-hacks.org/svn/fivestarvoteplugin/0.11/ at r13350
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 10 Aug 2013 04:54:07 -0500 |
parents | |
children | d118d75bc1d2 |
files | fivestarvote/__init__.py fivestarvote/htdocs/css/fivestarvote.css fivestarvote/htdocs/css/rating.png fivestarvote/htdocs/js/fivestarvote.js setup.py |
diffstat | 5 files changed, 322 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fivestarvote/__init__.py Sat Aug 10 04:54:07 2013 -0500 @@ -0,0 +1,204 @@ +# Based on code from : http://trac-hacks.org/wiki/VotePlugin +# Ported to a 5 star style voting system + +import re +from trac.core import * +from trac.config import ListOption +from trac.env import IEnvironmentSetupParticipant +from trac.web.api import IRequestFilter, IRequestHandler, Href +from trac.web.chrome import ITemplateProvider, add_ctxtnav, add_stylesheet, \ + add_script, add_notice +from trac.resource import get_resource_url +from trac.db import DatabaseManager, Table, Column +from trac.perm import IPermissionRequestor +from trac.util import get_reporter_id +from genshi import Markup, Stream +from genshi.builder import tag +from pkg_resources import resource_filename + + +class FiveStarVoteSystem(Component): + """Allow up and down-voting on Trac resources.""" + + implements(ITemplateProvider, IRequestFilter, IRequestHandler, + IEnvironmentSetupParticipant, IPermissionRequestor) + + voteable_paths = ListOption('fivestarvote', 'paths', '^/$,^/wiki*,^/ticket*', + doc='List of URL paths to allow voting on. Globs are supported.') + + schema = [ + Table('fivestarvote', key=('resource', 'username', 'vote'))[ + Column('resource'), + Column('username'), + Column('vote', 'int'), + ] + ] + + path_match = re.compile(r'/fivestarvote/([1-5])/(.*)') + + + # Public methods + def get_vote_counts(self, resource): + """Get total, count and tally vote counts and return them in a tuple.""" + resource = self.normalise_resource(resource) + db = self.env.get_db_cnx() + cursor = db.cursor() + sql = "select sum(vote), count(*) from fivestarvote where (resource = '%s')" % resource + self.env.log.debug("sql:: %s" % sql) + cursor.execute(sql) + row = cursor.fetchone() + sum = row[0] or 0 + total = row[1] or 0 + tally = 0 + if (total > 0): + tally = (sum / total) + return (sum, total, tally) + + def get_vote(self, req, resource): + """Return the current users vote for a resource.""" + resource = self.normalise_resource(resource) + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute('SELECT vote FROM fivestarvote WHERE username=%s ' + 'AND resource = %s', (get_reporter_id(req), resource)) + row = cursor.fetchone() + vote = row and row[0] or 0 + return vote + + def set_vote(self, req, resource, vote): + """Vote for a resource.""" + resource = self.normalise_resource(resource) + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute('DELETE FROM fivestarvote WHERE username=%s ' + 'AND resource = %s', (get_reporter_id(req), resource)) + if vote: + cursor.execute('INSERT INTO fivestarvote (resource, username, vote) ' + 'VALUES (%s, %s, %s)', + (resource, get_reporter_id(req), vote)) + db.commit() + + # IPermissionRequestor methods + def get_permission_actions(self): + return ['VOTE_VIEW', 'VOTE_MODIFY'] + + # ITemplateProvider methods + def get_templates_dirs(self): + return [] + + def get_htdocs_dirs(self): + return [('fivestarvote', resource_filename(__name__, 'htdocs'))] + + # IRequestHandler methods + def match_request(self, req): + return 'VOTE_VIEW' in req.perm and self.path_match.match(req.path_info) + + def process_request(self, req): + req.perm.require('VOTE_MODIFY') + match = self.path_match.match(req.path_info) + vote, resource = match.groups() + resource = self.normalise_resource(resource) + + self.set_vote(req, resource, vote) + self.env.log.debug("DAV::") + + if req.args.get('js'): + percent, str, title = self.format_votes(resource) + req.send(','.join(("%s" % percent, str, title))) + + req.redirect(req.get_header('Referer')) + + # IRequestFilter methods + def pre_process_request(self, req, handler): + if 'VOTE_VIEW' not in req.perm: + return handler + + for path in self.voteable_paths: + if re.match(path, req.path_info): + self.render_voter(req) + break + + return handler + + def post_process_request(self, req, template, data, content_type): + return (template, data, content_type) + + # IEnvironmentSetupParticipant methods + def environment_created(self): + self.upgrade_environment(self.env.get_db_cnx()) + + def environment_needs_upgrade(self, db): + cursor = db.cursor() + try: + cursor.execute("select count(*) from fivestarvote") + cursor.fetchone() + return False + except: + cursor.connection.rollback() + return True + + def upgrade_environment(self, db): + db_backend, _ = DatabaseManager(self.env)._get_connector() + cursor = db.cursor() + for table in self.schema: + for stmt in db_backend.to_sql(table): + self.env.log.debug(stmt) + cursor.execute(stmt) + db.commit() + + # Internal methods + def render_voter(self, req): + resource = self.normalise_resource(req.path_info) + + count = self.get_vote_counts(resource) + + add_stylesheet(req, 'fivestarvote/css/fivestarvote.css') + + names = ['', 'one', 'two', 'three', 'four', 'five'] + els = [] + percent = 0 + if count[2] > 0: + percent = count[2] * 20 + + str = "Currently %s/5 stars." % count[2] + sign = '%' + style = "width: %s%s" % (percent, sign) + li = tag.li(str, class_='current-rating', style=style) + els.append(li) + for i in range(1, 6): + className = "item %s-star" % names[i] + href = "#" + if 'VOTE_MODIFY' in req.perm and get_reporter_id(req) != 'anonymous': + href = req.href.fivestarvote(i, resource) + add_script(req, 'fivestarvote/js/fivestarvote.js', mimetype='text/javascript') + a = tag.a(i, href=href, class_=className) + li = tag.li(a) + els.append(li) + + ul = tag.ul(els, class_='star-rating') + className = '' + if 'VOTE_MODIFY' in req.perm and get_reporter_id(req) != 'anonymous': + className = 'active' + title = "Current Vote: %s users voted for a total of %s" % (count[1], count[0]) + add_ctxtnav(req, tag.span(tag.object(ul), id='fivestarvotes', title=title, class_=className)) + + + def normalise_resource(self, resource): + if isinstance(resource, basestring): + resource = resource.strip('/') + # Special-case start page + if not resource or resource == 'wiki': + resource = 'wiki/WikiStart' + return resource + return get_resource_url(self.env, resource, Href('')).strip('/') + + def format_votes(self, resource): + count = self.get_vote_counts(resource) + + percent = 0 + if count[2] > 0: + percent = count[2] * 20 + + str = "Currently %s/5 stars." % count[2] + title = "Current Vote: %s users voted for a total of %s" % (count[1], count[0]) + return (percent, str, title)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fivestarvote/htdocs/css/fivestarvote.css Sat Aug 10 04:54:07 2013 -0500 @@ -0,0 +1,88 @@ +/** +* CSS technique used from this article: +* http://www.komodomedia.com/blog/2007/01/css-star-rating-redux/ +*/ + +#fivestarvotes .star-rating, +#fivestarvotes .star-rating li a.item:hover, +#fivestarvotes .star-rating li a.item:active, +#fivestarvotes .star-rating li a.item:focus, +#fivestarvotes .star-rating li.current-rating { + background: url(rating.png) left -1000px repeat-x; +} +#fivestarvotes .star-rating li a.item, +#fivestarvotes .star-rating li.current-rating { + padding: 0; + margin: 0; + border-right: none; + background-color: transparent; +} +#fivestarvotes .star-rating { + position:relative; + width: 80px; + height: 18px; + overflow:hidden; + list-style:none; + margin:0; + padding:0; + display: block; + background-position: 0 -40px; + border: none; + text-align: left; +} +#fivestarvotes .star-rating li { + display: inline; + border: none; +} +#fivestarvotes .star-rating li a, +#fivestarvotes .star-rating li.current-rating { + position:absolute; + top:0; + left:0; + text-indent:-1000em; + height:25px; + line-height:25px; + outline:none; + overflow:hidden; + border: none; + background-color: transparent; +} +#fivestarvotes.active .star-rating li a.item:hover { + background-position: 0 -19px; +} +#fivestarvotes .star-rating li a.item:active, +#fivestarvotes .star-rating li a.item:focus { + /*background-position: 0 1px;*/ +} +#fivestarvotes .star-rating li a.one-star { + width:20%; + z-index:6; +} +#fivestarvotes .star-rating li a.two-star{ + width:40%; + z-index:5; +} +#fivestarvotes .star-rating li a.three-star{ + width:60%; + z-index:4; +} +#fivestarvotes .star-rating li a.four-star{ + width:80%; + z-index:3; +} +#fivestarvotes .star-rating li a.five-star{ + width:100%; + z-index:2; + } +#fivestarvotes .star-rating li.current-rating{ + z-index:1; + background-position: 0 1px; +} + +#fivestarvotes { + display: -moz-inline-block; + display: -moz-inline-box; + display: inline-block; + vertical-align: middle; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fivestarvote/htdocs/js/fivestarvote.js Sat Aug 10 04:54:07 2013 -0500 @@ -0,0 +1,13 @@ +$(document).ready(function() { + $('#fivestarvotes').click(function(e) { + var button = this; + e.preventDefault(); + $.get(e.target.href + '?js=1', function(result) { + result = result.split(','); + $('#fivestarvotes .current-rating').css('width', result[0] + '%').html(result[1]); + $('#fivestarvotes').attr('title', result[2]); + }); + return false; + }); +}); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Sat Aug 10 04:54:07 2013 -0500 @@ -0,0 +1,17 @@ +# Based on code from : http://trac-hacks.org/wiki/VotePlugin +from setuptools import setup + +setup( + name='FiveStarVote', + version='0.1.1', + packages=['fivestarvote'], + package_data={'fivestarvote' : ['htdocs/js/*.js', 'htdocs/css/*.css', 'htdocs/css/*.png']}, + author='Dav Glass', + author_email='dav.glass@yahoo.com', + maintainer = 'Ryan J Ollos', + maintainer_email = 'ryano@physiosonics.com', + license='BSD', + url='http://trac-hacks.org/wiki/FiveStarVotePlugin', + description='A plugin for 5-star voting on Trac resources.', + entry_points = {'trac.plugins': ['fivestarvote = fivestarvote']}, + )