diff --git a/lib/galaxy/tags/tag_handler.py b/lib/galaxy/tags/tag_handler.py index 08f7c6d5821..59aee19e533 100644 --- a/lib/galaxy/tags/tag_handler.py +++ b/lib/galaxy/tags/tag_handler.py @@ -1,5 +1,9 @@ from galaxy.model import Tag import re +from sqlalchemy.sql.expression import func, and_ +from sqlalchemy.sql import select +from galaxy.model import History, HistoryTagAssociation, Dataset, DatasetTagAssociation, \ + HistoryDatasetAssociation, HistoryDatasetAssociationTagAssociation, Page, PageTagAssociation class TagHandler( object ): @@ -18,14 +22,56 @@ class TagHandler( object ): # Key-value separator. key_value_separators = "=:" - def __init__(self): - self.tag_assoc_classes = dict() + # Item-specific information needed to perform tagging. + class ItemTagAssocInfo( object ): + def __init__( self, item_class, tag_assoc_class, item_id_col ): + self.item_class = item_class + self.tag_assoc_class = tag_assoc_class + self.item_id_col = item_id_col + + # Initialize with known classes. + item_tag_assoc_info = {} + item_tag_assoc_info["History"] = ItemTagAssocInfo( History, HistoryTagAssociation, HistoryTagAssociation.table.c.history_id ) + item_tag_assoc_info["HistoryDatasetAssociation"] = \ + ItemTagAssocInfo( HistoryDatasetAssociation, HistoryDatasetAssociationTagAssociation, HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id ) + item_tag_assoc_info["Page"] = ItemTagAssocInfo( Page, PageTagAssociation, PageTagAssociation.table.c.page_id ) - def add_tag_assoc_class(self, entity_class, tag_assoc_class): - self.tag_assoc_classes[entity_class] = tag_assoc_class + def get_tag_assoc_class(self, item_class): + """ Returns tag association class for item class. """ + return self.item_tag_assoc_info[item_class.__name__].tag_assoc_class - def get_tag_assoc_class(self, entity_class): - return self.tag_assoc_classes[entity_class] + def get_id_col_in_item_tag_assoc_table( self, item_class): + """ Returns item id column in class' item-tag association table. """ + return self.item_tag_assoc_info[item_class.__name__].item_id_col + + def get_community_tags(self, sa_session, item=None, limit=None): + """ Returns community tags for an item. """ + + # Get item-tag association class. + item_class = item.__class__ + item_tag_assoc_class = self.get_tag_assoc_class( item_class ) + if not item_tag_assoc_class: + return [] + + # Build select statement. + cols_to_select = [ item_tag_assoc_class.table.c.tag_id, func.count('*') ] + from_obj = item_tag_assoc_class.table.join(item_class.table).join(Tag.table) + where_clause = ( self.get_id_col_in_item_tag_assoc_table(item_class) == item.id ) + order_by = [ func.count("*").desc() ] + group_by = item_tag_assoc_class.table.c.tag_id + + # Do query and get result set. + query = select(columns=cols_to_select, from_obj=from_obj, + whereclause=where_clause, group_by=group_by, order_by=order_by, limit=limit) + result_set = sa_session.execute(query) + + # Return community tags. + community_tags = [] + for row in result_set: + tag_id = row[0] + community_tags.append( self.get_tag_by_id( sa_session, tag_id ) ) + + return community_tags def remove_item_tag( self, trans, item, tag_name ): """Remove a tag from an item.""" @@ -45,7 +91,7 @@ class TagHandler( object ): """Delete tags from an item.""" # Delete item-tag associations. for tag in item.tags: - trans.sa_ession.delete( tag ) + trans.sa_session.delete( tag ) # Delete tags from item. del item.tags[:] @@ -89,7 +135,7 @@ class TagHandler( object ): continue # Create tag association based on item class. - item_tag_assoc_class = self.tag_assoc_classes[item.__class__] + item_tag_assoc_class = self.get_tag_assoc_class( item.__class__ ) item_tag_assoc = item_tag_assoc_class() # Add tag to association. diff --git a/lib/galaxy/web/controllers/tag.py b/lib/galaxy/web/controllers/tag.py index 8e057e76545..ce0ec1dbc63 100644 --- a/lib/galaxy/web/controllers/tag.py +++ b/lib/galaxy/web/controllers/tag.py @@ -13,19 +13,10 @@ class TagsController ( BaseController ): def __init__(self, app): BaseController.__init__(self, app) - - # Keep a list of taggable classes. - self.taggable_classes = dict() - self.taggable_classes[History.__name__] = History - self.taggable_classes[HistoryDatasetAssociation.__name__] = HistoryDatasetAssociation - self.taggable_classes[Page.__name__] = Page - + # Set up tag handler to recognize the following items: History, HistoryDatasetAssociation, Page, ... self.tag_handler = TagHandler() - self.tag_handler.add_tag_assoc_class(History, HistoryTagAssociation) - self.tag_handler.add_tag_assoc_class(HistoryDatasetAssociation, HistoryDatasetAssociationTagAssociation) - self.tag_handler.add_tag_assoc_class(Page, PageTagAssociation) - + @web.expose @web.require_login( "Add tag to an item." ) def add_tag_async( self, trans, id=None, item_class=None, new_tag=None, context=None ): @@ -91,6 +82,8 @@ class TagsController ( BaseController ): item_class = History elif item_class == 'HistoryDatasetAssociation': item_class = HistoryDatasetAssociation + elif item_class == 'Page': + item_class = Page q = q.encode('utf-8') if q.find(":") == -1: @@ -207,16 +200,16 @@ class TagsController ( BaseController ): def _get_column_for_filtering_item_by_user_id(self, item_class): """ Returns the column to use when filtering by user id. """ - # TODO: make this generic by using a dict() to map from item class to a "user id" column - if item_class is History: - return History.table.c.user_id - elif item_class is HistoryDatasetAssociation: + if item_class is HistoryDatasetAssociation: # Use the user_id associated with the HDA's history. return History.table.c.user_id + else: + # Generically, just use the user_id column of the tagged item's table. + return item_class.table.c.user_id def _get_item(self, trans, item_class_name, id): """ Get an item based on type and id. """ - item_class = self.taggable_classes[item_class_name] + item_class = self.tag_handler.item_tag_assoc_info[item_class_name].item_class item = trans.sa_session.query(item_class).filter("id=" + str(id))[0] return item; @@ -234,3 +227,6 @@ class TagsController ( BaseController ): elif isinstance(item, HistoryDatasetAssociation): # TODO. pass + elif isinstance(item, Page): + # TODO. + pass diff --git a/static/scripts/autocomplete_tagging.js b/static/scripts/autocomplete_tagging.js index ad5a19b5277..b1f61db8f99 100644 --- a/static/scripts/autocomplete_tagging.js +++ b/static/scripts/autocomplete_tagging.js @@ -3,7 +3,22 @@ * @author: Jeremy Goecks * @require: jquery.autocomplete plugin */ -jQuery.fn.autocomplete_tagging = function(elt_id, options) +// +// Initialize "tag click functions" for tags. +// +function init_tag_click_function(tag_elt, click_func) +{ + $(tag_elt).find('.tag-name').each( function() { + $(this).click( function() { + var tag_str = $(this).text(); + var tag_name_and_value = tag_str.split(":") + click_func(tag_name_and_value[0], tag_name_and_value[1]); + return true; + }); + }); +} + +jQuery.fn.autocomplete_tagging = function(options) { // @@ -61,21 +76,17 @@ jQuery.fn.autocomplete_tagging = function(elt_id, options) // Initalize object's elements. // - // Get elements for this object. - var this_obj = $('#' + elt_id); - var id_parts = $(this).attr('id').split("-"); - var obj_id = id_parts[ id_parts.length-1 ]; - var tag_area = this_obj.find('#tag-area-' + obj_id); - var toggle_link = this_obj.find('#toggle-link-' + obj_id); - var tag_input_field = this_obj.find('#tag-input'); + // Get elements for this object. For this_obj, assume the last element with the id is the "this"; this is somewhat of a hack to address the problem + // that there may be two tagging elements for a single item if there are both community and individual tags for an element. + var this_obj = $(this); + var tag_area = this_obj.find('.tag-area'); + var toggle_link = this_obj.find('.toggle-link'); + var tag_input_field = this_obj.find('.tag-input'); var add_tag_button = this_obj.find('.add-tag-button'); // Initialize toggle link. toggle_link.click( function() { - var id = $(this).attr('id').split('-')[2]; - // Take special actions depending on whether toggle is showing or hiding link. - var tag_area = $('#tag-area-' + id); var showing_tag_area = (tag_area.css("display") == "none"); var after_toggle_fn; if (showing_tag_area) @@ -210,7 +221,7 @@ jQuery.fn.autocomplete_tagging = function(elt_id, options) } var autocomplete_options = { selectFirst: false, formatItem : format_item_func, autoFill: false, highlight: false }; - tag_input_field.autocomplete(settings.ajax_autocomplete_tag_url, autocomplete_options); + tag_input_field.autocomplete(settings.ajax_autocomplete_tag_url, autocomplete_options); // Initialize delete tag images for current tags. @@ -218,16 +229,9 @@ jQuery.fn.autocomplete_tagging = function(elt_id, options) init_delete_tag_image( $(this) ); }); - this_obj.find('.tag-name').each( function() { - $(this).click( function() { - var tag_str = $(this).text(); - var tag_name_and_value = tag_str.split(":") - settings.tag_click_fn(tag_name_and_value[0], tag_name_and_value[1]); - return true; - }); - }); - + // Initialize tag click function. + init_tag_click_function($(this), settings.tag_click_fn); // Initialize "add tag" button. add_tag_button.click( function() diff --git a/templates/page/display.mako b/templates/page/display.mako index 5e44e9775e6..ac4de4ffbf6 100644 --- a/templates/page/display.mako +++ b/templates/page/display.mako @@ -146,29 +146,35 @@ }; // - // Function provides text for tagging toggle link. + // Handle click on community tag. // - var get_toggle_link_text = function(tags) + function community_tag_click(tag_name, tag_value) { - var text = ""; - var num_tags = array_length(tags); - if (num_tags != 0) - { - text = num_tags + (num_tags != 1 ? " Tags" : " Tag"); - } - else - { - // No tags. - text = "Add tags to history"; - } - return text; - }; + alert("community tag click: " + tag_name); + } <%def name="stylesheets()"> ${parent.stylesheets()} ${h.css( "base", "history", "autocomplete_tagging" )} + <%def name="init()"> @@ -180,6 +186,7 @@ %> +<%namespace file="../tagging_common.mako" import="render_tagging_element, render_community_tagging_element" /> <%def name="center_panel()"> @@ -191,9 +198,27 @@
-
- ${page.latest_revision.content.decode( "utf-8" )} -
+
+ ${page.latest_revision.content.decode( "utf-8" )} +
+
+
Tags
+

