summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandrei <andrei@u15356270.onlinehome-server.com>2011-02-28 04:14:12 -0500
committerandrei <andrei@u15356270.onlinehome-server.com>2011-02-28 04:14:12 -0500
commit71b69217cd03e0cb4a971bf28216982761308357 (patch)
treeb5ff72719f8d6bd0bf353e43b02ffc15ebd7a7db
parentdcc354593940b63f15737a646cb676788298c493 (diff)
downloadaskbot-71b69217cd03e0cb4a971bf28216982761308357.tar.gz
askbot-71b69217cd03e0cb4a971bf28216982761308357.tar.bz2
askbot-71b69217cd03e0cb4a971bf28216982761308357.zip
finished the wildcard tag feature
-rw-r--r--askbot/management/commands/send_email_alerts.py26
-rw-r--r--askbot/models/tag.py26
-rw-r--r--askbot/skins/default/media/js/live_search.js11
-rw-r--r--askbot/skins/default/media/js/tag_selector.js113
-rw-r--r--askbot/skins/default/media/js/utils.js44
-rwxr-xr-xaskbot/skins/default/media/style/style.css9
-rw-r--r--askbot/skins/default/templates/blocks/bottom_scripts.html1
-rw-r--r--askbot/skins/default/templates/macros.html8
-rw-r--r--askbot/urls.py5
-rw-r--r--askbot/views/commands.py23
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 "<span></span>" 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(/\*$/, '&#10045;'));
+ $.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, '&#10045;');
+ return this._name.replace(/\*$/, '&#10045;');
+};
+
+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" %}';
</script>
<script
type="text/javascript"
diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html
index 0d894c44..d82feb2f 100644
--- a/askbot/skins/default/templates/macros.html
+++ b/askbot/skins/default/templates/macros.html
@@ -289,6 +289,7 @@ poor design of the data or methods on data objects #}
deletable = False,
is_link = True,
delete_link_title = None,
+ css_class = None,
url_params = None,
html_tag = 'div',
extra_content = ''
@@ -296,8 +297,8 @@ poor design of the data or methods on data objects #}
-%}
{% spaceless %}
<{{ html_tag }} class="tag-left{% if deletable %} deletable-tag{% endif %}">
- <{{ if_else(is_link, 'a', 'span') }}
- class="tag tag-right"
+ <{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %}
+ class="tag tag-right{% if css_class %} {{ css_class }}{% endif %}"
href="{% url questions %}?tags={{tag|urlencode}}{{
if_else(
url_params != None,
@@ -305,7 +306,8 @@ poor design of the data or methods on data objects #}
''
)|escape
}}"
- title="{% trans %}see questions tagged '{{ tag }}'{% endtrans %}" rel="tag">{{ tag|replace('*', '&#10045;') }}</{{ if_else(is_link, 'a', 'span') }}>
+ title="{% trans %}see questions tagged '{{ tag }}'{% endtrans %}" rel="tag"
+ >{{ tag|replace('*', '&#10045;') }}</{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %}>
{% if deletable %}
<span class="delete-icon"
{% if delete_link_title %}
diff --git a/askbot/urls.py b/askbot/urls.py
index e721d81b..56841cdc 100644
--- a/askbot/urls.py
+++ b/askbot/urls.py
@@ -157,6 +157,11 @@ urlpatterns = patterns('',
name='unmark_tag'
),
url(
+ 'get-tags-by-wildcard/',
+ views.commands.get_tags_by_wildcard,
+ name = 'get_tags_by_wildcard'
+ ),
+ url(
r'^%s$' % _('users/'),
views.users.users,
name='users'
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index d255b08b..c0637a71 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -15,7 +15,7 @@ from askbot import models
from askbot import forms
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
-from askbot.utils.decorators import ajax_only, ajax_login_required
+from askbot.utils import decorators
from askbot.skins.loaders import render_into_skin
from askbot import const
import logging
@@ -327,7 +327,7 @@ def vote(request, id):
return HttpResponse(data, mimetype="application/json")
#internally grouped views - used by the tagging system
-@ajax_login_required
+@decorators.ajax_login_required
def mark_tag(request, **kwargs):#tagging system
action = kwargs['action']
post_data = simplejson.loads(request.raw_post_data)
@@ -391,7 +391,22 @@ def mark_tag(request, **kwargs):#tagging system
return HttpResponse(simplejson.dumps(tag_usage_counts), mimetype="application/json")
-@ajax_login_required
+#@decorators.ajax_only
+@decorators.get_only
+def get_tags_by_wildcard(request):
+ """returns an json encoded array of tag names
+ in the response to a wildcard tag name
+ """
+ matching_tags = models.Tag.objects.get_by_wildcards(
+ [request.GET['wildcard'],]
+ )
+ count = matching_tags.count()
+ names = matching_tags.values_list('name', flat = True)[:10]
+ re_data = simplejson.dumps({'tag_count': count, 'tag_names': list(names)})
+ return HttpResponse(re_data, mimetype = 'application/json')
+
+
+@decorators.ajax_login_required
def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering system
if request.user.hide_ignored_questions:
new_hide_setting = False
@@ -400,7 +415,7 @@ def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering syste
request.user.hide_ignored_questions = new_hide_setting
request.user.save()
-@ajax_only
+@decorators.ajax_only
def ajax_command(request):
"""view processing ajax commands - note "vote" and view others do it too
"""