From 71b69217cd03e0cb4a971bf28216982761308357 Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 28 Feb 2011 04:14:12 -0500 Subject: finished the wildcard tag feature --- askbot/management/commands/send_email_alerts.py | 26 ++++- askbot/models/tag.py | 26 ++--- askbot/skins/default/media/js/live_search.js | 11 +- askbot/skins/default/media/js/tag_selector.js | 113 ++++++++++++++------- askbot/skins/default/media/js/utils.js | 44 ++++++-- askbot/skins/default/media/style/style.css | 9 +- .../default/templates/blocks/bottom_scripts.html | 1 + askbot/skins/default/templates/macros.html | 8 +- askbot/urls.py | 5 + askbot/views/commands.py | 23 ++++- 10 files changed, 183 insertions(+), 83 deletions(-) diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py index cf4ff16a..4a5b6d6c 100644 --- a/askbot/management/commands/send_email_alerts.py +++ b/askbot/management/commands/send_email_alerts.py @@ -251,16 +251,34 @@ class Command(NoArgsCommand): user_selections__user=user ) - q_all_A = Q_set_A.exclude( tags__in=ignored_tags ) + wk = user.ignored_tags.strip().split() + ignored_by_wildcards = Tag.objects.get_by_wildcards(wk) - q_all_B = Q_set_B.exclude( tags__in=ignored_tags ) + q_all_A = Q_set_A.exclude( + tags__in = ignored_tags + ).exclude( + tags__in = ignored_by_wildcards + ) + + q_all_B = Q_set_B.exclude( + tags__in = ignored_tags + ).exclude( + tags__in = ignored_by_wildcards + ) else: selected_tags = Tag.objects.filter( user_selections__reason='good', user_selections__user=user ) - q_all_A = Q_set_A.filter( tags__in=selected_tags ) - q_all_B = Q_set_B.filter( tags__in=selected_tags ) + + wk = user.interesting_tags.strip().split() + selected_by_wildcards = Tag.objects.get_by_wildcards(wk) + + tag_filter = Q(tags__in = list(selected_tags)) \ + | Q(tags__in = list(selected_by_wildcards)) + + q_all_A = Q_set_A.filter( tag_filter ) + q_all_B = Q_set_B.filter( tag_filter ) q_all_A = q_all_A[:askbot_settings.MAX_ALERTS_PER_EMAIL] q_all_B = q_all_B[:askbot_settings.MAX_ALERTS_PER_EMAIL] diff --git a/askbot/models/tag.py b/askbot/models/tag.py index 8f4e1a00..e94b8452 100644 --- a/askbot/models/tag.py +++ b/askbot/models/tag.py @@ -30,27 +30,19 @@ class TagManager(models.Manager): transaction.commit_unless_managed() - def get_matching_wildcards( - self, - wildcards = None - ): - """returns query set of tags that match the list of the - wildcard tag names, wildcards is the list of wildcard tag - names - each is guaranteed to end with an asterisk and has + def get_by_wildcards(self, wildcards = None): + """returns query set of tags that match the wildcard tags + wildcard tag is guaranteed to end with an asterisk and has at least one character preceding the the asterisk. and there is only one asterisk in the entire name """ - if len(wildcards) == 0: + if wildcards is None: return self.none() - - #initiate the Q object with name__startswith filter - #on the wildcard need to remove the trailing * - the_filter = models.Q(name__startswith = wildcards.pop()[:-1]) - #in the loop build the Q object - for wildcard in wildcards: - the_filter = the_filter | models.Q(name__startswith = wildcard[:-1]) - #execute the query and return - return self.filter(the_filter) + first_tag = wildcards.pop() + tag_filter = models.Q(name__startswith = first_tag[:-1]) + for next_tag in wildcards: + tag_filter |= models.Q(name__startswith = next_tag[:-1]) + return self.filter(tag_filter) def get_related_to_search( self, diff --git a/askbot/skins/default/media/js/live_search.js b/askbot/skins/default/media/js/live_search.js index 3c7512f2..04efa8b5 100644 --- a/askbot/skins/default/media/js/live_search.js +++ b/askbot/skins/default/media/js/live_search.js @@ -294,19 +294,18 @@ $(document).ready(function(){ var search_tags = $('#search-tags'); search_tags.children().remove(); var tags_html = ''; - $.each(tags, function(idx, tag){ + $.each(tags, function(idx, tag_name){ var tag = new Tag(); - tag.setName(tag); + tag.setName(tag_name); tag.setDeletable(true); tag.setLinkable(false); tag.setDeleteHandler( function(){ - remove_search_tag(search_tag); + remove_search_tag(tag_name); } ); - tags_html += tag.getElement().outerHTML(); + search_tags.append(tag.getElement()); }); - search_tags.html(tags_html); }; var create_relevance_tab = function(){ @@ -378,7 +377,7 @@ $(document).ready(function(){ var search_tags = $('#search-tags .tag-left'); $.each(search_tags, function(idx, element){ var tag = new Tag(); - tag.decorate(element); + tag.decorate($(element)); //todo: setDeleteHandler and setHandler //must work after decorate & must have getName tag.setDeleteHandler( diff --git a/askbot/skins/default/media/js/tag_selector.js b/askbot/skins/default/media/js/tag_selector.js index 954c7ab2..9ee8a6e7 100644 --- a/askbot/skins/default/media/js/tag_selector.js +++ b/askbot/skins/default/media/js/tag_selector.js @@ -8,28 +8,41 @@ var TagDetailBox = function(box_type){ }; inherits(TagDetailBox, WrappedElement); -TagDetailBox.prototype.belongs_to = function(wildcard){ +TagDetailBox.prototype.createDom = function(){ + this._element = this.makeElement('div'); + this._element.addClass('wildcard-tags'); + this._headline = this.makeElement('p'); + this._element.append(this._headline); + this._headline.html($.i18n._('Tag "" matches:')); + this._tag_list_element = this.makeElement('ul'); + this._tag_list_element.addClass('tags'); + this._element.append(this._tag_list_element); +} + +TagDetailBox.prototype.belongsTo = function(wildcard){ return (this.wildcard === wildcard); }; -TagDetailBox.prototype.is_blank = function(){ +TagDetailBox.prototype.isBlank = function(){ return this._is_blank; }; TagDetailBox.prototype.clear = function(){ - if (this.is_blank()){ + if (this.isBlank()){ return; } this._is_blank = true; + this.getElement().hide(); + this.wildcard = null; $.each(this._tags, function(idx, item){ item.dispose(); }); this._tags = new Array(); }; -TagDetailBox.prototype.load_tags = function(wildcard, callback){ +TagDetailBox.prototype.loadTags = function(wildcard, callback){ $.ajax({ - type: 'POST', + type: 'GET', dataType: 'json', cache: false, url: askbot['urls']['get_tags_by_wildcard'], @@ -38,18 +51,27 @@ TagDetailBox.prototype.load_tags = function(wildcard, callback){ }); }; -TagDetailBox.prototype.render_for = function(wildcard){ +TagDetailBox.prototype.renderFor = function(wildcard){ var me = this; - this.load_tags( + this.loadTags( wildcard, function(data, text_status, xhr){ me._tag_names = data['tag_names']; - $.each(me._tag_names, function(idx, name){ - var tag = new Tag(); - tag.setName(name); - me._tags.push(tag); - me._element.append(tag.getElement()); - }); + if (data['tag_count'] > 0){ + me._element.show(); + me._headline.find('span').html(wildcard.replace(/\*$/, '✽')); + $.each(me._tag_names, function(idx, name){ + var tag = new Tag(); + tag.setName(name); + tag.setLinkable(false); + me._tags.push(tag); + me._tag_list_element.append(tag.getElement()); + }); + me._is_blank = false; + me.wildcard = wildcard; + } else { + me.clear(); + } } ); } @@ -101,12 +123,6 @@ function pickedTags(){ } }; - var setupTagDeleteEvents = function(obj,tag_store,tagname,reason,send_ajax){ - obj.click( function(){ - unpickTag(tag_store,tagname,reason,send_ajax); - }); - }; - var getTagList = function(reason){ var base_selector = '.marked-tags'; if (reason === 'good'){ @@ -119,22 +135,22 @@ function pickedTags(){ var getWildcardTagDetailBox = function(reason){ if (reason === 'good'){ - var tag_box = interestingTagDetailBox; + return interestingTagDetailBox; } else { - var tag_box = ignoredTagDetailBox; + return ignoredTagDetailBox; } }; var handleWildcardTagClick = function(tag_name, reason){ var detail_box = getWildcardTagDetailBox(reason); var tag_box = getTagList(reason); - if (detail_box.is_blank()){ - detail_box.render_for(tag_name); - } else if (detail_box.belongs_to(tag_name)){ + if (detail_box.isBlank()){ + detail_box.renderFor(tag_name); + } else if (detail_box.belongsTo(tag_name)){ detail_box.clear();//toggle off } else { detail_box.clear();//redraw with new data - detail_box.render_for(tag_name); + detail_box.renderFor(tag_name); } if (!detail_box.inDocument()){ tag_box.after(detail_box.getElement()); @@ -155,14 +171,26 @@ function pickedTags(){ if (/\*$/.test(tag_name)){ tag.setLinkable(false); + var detail_box = getWildcardTagDetailBox(reason); tag.setHandler(function(){ handleWildcardTagClick(tag_name, reason); + if (detail_box.belongsTo(tag_name)){ + detail_box.clear(); + } }); + var delete_handler = function(){ + unpickTag(to_target, tag_name, reason, true); + if (detail_box.belongsTo(tag_name)){ + detail_box.clear(); + } + } + } else { + var delete_handler = function(){ + unpickTag(to_target, tag_name, reason, true); + } } - tag.setDeleteHandler(function(){ - unpickTag(to_target, tag_name, reason, true); - }); - + + tag.setDeleteHandler(delete_handler); var tag_element = tag.getElement(); to_tag_container.append(tag_element); to_target[tag_name] = tag_element; @@ -230,17 +258,24 @@ function pickedTags(){ else { return; } - $('.' + section + '.tags.marked-tags a.tag').each( + $('.' + section + '.tags.marked-tags .tag-left').each( function(i,item){ - var tag_name = $(item).html().replace('\u273d','*'); - tag_store[tag_name] = $(item).parent(); - setupTagDeleteEvents( - $(item).parent().find('.delete-icon'), - tag_store, - tag_name, - reason, - true - ); + var tag = new Tag(); + tag.decorate($(item)); + tag.setDeleteHandler(function(){ + unpickTag( + tag_store, + tag.getName(), + reason, + true + ) + }); + if (tag.isWildcard()){ + tag.setHandler(function(){ + handleWildcardTagClick(tag.getName(), reason) + }); + } + tag_store[tag.getName()] = $(item); } ); }; diff --git a/askbot/skins/default/media/js/utils.js b/askbot/skins/default/media/js/utils.js index 9c584606..2d59abd2 100644 --- a/askbot/skins/default/media/js/utils.js +++ b/askbot/skins/default/media/js/utils.js @@ -197,7 +197,9 @@ DeleteIcon.prototype.decorate = function(element){ this._element = element; this._element.attr('class', 'delete-icon'); this._element.attr('title', this._title); - this.setHandlerInternal(); + if (this._handler !== null){ + this.setHandlerInternal(); + } }; DeleteIcon.prototype.setHandlerInternal = function(){ @@ -210,12 +212,13 @@ DeleteIcon.prototype.createDom = function(){ }; var Tag = function(){ - WrappedElement.call(this); + SimpleControl.call(this); this._deletable = false; this._delete_handler = null; this._delete_icon_title = null; this._tag_title = null; this._name = null; + this._url_params = null; this._inner_html_tag = 'a'; this._html_tag = 'li'; } @@ -225,6 +228,10 @@ Tag.prototype.setName = function(name){ this._name = name; }; +Tag.prototype.getName = function(){ + return this._name; +}; + Tag.prototype.setHtmlTag = function(html_tag){ this._html_tag = html_tag; }; @@ -245,6 +252,14 @@ Tag.prototype.isLinkable = function(){ return (this._inner_html_tag === 'a'); }; +Tag.prototype.isDeletable = function(){ + return this._deletable; +}; + +Tag.prototype.isWildcard = function(){ + return (this.getName().substr(-1) === '*'); +}; + Tag.prototype.setUrlParams = function(url_params){ this._url_params = url_params; }; @@ -271,15 +286,18 @@ Tag.prototype.setDeleteIconTitle = function(title){ Tag.prototype.decorate = function(element){ this._element = element; - if (this._deletable === true){ + var del = element.find('.delete-icon'); + if (del.length === 1){ + this.setDeletable(true); this._delete_icon = new DeleteIcon(); if (this._delete_icon_title != null){ this._delete_icon.setTitle(this._delete_icon_title); } - this._delete_icon.setDeleteHandler(this.getDeleteHandler()); - DeleteIcon.decorate(this._element.find('.delete-icon')); + //do not set the delete handler here + this._delete_icon.decorate(del); } this._inner_element = this._element.find('.tag'); + this._name = this.decodeTagName($.trim(this._inner_element.html())); if (this._title !== null){ this._inner_element.attr('title', this._title); } @@ -290,7 +308,11 @@ Tag.prototype.decorate = function(element){ Tag.prototype.getDisplayTagName = function(){ //replaces the trailing * symbol with the unicode asterisk - return /\*$/.replace(this._name, '✽'); + return this._name.replace(/\*$/, '✽'); +}; + +Tag.prototype.decodeTagName = function(encoded_name){ + return encoded_name.replace('\u273d', '*'); }; Tag.prototype.createDom = function(){ @@ -305,7 +327,7 @@ Tag.prototype.createDom = function(){ this._inner_element = this.makeElement(this._inner_html_tag); if (this.isLinkable()){ var url = askbot['urls']['questions']; - url += '?tag=' + escape(this._name); + url += '?tags=' + escape(this.getName()); if (this._url_params !== null){ url += escape('&' + this._url_params); } @@ -319,7 +341,7 @@ Tag.prototype.createDom = function(){ "see questions tagged '{tag}'" ).replace( '{tag}', - tag_name + this.getName() ) ); } @@ -328,8 +350,12 @@ Tag.prototype.createDom = function(){ this._element.append(this._inner_element); + if (!this.isLinkable() && this._handler !== null){ + this.setHandlerInternal(); + } + if (this._deletable){ - this._delete_icon = DeleteIcon(); + this._delete_icon = new DeleteIcon(); this._delete_icon.setHandler(this.getDeleteHandler()); if (this._delete_icon_title !== null){ this._delete_icon.setTitle(this._delete_icon_title); diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css index 77e5b0c2..bcaad532 100755 --- a/askbot/skins/default/media/style/style.css +++ b/askbot/skins/default/media/style/style.css @@ -341,6 +341,7 @@ blockquote { askbot/models/__init__.py:format_instant_notification_email() */ ul.tags, +.boxC ul.tags, ul.tags.marked-tags, ul#related-tags { list-style: none; @@ -357,7 +358,12 @@ ul.tags li { padding: 0; } -ul.tags.marked-tags li { +.wildcard-tags { + clear: both; +} + +ul.tags.marked-tags li, +.wildcard-tags ul.tags li { margin-bottom: 5px; } @@ -381,6 +387,7 @@ ul#related-tags li { .tag-left { background: url(../images/tag-right.png) no-repeat right center; border: none; + cursor: pointer; display: block; float: left; height: 18px; diff --git a/askbot/skins/default/templates/blocks/bottom_scripts.html b/askbot/skins/default/templates/blocks/bottom_scripts.html index 3bbedc3d..4d3b8afe 100644 --- a/askbot/skins/default/templates/blocks/bottom_scripts.html +++ b/askbot/skins/default/templates/blocks/bottom_scripts.html @@ -18,6 +18,7 @@ askbot['data']['userIsAuthenticated'] = false; {% endif %} askbot['urls']['mark_read_message'] = '{% url "read_message" %}'; + askbot['urls']['get_tags_by_wildcard'] = '{% url "get_tags_by_wildcard" %}';