+ ## Community tags. +

+ Community: + ${render_community_tagging_element( tagged_item=page, tag_click_fn='community_tag_click', use_toggle_link=False )} + %if len ( page.tags ) == 0: + none + %endif +
+ ## User tags. +

+

+ Yours: + ${render_tagging_element( tagged_item=page, elt_context='display.mako', use_toggle_link=False )} +
+
diff --git a/templates/tagging_common.mako b/templates/tagging_common.mako index 42e2f702183..78dfefbed42 100644 --- a/templates/tagging_common.mako +++ b/templates/tagging_common.mako @@ -1,40 +1,59 @@ <%! from cgi import escape from galaxy.web.framework.helpers import iff + from random import random + from sys import maxint + from math import floor + from galaxy.tags.tag_handler import TagHandler + from galaxy.model import Tag, ItemTagAssociation + + tag_handler = TagHandler() %> + ## Render a tagging element if there is a tagged_item. %if tagged_item is not None: ${render_tagging_element(tagged_item=tagged_item, elt_context=elt_context, in_form=in_form, input_size=input_size, tag_click_fn=tag_click_fn)} %endif -## Render HTML for a tagging element. -<%def name="render_tagging_element_html(tagged_item=None, editable=True, use_toggle_link=True, input_size='15', in_form=False)"> +## Render HTML for a list of tags. +<%def name="render_tagging_element_html(elt_id=None, tags=None, editable=True, use_toggle_link=True, input_size='15', in_form=False)"> ## Useful attributes. <% - tagged_item_id = str( trans.security.encode_id (tagged_item.id) ) - elt_id = "tag-element-" + tagged_item_id + num_tags = len( tags ) %> -
- %if use_toggle_link: - ${len(tagged_item.tags)} Tags +
+ ## Do not display element if there are no tags and it is not editable. + %if num_tags == 0 and not editable: + style="display: none" + %endif + > + %if use_toggle_link: + ${num_tags} Tags + %endif +
## Build buttons for current tags. - %for tag in tagged_item.tags: + %for tag in tags: <% - tag_name = tag.user_tname - tag_value = None - if tag.value is not None: + ## Handle both Tag and ItemTagAssociation objects. + if isinstance( tag, Tag ): + tag_name = tag.name + tag_value = None + elif isinstance( tag, ItemTagAssociation ): + tag_name = tag.user_tname tag_value = tag.user_value ## Convert tag name, value to unicode. if isinstance( tag_name, str ): tag_name = unicode( escape( tag_name ), 'utf-8' ) if tag_value: tag_value = unicode( escape( tag_value ), 'utf-8' ) - tag_str = tag_name + ":" + tag_value - else: - tag_str = tag_name + if tag_value: + tag_str = tag_name + ":" + tag_value + else: + tag_str = tag_name %> ${tag_str} @@ -47,9 +66,9 @@ ## Add tag input field. If element is in form, tag input is a textarea; otherwise element is a input type=text. %if editable: %if in_form: - + %else: - + %endif ## Add "add tag" button. @@ -58,17 +77,32 @@
+## Render community tagging element. +<%def name="render_community_tagging_element(tagged_item=None, use_toggle_link=False, tag_click_fn='default_tag_click_fn')"> + ## Build HTML. + <% + elt_id = int ( floor ( random()*maxint ) ) + community_tags = tag_handler.get_community_tags(trans.sa_session, tagged_item, 10) + %> + ${self.render_tagging_element_html(elt_id=elt_id, tags=community_tags, use_toggle_link=use_toggle_link, editable=False)} + + ## Set up tag click function. + + + ## Render the tags 'tags' as an autocomplete element. <%def name="render_tagging_element(tagged_item=None, elt_context=None, use_toggle_link=True, in_form=False, input_size='15', tag_click_fn='default_tag_click_fn', get_toggle_link_text_fn='default_get_toggle_link_text_fn', editable=True)"> ## Useful attributes. <% tagged_item_id = str( trans.security.encode_id (tagged_item.id) ) - elt_id = "tag-element-" + tagged_item_id + elt_id = int ( floor ( random()*maxint ) ) %> ## Build HTML. - ${self.render_tagging_element_html(tagged_item, editable, use_toggle_link, input_size, in_form)} + ${self.render_tagging_element_html(elt_id, tagged_item.tags, editable, use_toggle_link, input_size, in_form)} ## Build script that augments tags using progressive javascript. ## Use style to hide/display the tag area.