Add a ratings framework so that users can rate shared items. All sharable items--histories, workflows, datasets, visualizations, and pages--can be rated. Main framework components are (1) database tables for storing ratings; (2) UsesItemRatings mixin that supports rating items and obtaining an item's average rating; (3) controller methods to handle rating asynchronously; and (4) jQuery star rating plugin and supporting code that enables interactive item rating. (2) and (4) can be reused in the community space.

This commit is contained in:
Jeremy Goecks
2010-08-03 11:45:25 -04:00
parent a1f9e6a572
commit 1e12562272
16 changed files with 987 additions and 24 deletions
+3
View File
@@ -0,0 +1,3 @@
"""
Galaxy item attributes, such as tags, ratings, and annotations.
"""
+74
View File
@@ -0,0 +1,74 @@
from sqlalchemy.sql.expression import func
class UsesItemRatings:
"""
Mixin for getting and setting item ratings.
Class makes two assumptions:
(1) item-rating association table is named <item_class>RatingAssocation
and is in trans.model;
(2) item-rating association table has a column with a foreign key referencing
item table that contains the item's id.
"""
def get_ave_item_rating_data( self, trans, item):
""" Returns the average rating for an item."""
item_rating_assoc_class = self._get_item_rating_assoc_class( trans, item )
if not item_rating_assoc_class:
raise RuntimeException( "Item does not have ratings: %s" % item.__class__.__name__ )
item_id_filter = self._get_item_id_filter_str( trans, item, item_rating_assoc_class )
ave_rating = trans.sa_session.query( func.avg( item_rating_assoc_class.rating ) ).filter( item_id_filter ).scalar()
# Convert ave_rating to float; note: if there are no item ratings, ave rating is None.
if ave_rating:
ave_rating = float( ave_rating )
else:
ave_rating = 0
num_ratings = int( trans.sa_session.query( func.count( item_rating_assoc_class.rating ) ).filter( item_id_filter ).scalar() )
return ( ave_rating, num_ratings )
def rate_item( self, trans, user, item, rating ):
""" Rate an item. Return type is <item_class>RatingAssociation. """
item_rating = self.get_user_item_rating( trans, user, item )
if not item_rating:
# User has not yet rated item; create rating.
item_rating_assoc_class = self._get_item_rating_assoc_class( trans, item )
item_rating = item_rating_assoc_class()
item_rating.user = trans.get_user()
item_rating.set_item( item )
item_rating.rating = rating
trans.sa_session.add( item_rating )
trans.sa_session.flush()
elif item_rating.rating != rating:
# User has rated item; update rating.
item_rating.rating = rating
trans.sa_session.flush()
return item_rating
def get_user_item_rating( self, trans, user, item ):
""" Returns user's rating for an item. Return type is <item_class>RatingAssociation. """
item_rating_assoc_class = self._get_item_rating_assoc_class( trans, item )
if not item_rating_assoc_class:
raise RuntimeException( "Item does not have ratings: %s" % item.__class__.__name__ )
# Query rating table by user and item id.
item_id_filter = self._get_item_id_filter_str( trans, item, item_rating_assoc_class )
return trans.sa_session.query( item_rating_assoc_class ).filter_by( user=user ).filter( item_id_filter ).first()
def _get_item_rating_assoc_class( self, trans, item ):
""" Returns an item's item-rating association class. """
item_rating_assoc_class = '%sRatingAssociation' % item.__class__.__name__
return trans.model.get( item_rating_assoc_class, None )
def _get_item_id_filter_str( self, trans, item, item_rating_assoc_class ):
# Get foreign key in item-rating association table that references item table.
item_fk = None
for fk in item_rating_assoc_class.table.foreign_keys:
if fk.references( item.table ):
item_fk = fk
break
if not item_fk:
raise RuntimeException( "Cannot find item id column in item-rating association table: %s, %s" % item_rating_assoc_class.__name__, item_rating_assoc_class.table.name )
# TODO: can we provide a better filter than a raw string?
return "%s=%i" % ( item_fk.parent.name, item.id )
+35
View File
@@ -1766,6 +1766,8 @@ class StoredWorkflowTagAssociation ( ItemTagAssociation ):
class VisualizationTagAssociation ( ItemTagAssociation ):
pass
# Item annotation classes.
class HistoryAnnotationAssociation( object ):
pass
@@ -1784,6 +1786,39 @@ class PageAnnotationAssociation( object ):
class VisualizationAnnotationAssociation( object ):
pass
# Item rating classes.
class ItemRatingAssociation( object ):
def __init__( self, id=None, user=None, item=None, rating=0 ):
self.id = id
self.user = user
self.item = item
self.rating = rating
def set_item( self, item ):
""" Set association's item. """
pass
class HistoryRatingAssociation( ItemRatingAssociation ):
def set_item( self, history ):
self.history = history
class HistoryDatasetAssociationRatingAssociation( ItemRatingAssociation ):
def set_item( self, history_dataset_association ):
self.history_dataset_association = history_dataset_association
class StoredWorkflowRatingAssociation( ItemRatingAssociation ):
def set_item( self, stored_workflow ):
self.stored_workflow = stored_workflow
class PageRatingAssociation( ItemRatingAssociation ):
def set_item( self, page ):
self.page = page
class VisualizationRatingAssociation( ItemRatingAssociation ):
def set_item( self, visualization ):
self.visualization = visualization
class UserPreference ( object ):
def __init__( self, name=None, value=None ):
self.name = name
+72 -6
View File
@@ -771,7 +771,38 @@ VisualizationAnnotationAssociation.table = Table( "visualization_annotation_asso
Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "annotation", TEXT, index=True) )
# Ratings tables.
HistoryRatingAssociation.table = Table( "history_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
HistoryDatasetAssociationRatingAssociation.table = Table( "history_dataset_association_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
StoredWorkflowRatingAssociation.table = Table( "stored_workflow_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
PageRatingAssociation.table = Table( "page_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
VisualizationRatingAssociation.table = Table( "visualization_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
# User tables.
UserPreference.table = Table( "user_preference", metadata,
@@ -916,7 +947,8 @@ assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.tab
HistoryDatasetAssociation,
primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) ),
tags=relation( HistoryDatasetAssociationTagAssociation, order_by=HistoryDatasetAssociationTagAssociation.table.c.id, backref='history_tag_associations' ),
annotations=relation( HistoryDatasetAssociationAnnotationAssociation, order_by=HistoryDatasetAssociationAnnotationAssociation.table.c.id, backref="hdas" ) )
annotations=relation( HistoryDatasetAssociationAnnotationAssociation, order_by=HistoryDatasetAssociationAnnotationAssociation.table.c.id, backref="hdas" ),
ratings=relation( HistoryDatasetAssociationRatingAssociation, order_by=HistoryDatasetAssociationRatingAssociation.table.c.id, backref="hdas" ) )
)
assign_mapper( context, Dataset, Dataset.table,
@@ -954,7 +986,8 @@ assign_mapper( context, History, History.table,
datasets=relation( HistoryDatasetAssociation, backref="history", order_by=asc(HistoryDatasetAssociation.table.c.hid) ),
active_datasets=relation( HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.history_id == History.table.c.id ) & ( not_( HistoryDatasetAssociation.table.c.deleted ) ) ), order_by=asc( HistoryDatasetAssociation.table.c.hid ), viewonly=True ),
tags=relation( HistoryTagAssociation, order_by=HistoryTagAssociation.table.c.id, backref="histories" ),
annotations=relation( HistoryAnnotationAssociation, order_by=HistoryAnnotationAssociation.table.c.id, backref="histories" ) )
annotations=relation( HistoryAnnotationAssociation, order_by=HistoryAnnotationAssociation.table.c.id, backref="histories" ),
ratings=relation( HistoryRatingAssociation, order_by=HistoryRatingAssociation.table.c.id, backref="histories" ) )
)
# Set up proxy so that
@@ -1220,7 +1253,8 @@ assign_mapper( context, StoredWorkflow, StoredWorkflow.table,
primaryjoin=( StoredWorkflow.table.c.latest_workflow_id == Workflow.table.c.id ),
lazy=False ),
tags=relation( StoredWorkflowTagAssociation, order_by=StoredWorkflowTagAssociation.table.c.id, backref="stored_workflows" ),
annotations=relation( StoredWorkflowAnnotationAssociation, order_by=StoredWorkflowAnnotationAssociation.table.c.id, backref="stored_workflows" ) )
annotations=relation( StoredWorkflowAnnotationAssociation, order_by=StoredWorkflowAnnotationAssociation.table.c.id, backref="stored_workflows" ),
ratings=relation( StoredWorkflowRatingAssociation, order_by=StoredWorkflowRatingAssociation.table.c.id, backref="stored_workflows" ) )
)
# Set up proxy so that
@@ -1260,7 +1294,8 @@ assign_mapper( context, Page, Page.table,
primaryjoin=( Page.table.c.latest_revision_id == PageRevision.table.c.id ),
lazy=False ),
tags=relation(PageTagAssociation, order_by=PageTagAssociation.table.c.id, backref="pages"),
annotations=relation( PageAnnotationAssociation, order_by=PageAnnotationAssociation.table.c.id, backref="pages" )
annotations=relation( PageAnnotationAssociation, order_by=PageAnnotationAssociation.table.c.id, backref="pages" ),
ratings=relation( PageRatingAssociation, order_by=PageRatingAssociation.table.c.id, backref="pages" )
) )
# Set up proxy so that
@@ -1284,7 +1319,8 @@ assign_mapper( context, Visualization, Visualization.table,
primaryjoin=( Visualization.table.c.latest_revision_id == VisualizationRevision.table.c.id ),
lazy=False ),
tags=relation( VisualizationTagAssociation, order_by=VisualizationTagAssociation.table.c.id, backref="visualizations" ),
annotations=relation( VisualizationAnnotationAssociation, order_by=VisualizationAnnotationAssociation.table.c.id, backref="visualizations" )
annotations=relation( VisualizationAnnotationAssociation, order_by=VisualizationAnnotationAssociation.table.c.id, backref="visualizations" ),
ratings=relation( VisualizationRatingAssociation, order_by=VisualizationRatingAssociation.table.c.id, backref="visualizations" )
) )
# Set up proxy so that
@@ -1296,6 +1332,8 @@ assign_mapper( context, VisualizationUserShareAssociation, VisualizationUserShar
properties=dict( user=relation( User, backref='visualizations_shared_by_others' ),
visualization=relation( Visualization, backref='users_shared_with' )
) )
# Tag tables.
assign_mapper( context, Tag, Tag.table,
properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[Tag.table.c.id] ) )
@@ -1329,6 +1367,8 @@ assign_mapper( context, VisualizationTagAssociation, VisualizationTagAssociation
properties=dict( tag=relation(Tag, backref="tagged_visualizations"), user=relation( User ) )
)
# Annotation tables.
assign_mapper( context, HistoryAnnotationAssociation, HistoryAnnotationAssociation.table,
properties=dict( history=relation( History ), user=relation( User ) )
)
@@ -1353,6 +1393,30 @@ assign_mapper( context, VisualizationAnnotationAssociation, VisualizationAnnotat
properties=dict( visualization=relation( Visualization ), user=relation( User ) )
)
# Rating tables.
assign_mapper( context, HistoryRatingAssociation, HistoryRatingAssociation.table,
properties=dict( history=relation( History ), user=relation( User ) )
)
assign_mapper( context, HistoryDatasetAssociationRatingAssociation, HistoryDatasetAssociationRatingAssociation.table,
properties=dict( hda=relation( HistoryDatasetAssociation ), user=relation( User ) )
)
assign_mapper( context, StoredWorkflowRatingAssociation, StoredWorkflowRatingAssociation.table,
properties=dict( stored_workflow=relation( StoredWorkflow ), user=relation( User ) )
)
assign_mapper( context, PageRatingAssociation, PageRatingAssociation.table,
properties=dict( page=relation( Page ), user=relation( User ) )
)
assign_mapper( context, VisualizationRatingAssociation, VisualizationRatingAssociation.table,
properties=dict( visualization=relation( Visualization ), user=relation( User ) )
)
# User tables.
assign_mapper( context, UserPreference, UserPreference.table,
properties = {}
)
@@ -1363,6 +1427,8 @@ assign_mapper( context, UserAction, UserAction.table,
assign_mapper( context, APIKeys, APIKeys.table,
properties = {} )
# Helper methods.
def db_next_hid( self ):
"""
@@ -0,0 +1,133 @@
"""
Migration script to create tables for rating histories, datasets, workflows, pages, and visualizations.
"""
from sqlalchemy import *
from sqlalchemy.orm import *
from migrate import *
from migrate.changeset import *
import logging
log = logging.getLogger( __name__ )
metadata = MetaData( migrate_engine )
db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) )
# Rating tables.
HistoryRatingAssociation_table = Table( "history_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
HistoryDatasetAssociationRatingAssociation_table = Table( "history_dataset_association_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
StoredWorkflowRatingAssociation_table = Table( "stored_workflow_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
PageRatingAssociation_table = Table( "page_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
VisualizationRatingAssociation_table = Table( "visualization_rating_association", metadata,
Column( "id", Integer, primary_key=True ),
Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ),
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "rating", Integer, index=True) )
def upgrade():
print __doc__
metadata.reflect()
# Create history_rating_association table.
try:
HistoryRatingAssociation_table.create()
except Exception, e:
print str(e)
log.debug( "Creating history_rating_association table failed: %s" % str( e ) )
# Create history_dataset_association_rating_association table.
try:
HistoryDatasetAssociationRatingAssociation_table.create()
except Exception, e:
# MySQL cannot handle long index names; when we see this error, create the index name manually.
if migrate_engine.name == 'mysql' and \
str(e).lower().find("identifier name 'ix_history_dataset_association_rating_association_history_dataset_association_id' is too long"):
i = Index( "ix_hda_rating_association_hda_id", HistoryDatasetAssociationRatingAssociation_table.c.history_dataset_association_id )
try:
i.create()
except Exception, e:
print str(e)
log.debug( "Adding index 'ix_hda_rating_association_hda_id' to table 'history_dataset_association_rating_association' table failed: %s" % str( e ) )
else:
print str(e)
log.debug( "Creating history_dataset_association_rating_association table failed: %s" % str( e ) )
# Create stored_workflow_rating_association table.
try:
StoredWorkflowRatingAssociation_table.create()
except Exception, e:
print str(e)
log.debug( "Creating stored_workflow_rating_association table failed: %s" % str( e ) )
# Create page_rating_association table.
try:
PageRatingAssociation_table.create()
except Exception, e:
print str(e)
log.debug( "Creating page_rating_association table failed: %s" % str( e ) )
# Create visualization_rating_association table.
try:
VisualizationRatingAssociation_table.create()
except Exception, e:
print str(e)
log.debug( "Creating visualization_rating_association table failed: %s" % str( e ) )
def downgrade():
metadata.reflect()
# Drop history_rating_association table.
try:
HistoryRatingAssociation_table.drop()
except Exception, e:
print str(e)
log.debug( "Dropping history_rating_association table failed: %s" % str( e ) )
# Drop history_dataset_association_rating_association table.
try:
HistoryDatasetAssociationRatingAssociation_table.drop()
except Exception, e:
print str(e)
log.debug( "Dropping history_dataset_association_rating_association table failed: %s" % str( e ) )
# Drop stored_workflow_rating_association table.
try:
StoredWorkflowRatingAssociation_table.drop()
except Exception, e:
print str(e)
log.debug( "Dropping stored_workflow_rating_association table failed: %s" % str( e ) )
# Drop page_rating_association table.
try:
PageRatingAssociation_table.drop()
except Exception, e:
print str(e)
log.debug( "Dropping page_rating_association table failed: %s" % str( e ) )
# Drop visualization_rating_association table.
try:
VisualizationRatingAssociation_table.drop()
except Exception, e:
print str(e)
log.debug( "Dropping visualization_rating_association table failed: %s" % str( e ) )
+28 -1
View File
@@ -6,6 +6,7 @@ from galaxy import util, datatypes, jobs, web, model
from cgi import escape, FieldStorage
from galaxy.datatypes.display_applications.util import encode_dataset_user, decode_dataset_user
from galaxy.util.sanitize_html import sanitize_html
from galaxy.item_attrs.ratings import UsesItemRatings
from email.MIMEText import MIMEText
import pkg_resources;
@@ -466,6 +467,21 @@ class DatasetInterface( BaseController, UsesAnnotations, UsesHistoryDatasetAssoc
""" Does nothing because datasets do not have an importable/accessible attribute. This method could potentially set another attribute. """
return
@web.expose
@web.require_login( "rate items" )
@web.json
def rate_async( self, trans, id, rating ):
""" Rate a dataset asynchronously and return updated community data. """
dataset = self.get_dataset( trans, id, check_ownership=False, check_accessible=True )
if not dataset:
return trans.show_error_message( "The specified dataset does not exist." )
# Rate dataset.
dataset_rating = self.rate_item( trans, trans.get_user(), dataset, rating )
return self.get_ave_item_rating_data( trans, dataset )
@web.expose
def display_by_username_and_slug( self, trans, username, slug, preview=True ):
""" Display dataset by username and slug; because datasets do not yet have slugs, the slug is the dataset's id. """
@@ -481,7 +497,18 @@ class DatasetInterface( BaseController, UsesAnnotations, UsesHistoryDatasetAssoc
trans.response.set_content_type( mime )
return open( dataset.file_name )
else:
return trans.fill_template_mako( "/dataset/display.mako", item=dataset, item_data=dataset_data, truncated=truncated )
# Get rating data.
user_item_rating = 0
if trans.get_user():
user_item_rating = self.get_user_item_rating( trans, trans.get_user(), dataset )
if user_item_rating:
user_item_rating = user_item_rating.rating
else:
user_item_rating = 0
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans, dataset )
return trans.fill_template_mako( "/dataset/display.mako", item=dataset, item_data=dataset_data, truncated=truncated,
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings )
else:
raise web.httpexceptions.HTTPNotFound()
+30 -4
View File
@@ -9,6 +9,7 @@ from galaxy.util.sanitize_html import sanitize_html
from galaxy.tools.parameters.basic import UnvalidatedValue
from galaxy.tools.actions import upload_common
from galaxy.tags.tag_handler import GalaxyTagHandler
from galaxy.item_attrs.ratings import UsesItemRatings
from sqlalchemy.sql.expression import ClauseElement
import webhelpers, logging, operator, os, tempfile, subprocess, shutil, tarfile
from datetime import datetime
@@ -147,7 +148,7 @@ class HistoryAllPublishedGrid( grids.Grid ):
# A public history is published, has a slug, and is not deleted.
return query.filter( self.model_class.published == True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False )
class HistoryController( BaseController, Sharable, UsesAnnotations, UsesHistory ):
class HistoryController( BaseController, Sharable, UsesAnnotations, UsesItemRatings, UsesHistory ):
@web.expose
def index( self, trans ):
return ""
@@ -415,7 +416,22 @@ class HistoryController( BaseController, Sharable, UsesAnnotations, UsesHistory
trans.log_event( "History id %d marked as deleted" % history.id )
# Regardless of whether it was previously deleted, we make a new history active
trans.new_history()
return trans.show_ok_message( "History deleted, a new history is active", refresh_frames=['history'] )
return trans.show_ok_message( "History deleted, a new history is active", refresh_frames=['history'] )
@web.expose
@web.require_login( "rate items" )
@web.json
def rate_async( self, trans, id, rating ):
""" Rate a history asynchronously and return updated community data. """
history = self.get_history( trans, id, check_ownership=False, check_accessible=True )
if not history:
return trans.show_error_message( "The specified history does not exist." )
# Rate history.
history_rating = self.rate_item( trans, trans.get_user(), history, rating )
return self.get_ave_item_rating_data( trans, history )
@web.expose
def rename_async( self, trans, id=None, new_name=None ):
@@ -1047,8 +1063,18 @@ class HistoryController( BaseController, Sharable, UsesAnnotations, UsesHistory
history.annotation = self.get_item_annotation_str( trans, history.user, history )
for dataset in datasets:
dataset.annotation = self.get_item_annotation_str( trans, history.user, dataset )
return trans.stream_template_mako( "history/display.mako",
item = history, item_data = datasets )
# Get rating data.
user_item_rating = 0
if trans.get_user():
user_item_rating = self.get_user_item_rating( trans, trans.get_user(), history )
if user_item_rating:
user_item_rating = user_item_rating.rating
else:
user_item_rating = 0
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans, history )
return trans.stream_template_mako( "history/display.mako", item = history, item_data = datasets,
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings )
@web.expose
@web.require_login( "share Galaxy histories" )
+32 -2
View File
@@ -4,6 +4,7 @@ from galaxy.web.framework.helpers import time_ago, grids
from galaxy.util.sanitize_html import sanitize_html, _BaseHTMLProcessor
from galaxy.util.odict import odict
from galaxy.util.json import from_json_string
from galaxy.item_attrs.ratings import UsesItemRatings
def format_bool( b ):
if b:
@@ -269,7 +270,8 @@ class _PageContentProcessor( _BaseHTMLProcessor ):
# Default behavior:
_BaseHTMLProcessor.unknown_endtag( self, tag )
class PageController( BaseController, Sharable, UsesAnnotations, UsesHistory, UsesStoredWorkflow, UsesHistoryDatasetAssociation, UsesVisualization ):
class PageController( BaseController, Sharable, UsesAnnotations, UsesHistory,
UsesStoredWorkflow, UsesHistoryDatasetAssociation, UsesVisualization, UsesItemRatings ):
_page_list = PageListGrid()
_all_published_list = PageAllPublishedGrid()
@@ -582,9 +584,22 @@ class PageController( BaseController, Sharable, UsesAnnotations, UsesHistory, Us
# Process page content.
processor = _PageContentProcessor( trans, 'utf-8', 'text/html', self._get_embed_html )
processor.feed( page.latest_revision.content )
# Get rating data.
user_item_rating = 0
if trans.get_user():
user_item_rating = self.get_user_item_rating( trans, trans.get_user(), page )
if user_item_rating:
user_item_rating = user_item_rating.rating
else:
user_item_rating = 0
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans, page )
# Output is string, so convert to unicode for display.
page_content = unicode( processor.output(), 'utf-8' )
return trans.fill_template_mako( "page/display.mako", item=page, item_data=page_content, content_only=True )
return trans.fill_template_mako( "page/display.mako", item=page, item_data=page_content,
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
content_only=True )
@web.expose
@web.require_login( "use Galaxy pages" )
@@ -611,6 +626,21 @@ class PageController( BaseController, Sharable, UsesAnnotations, UsesHistory, Us
trans.sa_session.flush()
return page.slug
@web.expose
@web.require_login( "rate items" )
@web.json
def rate_async( self, trans, id, rating ):
""" Rate a page asynchronously and return updated community data. """
page = self.get_page( trans, id, check_ownership=False, check_accessible=True )
if not page:
return trans.show_error_message( "The specified page does not exist." )
# Rate page.
page_rating = self.rate_item( trans, trans.get_user(), page, rating )
return self.get_ave_item_rating_data( trans, page )
@web.expose
def get_embed_html_async( self, trans, id ):
""" Returns HTML for embedding a workflow in a page. """
+31 -3
View File
@@ -1,5 +1,6 @@
from galaxy import model
from galaxy.web.base.controller import *
from galaxy.item_attrs.ratings import UsesItemRatings
from galaxy.web.framework.helpers import time_ago, grids, iff
from galaxy.util.sanitize_html import sanitize_html
@@ -65,7 +66,7 @@ class VisualizationAllPublishedGrid( grids.Grid ):
return query.filter( self.model_class.deleted==False ).filter( self.model_class.published==True )
class VisualizationController( BaseController, Sharable, UsesAnnotations, UsesVisualization ):
class VisualizationController( BaseController, Sharable, UsesAnnotations, UsesVisualization, UsesItemRatings ):
_user_list_grid = VisualizationListGrid()
_published_list_grid = VisualizationAllPublishedGrid()
@@ -138,6 +139,21 @@ class VisualizationController( BaseController, Sharable, UsesAnnotations, UsesVi
return
@web.expose
@web.require_login( "rate items" )
@web.json
def rate_async( self, trans, id, rating ):
""" Rate a visualization asynchronously and return updated community data. """
visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
if not visualization:
return trans.show_error_message( "The specified visualization does not exist." )
# Rate visualization.
visualization_rating = self.rate_item( trans, trans.get_user(), visualization, rating )
return self.get_ave_item_rating_data( trans, visualization )
@web.expose
@web.require_login( "share Galaxy visualizations" )
def imp( self, trans, id ):
@@ -263,9 +279,21 @@ class VisualizationController( BaseController, Sharable, UsesAnnotations, UsesVi
# Security check raises error if user cannot access visualization.
self.security_check( trans.get_user(), visualization, False, True)
# Get rating data.
user_item_rating = 0
if trans.get_user():
user_item_rating = self.get_user_item_rating( trans, trans.get_user(), visualization )
if user_item_rating:
user_item_rating = user_item_rating.rating
else:
user_item_rating = 0
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans, visualization )
# Display.
visualization_config = self.get_visualization_config( trans, visualization )
return trans.fill_template_mako( "visualization/display.mako", item=visualization, item_data=visualization_config, content_only=True )
visualization_config = self.get_visualization_config( trans, visualization )
return trans.stream_template_mako( "visualization/display.mako", item = visualization, item_data = visualization_config,
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
content_only=True )
@web.expose
@web.json
+29 -2
View File
@@ -18,6 +18,7 @@ from galaxy import model
from galaxy.model.mapping import desc
from galaxy.model.orm import *
from galaxy.jobs.actions.post import *
from galaxy.item_attrs.ratings import UsesItemRatings
import urllib2
@@ -83,7 +84,7 @@ class StoredWorkflowAllPublishedGrid( grids.Grid ):
# A public workflow is published, has a slug, and is not deleted.
return query.filter( self.model_class.published==True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False )
class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnnotations ):
class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnnotations, UsesItemRatings ):
stored_list_grid = StoredWorkflowListGrid()
published_list_grid = StoredWorkflowAllPublishedGrid()
@@ -185,7 +186,18 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno
stored_workflow.annotation = self.get_item_annotation_str( trans, stored_workflow.user, stored_workflow )
for step in stored_workflow.latest_workflow.steps:
step.annotation = self.get_item_annotation_str( trans, stored_workflow.user, step )
return trans.fill_template_mako( "workflow/display.mako", item=stored_workflow, item_data=stored_workflow.latest_workflow.steps )
# Get rating data.
user_item_rating = 0
if trans.get_user():
user_item_rating = self.get_user_item_rating( trans, trans.get_user(), stored_workflow )
if user_item_rating:
user_item_rating = user_item_rating.rating
else:
user_item_rating = 0
ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans, stored_workflow )
return trans.fill_template_mako( "workflow/display.mako", item=stored_workflow, item_data=stored_workflow.latest_workflow.steps,
user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings )
@web.expose
def get_item_content_async( self, trans, id ):
@@ -376,6 +388,21 @@ class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnno
trans.sa_session.flush()
return new_annotation
@web.expose
@web.require_login( "rate items" )
@web.json
def rate_async( self, trans, id, rating ):
""" Rate a workflow asynchronously and return updated community data. """
stored = self.get_stored_workflow( trans, id, check_ownership=False, check_accessible=True )
if not stored:
return trans.show_error_message( "The specified workflow does not exist." )
# Rate workflow.
stored_rating = self.rate_item( trans, trans.get_user(), stored, rating )
return self.get_ave_item_rating_data( trans, stored )
@web.expose
@web.require_login( "use Galaxy workflows" )
def set_accessible_async( self, trans, id=None, accessible=False ):
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

+12
View File
@@ -0,0 +1,12 @@
/* jQuery.Rating Plugin CSS - http://www.fyneworks.com/jquery/star-rating/ */
div.rating-cancel,div.star-rating{float:left;width:17px;height:15px;text-indent:-999em;cursor:pointer;display:block;background:transparent;overflow:hidden}
div.rating-cancel,div.rating-cancel a{background:url(../images/delete.gif) no-repeat 0 -16px}
div.star-rating,div.star-rating a{background:url(../images/star.gif) no-repeat 0 0px}
div.rating-cancel a,div.star-rating a{display:block;width:16px;height:100%;background-position:0 0px;border:0}
div.star-rating-on a{background-position:0 -16px!important}
div.star-rating-hover a{background-position:0 -32px}
/* Read Only CSS */
div.star-rating-readonly a{cursor:default !important}
/* Partial Star CSS */
div.star-rating{background:transparent!important;overflow:hidden!important}
/* END jQuery.Rating Plugin CSS */
+383
View File
@@ -0,0 +1,383 @@
/*
### jQuery Star Rating Plugin v3.13 - 2009-03-26 ###
* Home: http://www.fyneworks.com/jquery/star-rating/
* Code: http://code.google.com/p/jquery-star-rating-plugin/
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
###
*/
/*# AVOID COLLISIONS #*/
;if(window.jQuery) (function($){
/*# AVOID COLLISIONS #*/
// IE6 Background Image Fix
if ($.browser.msie) try { document.execCommand("BackgroundImageCache", false, true)} catch(e) { };
// Thanks to http://www.visualjquery.com/rating/rating_redux.html
// plugin initialization
$.fn.rating = function(options){
if(this.length==0) return this; // quick fail
// Handle API methods
if(typeof arguments[0]=='string'){
// Perform API methods on individual elements
if(this.length>1){
var args = arguments;
return this.each(function(){
$.fn.rating.apply($(this), args);
});
};
// Invoke API method handler
$.fn.rating[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []);
// Quick exit...
return this;
};
// Initialize options for this call
var options = $.extend(
{}/* new object */,
$.fn.rating.options/* default options */,
options || {} /* just-in-time options */
);
// Allow multiple controls with the same name by making each call unique
$.fn.rating.calls++;
// loop through each matched element
this
.not('.star-rating-applied')
.addClass('star-rating-applied')
.each(function(){
// Load control parameters / find context / etc
var control, input = $(this);
var eid = (this.name || 'unnamed-rating').replace(/\[|\]/g, '_').replace(/^\_+|\_+$/g,'');
var context = $(this.form || document.body);
// FIX: http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=23
var raters = context.data('rating');
if(!raters || raters.call!=$.fn.rating.calls) raters = { count:0, call:$.fn.rating.calls };
var rater = raters[eid];
// if rater is available, verify that the control still exists
if(rater) control = rater.data('rating');
if(rater && control)//{// save a byte!
// add star to control if rater is available and the same control still exists
control.count++;
//}// save a byte!
else{
// create new control if first star or control element was removed/replaced
// Initialize options for this raters
control = $.extend(
{}/* new object */,
options || {} /* current call options */,
($.metadata? input.metadata(): ($.meta?input.data():null)) || {}, /* metadata options */
{ count:0, stars: [], inputs: [] }
);
// increment number of rating controls
control.serial = raters.count++;
// create rating element
rater = $('<span class="star-rating-control"/>');
input.before(rater);
// Mark element for initialization (once all stars are ready)
rater.addClass('rating-to-be-drawn');
// Accept readOnly setting from 'disabled' property
if(input.attr('disabled')) control.readOnly = true;
// Create 'cancel' button
rater.append(
control.cancel = $('<div class="rating-cancel"><a title="' + control.cancel + '">' + control.cancelValue + '</a></div>')
.mouseover(function(){
$(this).rating('drain');
$(this).addClass('star-rating-hover');
//$(this).rating('focus');
})
.mouseout(function(){
$(this).rating('draw');
$(this).removeClass('star-rating-hover');
//$(this).rating('blur');
})
.click(function(){
$(this).rating('select');
})
.data('rating', control)
);
}; // first element of group
// insert rating star
var star = $('<div class="star-rating rater-'+ control.serial +'"><a title="' + (this.title || this.value) + '">' + this.value + '</a></div>');
rater.append(star);
// inherit attributes from input element
if(this.id) star.attr('id', this.id);
if(this.className) star.addClass(this.className);
// Half-stars?
if(control.half) control.split = 2;
// Prepare division control
if(typeof control.split=='number' && control.split>0){
var stw = ($.fn.width ? star.width() : 0) || control.starWidth;
var spi = (control.count % control.split), spw = Math.floor(stw/control.split);
star
// restrict star's width and hide overflow (already in CSS)
.width(spw)
// move the star left by using a negative margin
// this is work-around to IE's stupid box model (position:relative doesn't work)
.find('a').css({ 'margin-left':'-'+ (spi*spw) +'px' })
};
// readOnly?
if(control.readOnly)//{ //save a byte!
// Mark star as readOnly so user can customize display
star.addClass('star-rating-readonly');
//} //save a byte!
else//{ //save a byte!
// Enable hover css effects
star.addClass('star-rating-live')
// Attach mouse events
.mouseover(function(){
$(this).rating('fill');
$(this).rating('focus');
})
.mouseout(function(){
$(this).rating('draw');
$(this).rating('blur');
})
.click(function(){
$(this).rating('select');
})
;
//}; //save a byte!
// set current selection
if(this.checked) control.current = star;
// hide input element
input.hide();
// backward compatibility, form element to plugin
input.change(function(){
$(this).rating('select');
});
// attach reference to star to input element and vice-versa
star.data('rating.input', input.data('rating.star', star));
// store control information in form (or body when form not available)
control.stars[control.stars.length] = star[0];
control.inputs[control.inputs.length] = input[0];
control.rater = raters[eid] = rater;
control.context = context;
input.data('rating', control);
rater.data('rating', control);
star.data('rating', control);
context.data('rating', raters);
}); // each element
// Initialize ratings (first draw)
$('.rating-to-be-drawn').rating('draw').removeClass('rating-to-be-drawn');
return this; // don't break the chain...
};
/*--------------------------------------------------------*/
/*
### Core functionality and API ###
*/
$.extend($.fn.rating, {
// Used to append a unique serial number to internal control ID
// each time the plugin is invoked so same name controls can co-exist
calls: 0,
focus: function(){
var control = this.data('rating'); if(!control) return this;
if(!control.focus) return this; // quick fail if not required
// find data for event
var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
// focus handler, as requested by focusdigital.co.uk
if(control.focus) control.focus.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
}, // $.fn.rating.focus
blur: function(){
var control = this.data('rating'); if(!control) return this;
if(!control.blur) return this; // quick fail if not required
// find data for event
var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
// blur handler, as requested by focusdigital.co.uk
if(control.blur) control.blur.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
}, // $.fn.rating.blur
fill: function(){ // fill to the current mouse position.
var control = this.data('rating'); if(!control) return this;
// do not execute when control is in read-only mode
if(control.readOnly) return;
// Reset all stars and highlight them up to this element
this.rating('drain');
this.prevAll().andSelf().filter('.rater-'+ control.serial).addClass('star-rating-hover');
},// $.fn.rating.fill
drain: function() { // drain all the stars.
var control = this.data('rating'); if(!control) return this;
// do not execute when control is in read-only mode
if(control.readOnly) return;
// Reset all stars
control.rater.children().filter('.rater-'+ control.serial).removeClass('star-rating-on').removeClass('star-rating-hover');
},// $.fn.rating.drain
draw: function(){ // set value and stars to reflect current selection
var control = this.data('rating'); if(!control) return this;
// Clear all stars
this.rating('drain');
// Set control value
if(control.current){
control.current.data('rating.input').attr('checked','checked');
control.current.prevAll().andSelf().filter('.rater-'+ control.serial).addClass('star-rating-on');
}
else
$(control.inputs).removeAttr('checked');
// Show/hide 'cancel' button
control.cancel[control.readOnly || control.required?'hide':'show']();
// Add/remove read-only classes to remove hand pointer
this.siblings()[control.readOnly?'addClass':'removeClass']('star-rating-readonly');
},// $.fn.rating.draw
select: function(value,wantCallBack){ // select a value
// ***** MODIFICATION *****
// Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27
//
// ***** LIST OF MODIFICATION *****
// ***** added Parameter wantCallBack : false if you don't want a callback. true or undefined if you want postback to be performed at the end of this method'
// ***** recursive calls to this method were like : ... .rating('select') it's now like .rating('select',undefined,wantCallBack); (parameters are set.)
// ***** line which is calling callback
// ***** /LIST OF MODIFICATION *****
var control = this.data('rating'); if(!control) return this;
// do not execute when control is in read-only mode
if(control.readOnly) return;
// clear selection
control.current = null;
// programmatically (based on user input)
if(typeof value!='undefined'){
// select by index (0 based)
if(typeof value=='number')
return $(control.stars[value]).rating('select',undefined,wantCallBack);
// select by literal value (must be passed as a string
if(typeof value=='string')
//return
$.each(control.stars, function(){
if($(this).data('rating.input').val()==value) $(this).rating('select',undefined,wantCallBack);
});
}
else
control.current = this[0].tagName=='INPUT' ?
this.data('rating.star') :
(this.is('.rater-'+ control.serial) ? this : null);
// Update rating control state
this.data('rating', control);
// Update display
this.rating('draw');
// find data for event
var input = $( control.current ? control.current.data('rating.input') : null );
// click callback, as requested here: http://plugins.jquery.com/node/1655
// **** MODIFICATION *****
// Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27
//
//old line doing the callback :
//if(control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event
//
//new line doing the callback (if i want :)
if((wantCallBack ||wantCallBack == undefined) && control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event
//to ensure retro-compatibility, wantCallBack must be considered as true by default
// **** /MODIFICATION *****
},// $.fn.rating.select
readOnly: function(toggle, disable){ // make the control read-only (still submits value)
var control = this.data('rating'); if(!control) return this;
// setread-only status
control.readOnly = toggle || toggle==undefined ? true : false;
// enable/disable control value submission
if(disable) $(control.inputs).attr("disabled", "disabled");
else $(control.inputs).removeAttr("disabled");
// Update rating control state
this.data('rating', control);
// Update display
this.rating('draw');
},// $.fn.rating.readOnly
disable: function(){ // make read-only and never submit value
this.rating('readOnly', true, true);
},// $.fn.rating.disable
enable: function(){ // make read/write and submit value
this.rating('readOnly', false, false);
}// $.fn.rating.select
});
/*--------------------------------------------------------*/
/*
### Default Settings ###
eg.: You can override default control like this:
$.fn.rating.options.cancel = 'Clear';
*/
$.fn.rating.options = { //$.extend($.fn.rating, { options: {
cancel: 'Cancel Rating', // advisory title for the 'cancel' link
cancelValue: '', // value to submit when user click the 'cancel' link
split: 0, // split the star into how many parts?
// Width of star image in case the plugin can't work it out. This can happen if
// the jQuery.dimensions plugin is not available OR the image is hidden at installation
starWidth: 16//,
//NB.: These don't need to be pre-defined (can be undefined/null) so let's save some code!
//half: false, // just a shortcut to control.split = 2
//required: false, // disables the 'cancel' button so user can only select one of the specified values
//readOnly: false, // disable rating plugin interaction/ values cannot be changed
//focus: function(){}, // executed when stars are focused
//blur: function(){}, // executed when stars are focused
//callback: function(){}, // executed when a star is clicked
}; //} });
/*--------------------------------------------------------*/
/*
### Default implementation ###
The plugin will attach itself to file inputs
with the class 'multi' when the page loads
*/
$(function(){
$('input[type=radio].star').rating();
});
/*# AVOID COLLISIONS #*/
})(jQuery);
/*# AVOID COLLISIONS #*/
+1
View File
@@ -0,0 +1 @@
if(window.jQuery){(function(a){if(a.browser.msie){try{document.execCommand("BackgroundImageCache",false,true)}catch(b){}}a.fn.rating=function(d){if(this.length==0){return this}if(typeof arguments[0]=="string"){if(this.length>1){var c=arguments;return this.each(function(){a.fn.rating.apply(a(this),c)})}a.fn.rating[arguments[0]].apply(this,a.makeArray(arguments).slice(1)||[]);return this}var d=a.extend({},a.fn.rating.options,d||{});a.fn.rating.calls++;this.not(".star-rating-applied").addClass("star-rating-applied").each(function(){var g,l=a(this);var e=(this.name||"unnamed-rating").replace(/\[|\]/g,"_").replace(/^\_+|\_+$/g,"");var f=a(this.form||document.body);var k=f.data("rating");if(!k||k.call!=a.fn.rating.calls){k={count:0,call:a.fn.rating.calls}}var n=k[e];if(n){g=n.data("rating")}if(n&&g){g.count++}else{g=a.extend({},d||{},(a.metadata?l.metadata():(a.meta?l.data():null))||{},{count:0,stars:[],inputs:[]});g.serial=k.count++;n=a('<span class="star-rating-control"/>');l.before(n);n.addClass("rating-to-be-drawn");if(l.attr("disabled")){g.readOnly=true}n.append(g.cancel=a('<div class="rating-cancel"><a title="'+g.cancel+'">'+g.cancelValue+"</a></div>").mouseover(function(){a(this).rating("drain");a(this).addClass("star-rating-hover")}).mouseout(function(){a(this).rating("draw");a(this).removeClass("star-rating-hover")}).click(function(){a(this).rating("select")}).data("rating",g))}var j=a('<div class="star-rating rater-'+g.serial+'"><a title="'+(this.title||this.value)+'">'+this.value+"</a></div>");n.append(j);if(this.id){j.attr("id",this.id)}if(this.className){j.addClass(this.className)}if(g.half){g.split=2}if(typeof g.split=="number"&&g.split>0){var i=(a.fn.width?j.width():0)||g.starWidth;var h=(g.count%g.split),m=Math.floor(i/g.split);j.width(m).find("a").css({"margin-left":"-"+(h*m)+"px"})}if(g.readOnly){j.addClass("star-rating-readonly")}else{j.addClass("star-rating-live").mouseover(function(){a(this).rating("fill");a(this).rating("focus")}).mouseout(function(){a(this).rating("draw");a(this).rating("blur")}).click(function(){a(this).rating("select")})}if(this.checked){g.current=j}l.hide();l.change(function(){a(this).rating("select")});j.data("rating.input",l.data("rating.star",j));g.stars[g.stars.length]=j[0];g.inputs[g.inputs.length]=l[0];g.rater=k[e]=n;g.context=f;l.data("rating",g);n.data("rating",g);j.data("rating",g);f.data("rating",k)});a(".rating-to-be-drawn").rating("draw").removeClass("rating-to-be-drawn");return this};a.extend(a.fn.rating,{calls:0,focus:function(){var d=this.data("rating");if(!d){return this}if(!d.focus){return this}var c=a(this).data("rating.input")||a(this.tagName=="INPUT"?this:null);if(d.focus){d.focus.apply(c[0],[c.val(),a("a",c.data("rating.star"))[0]])}},blur:function(){var d=this.data("rating");if(!d){return this}if(!d.blur){return this}var c=a(this).data("rating.input")||a(this.tagName=="INPUT"?this:null);if(d.blur){d.blur.apply(c[0],[c.val(),a("a",c.data("rating.star"))[0]])}},fill:function(){var c=this.data("rating");if(!c){return this}if(c.readOnly){return}this.rating("drain");this.prevAll().andSelf().filter(".rater-"+c.serial).addClass("star-rating-hover")},drain:function(){var c=this.data("rating");if(!c){return this}if(c.readOnly){return}c.rater.children().filter(".rater-"+c.serial).removeClass("star-rating-on").removeClass("star-rating-hover")},draw:function(){var c=this.data("rating");if(!c){return this}this.rating("drain");if(c.current){c.current.data("rating.input").attr("checked","checked");c.current.prevAll().andSelf().filter(".rater-"+c.serial).addClass("star-rating-on")}else{a(c.inputs).removeAttr("checked")}c.cancel[c.readOnly||c.required?"hide":"show"]();this.siblings()[c.readOnly?"addClass":"removeClass"]("star-rating-readonly")},select:function(d,f){var e=this.data("rating");if(!e){return this}if(e.readOnly){return}e.current=null;if(typeof d!="undefined"){if(typeof d=="number"){return a(e.stars[d]).rating("select",undefined,f)}if(typeof d=="string"){a.each(e.stars,function(){if(a(this).data("rating.input").val()==d){a(this).rating("select",undefined,f)}})}}else{e.current=this[0].tagName=="INPUT"?this.data("rating.star"):(this.is(".rater-"+e.serial)?this:null)}this.data("rating",e);this.rating("draw");var c=a(e.current?e.current.data("rating.input"):null);if((f||f==undefined)&&e.callback){e.callback.apply(c[0],[c.val(),a("a",e.current)[0]])}},readOnly:function(c,d){var e=this.data("rating");if(!e){return this}e.readOnly=c||c==undefined?true:false;if(d){a(e.inputs).attr("disabled","disabled")}else{a(e.inputs).removeAttr("disabled")}this.data("rating",e);this.rating("draw")},disable:function(){this.rating("readOnly",true,true)},enable:function(){this.rating("readOnly",false,false)}});a.fn.rating.options={cancel:"Cancel Rating",cancelValue:"",split:0,starWidth:16};a(function(){a("input[type=radio].star").rating()})})(jQuery)};
+124 -6
View File
@@ -32,7 +32,7 @@
<%def name="javascripts()">
${parent.javascripts()}
${h.js( "jquery", "jquery.tipsy", "galaxy.base", "json2", "class", "jquery.jstore", "jquery.autocomplete", "autocomplete_tagging", "trackster" )}
${h.js( "jquery", "jquery.tipsy", "galaxy.base", "json2", "class", "jquery.jstore", "jquery.autocomplete", "jquery.rating", "autocomplete_tagging", "trackster" )}
<script type="text/javascript">
@@ -47,6 +47,23 @@
self.location = href;
}
// Map item rating to number of stars to show.
function map_rating_to_num_stars(rating) {
if (rating <= 0)
return 0;
else if (rating > 0 && rating <= 1.5)
return 1;
else if (rating > 1.5 && rating <= 2.5)
return 2;
else if (rating > 2.5 && rating <= 3.5)
return 3;
else if (rating > 3.5 && rating <= 4.5)
return 4;
else if (rating > 4.5)
return 5;
}
// Init. on document load.
$(function() {
// Set links to Galaxy screencasts to open in overlay.
$(this).find("a[href^='http://screencast.g2.bx.psu.edu/']").each( function() {
@@ -63,15 +80,38 @@
return false;
});
});
// Init history boxes
// Init history boxes.
init_history_items( $("div.historyItemWrapper"), false, "nochanges" );
// Init user item rating.
$('.user_rating_star').rating({
callback: function(rating, link) {
$.ajax({
type: "GET",
url: "${h.url_for ( controller='/' + controller_name , action='rate_async' )}",
data: { id : "${trans.security.encode_id( item.id )}", rating : rating },
dataType: 'json',
error: function() { alert( "Rating submission failed" ); },
success: function( community_data ) {
$('#rating_feedback').show();
$('#num_ratings').text(Math.round(community_data[1]*10)/10);
$('#ave_rating').text(community_data[0]);
$('.community_rating_star').rating('readOnly', false);
$('.community_rating_star').rating('select', map_rating_to_num_stars(community_data[0])-1);
$('.community_rating_star').rating('readOnly', true);
}
});
},
required: true // Hide cancel button.
});
});
</script>
</%def>
<%def name="stylesheets()">
${parent.stylesheets()}
${h.css( "autocomplete_tagging", "embed_item", "trackster" )}
${h.css( "autocomplete_tagging", "embed_item", "trackster", "jquery.rating" )}
<style type="text/css">
.page-body {
@@ -211,13 +251,91 @@
<h4>Author</h4>
<p>${item.user.username | h}</p>
## Page meta.
## Related items.
<h4>Related ${item_plural}</h4>
<p>
<a href="${href_to_all_items}">All published ${item_plural.lower()}</a><br>
<a href="${href_to_user_items}">Published ${item_plural.lower()} by ${item.user.username | h}</a>
## Rating.
<h4>Rating</h4>
<%
label = "ratings"
if num_ratings == 1:
label = "rating"
%>
<div style="padding-bottom: 0.75em; float: left">
Community<br>
<span style="font-size:80%">
(<span id="num_ratings">${num_ratings}</span> ${label},
<span id="ave_rating">${"%.1f" % ave_item_rating}</span> average)
<span>
</div>
<div style="float: right">
<input name="star1" type="radio" class="community_rating_star star" disabled="disabled" value="1"
%if ave_item_rating > 0 and ave_item_rating <= 1.5:
checked="checked"
%endif
/>
<input name="star1" type="radio" class="community_rating_star star" disabled="disabled" value="2"
%if ave_item_rating > 1.5 and ave_item_rating <= 2.5:
checked="checked"
%endif
/>
<input name="star1" type="radio" class="community_rating_star star" disabled="disabled" value="3"
%if ave_item_rating > 2.5 and ave_item_rating <= 3.5:
checked="checked"
%endif
/>
<input name="star1" type="radio" class="community_rating_star star" disabled="disabled" value="4"
%if ave_item_rating > 3.5 and ave_item_rating <= 4.5:
checked="checked"
%endif
/>
<input name="star1" type="radio" class="community_rating_star star" disabled="disabled" value="5"
%if ave_item_rating > 4.5:
checked="checked"
%endif
/>
</div>
<div style="clear: both;"></div>
%if trans.get_user():
<div style="float: left">
Yours<br><span id="rating_feedback" style="font-size:80%; display: none">(thanks!)</span>
</div>
<div style="float: right">
<input name="star2" type="radio" class="user_rating_star" value="1"
%if user_item_rating == 1:
checked="checked"
%endif
/>
<input name="star2" type="radio" class="user_rating_star" value="2"
%if user_item_rating == 2:
checked="checked"
%endif
/>
<input name="star2" type="radio" class="user_rating_star" value="3"
%if user_item_rating == 3:
checked="checked"
%endif
/>
<input name="star2" type="radio" class="user_rating_star" value="4"
%if user_item_rating == 4:
checked="checked"
%endif
/>
<input name="star2" type="radio" class="user_rating_star" value="5"
%if user_item_rating == 5:
checked="checked"
%endif
/>
</div>
%endif
<div style="clear: both;"></div>
## Tags.
<h4>Tags</h4>
<p>