mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
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:
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Galaxy item attributes, such as tags, ratings, and annotations.
|
||||
"""
|
||||
@@ -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 )
|
||||
@@ -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
|
||||
|
||||
@@ -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 ) )
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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" )
|
||||
|
||||
@@ -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. """
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 752 B |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 815 B |
+12
@@ -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 */
|
||||
Executable
+383
@@ -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 #*/
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user