diff options
27 files changed, 273 insertions, 102 deletions
diff --git a/askbot/context.py b/askbot/context.py index 48c1cf3b..5e53febc 100644 --- a/askbot/context.py +++ b/askbot/context.py @@ -59,21 +59,33 @@ def application_settings(request): } if askbot_settings.GROUPS_ENABLED: - + #calculate context needed to list all the groups def _get_group_url(group): + """calculates url to the group based on its id and name""" group_slug = slugify(group['name']) - return reverse('users_by_group', - kwargs={'group_id': group['id'], - 'group_slug': group_slug}) - + return reverse( + 'users_by_group', + kwargs={'group_id': group['id'], 'group_slug': group_slug} + ) + #load id's and names of all groups global_group = models.tag.get_global_group() - groups = models.Group.objects.exclude_personal().exclude(id=global_group.id).order_by('name') - groups = groups.values('id', 'name') - group_list = [{'link': _get_group_url({'name': global_group.name, - 'id': global_group.id}), - 'name': global_group.name},] - for group in groups: + groups = models.Group.objects.exclude_personal() + groups = groups.exclude(id=global_group.id) + groups_data = list(groups.values('id', 'name')) + + #sort groups_data alphanumerically, but case-insensitive + groups_data = sorted( + groups_data, + lambda x, y: cmp(x['name'].lower(), y['name'].lower()) + ) + + #insert data for the global group at the first position + groups_data.insert(0, {'id': global_group.id, 'name': global_group.name}) + + #build group_list for the context + group_list = list() + for group in groups_data: link = _get_group_url(group) group_list.append({'name': group['name'], 'link': link}) context['group_list'] = simplejson.dumps(group_list) diff --git a/askbot/forms.py b/askbot/forms.py index 8548683f..0659e338 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -365,6 +365,10 @@ class TagNamesField(forms.CharField): attrs={'size': 50, 'autocomplete': 'off'} ) self.max_length = 255 + self.error_messages['max_length'] = _( + 'We ran out of space for recording the tags. ' + 'Please shorten or delete some of them.' + ) self.label = _('tags') self.help_text = ungettext_lazy( 'Tags are short keywords, with no spaces within. ' @@ -412,6 +416,11 @@ class TagNamesField(forms.CharField): if cleaned_tag not in cleaned_entered_tags: cleaned_entered_tags.append(clean_tag(tag)) + result = u' '.join(cleaned_entered_tags) + + if len(result) > 125:#magic number!, the same as max_length in db + raise forms.ValidationError(self.error_messages['max_length']) + return u' '.join(cleaned_entered_tags) diff --git a/askbot/media/js/editor.js b/askbot/media/js/editor.js index c6f1c873..25fdaa7e 100644 --- a/askbot/media/js/editor.js +++ b/askbot/media/js/editor.js @@ -69,7 +69,6 @@ function ajaxFileUpload(options) { $('#' + uploadInputId).unbind('change').change(startUploadHandler); }, error: function (data, status, e) { - alert(e); if (startUploadHandler){ /* re-install this as the upload extension * will remove the handler to prevent double uploading */ diff --git a/askbot/media/js/post.js b/askbot/media/js/post.js index 4c0dfa67..483973ec 100644 --- a/askbot/media/js/post.js +++ b/askbot/media/js/post.js @@ -1241,7 +1241,6 @@ var questionRetagger = function(){ var div = $('<form method="post"></form>'); tagInput = $('<input id="retag_tags" type="text" autocomplete="off" name="tags" size="30"/>'); //var tagLabel = $('<label for="retag_tags" class="error"></label>'); - tagInput.val(old_tags_string); //populate input var tagAc = new AutoCompleter({ url: askbot['urls']['get_tag_list'], @@ -1253,6 +1252,7 @@ var questionRetagger = function(){ delay: 10 }); tagAc.decorate(tagInput); + tagInput.val(old_tags_string); div.append(tagInput); //div.append(tagLabel); setupInputEventHandlers(tagInput); diff --git a/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js b/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js index b1d9a918..717a4716 100644 --- a/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js +++ b/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js @@ -10,15 +10,21 @@ (function() {
var insertIntoDom = function(url, description) {
- var sel = tinyMCE.activeEditor.selection;
-
var content = '<a href="' + url;
if (description) {
content = content + '" title="' + description;
}
- content = content + '">see attachment</a>';
+ content = content + '"/>';
- sel.setContent(content);
+ tinyMCE.activeEditor.focus();
+ if (document.selection) {
+ //this branch is a work around the IE's "this" quirk
+ var sel = document.selection.createRange();
+ sel.pasteHTML(content);
+ } else {
+ var sel = tinyMCE.activeEditor.selection;
+ sel.setContent(content);
+ }
};
var modalMenuHeadline = gettext('Insert a file');
diff --git a/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js b/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js index f23d89db..7fa6b6be 100644 --- a/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js +++ b/askbot/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js @@ -10,15 +10,21 @@ (function() {
var insertIntoDom = function(url, description) {
- var sel = tinyMCE.activeEditor.selection;
-
var content = '<img src="' + url;
if (description) {
content = content + '" alt="' + description;
}
content = content + '"/>';
- sel.setContent(content);
+ tinyMCE.activeEditor.focus();
+ if (document.selection) {
+ //this branch is a work around the IE "this" quirk
+ var sel = document.selection.createRange();
+ sel.pasteHTML(content);
+ } else {
+ var sel = tinyMCE.activeEditor.selection;
+ sel.setContent(content);
+ }
};
var modalMenuHeadline = gettext('Upload an image');
diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js index f34ee876..f6b46de5 100644 --- a/askbot/media/js/utils.js +++ b/askbot/media/js/utils.js @@ -1712,14 +1712,37 @@ GroupDropdown.prototype.decorate = function(element){ } }; -GroupDropdown.prototype.prependGroup = function(group_name, url){ - new_group_li = this.makeElement('li'); - new_group_a = this.makeElement('a'); - new_group_a.attr('href', url); - new_group_a.attr('class', 'group-name'); - new_group_a.text(group_name); - new_group_li.append(new_group_a); - this._element.prepend(new_group_li); +GroupDropdown.prototype.insertGroup = function(group_name, url){ + var new_group_li = this.makeElement('li'); + new_group_a = this.makeElement('a'); + new_group_a.attr('href', url); + new_group_a.attr('class', 'group-name'); + new_group_a.text(group_name); + new_group_li.append(new_group_a); + links_array = this._element.find('a') + for (i=1; i < links_array.length; i++){ + var listedName = links_array[i].text; + var cleanedListedName = listedName.toLowerCase(); + var cleanedNewName = group_name.toLowerCase() + if (listedName < newName) { + if (i == links_array.length - 1){ + new_group_li.insertAfter(this._element.find('li')[i-1]) + break; + } else { + continue; + } + } else if (cleanedNewName === cleanedNewName) { + var message = interpolate(gettext( + 'Group %(name)s already exists. Group names are case-insensitive.' + ), {'name': listedName}, true + ); + notify.show(message); + return; + } else { + new_group_li.insertAfter(this._element.find('li')[i-1]) + break; + } + } }; GroupDropdown.prototype._add_group_handler = function(group_name){ @@ -1735,7 +1758,7 @@ GroupDropdown.prototype._add_group_handler = function(group_name){ data: {group: group_name}, success: function(data){ if (data.success){ - self.prependGroup(data.group_name, data.url); + self.insertGroup(data.group_name, data.url); self._input_box_element.hide(); self._add_link.show(); return true; diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 2044dc16..d8f30503 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -1754,7 +1754,7 @@ def user_create_post_reject_reason( added_at = timestamp, text = details ) - details.parse_and_save(author = self) + details.parse_and_save(author=self) details.add_revision( author = self, revised_at = timestamp, @@ -2942,6 +2942,7 @@ def format_instant_notification_email( user_action = user_action % { 'user': '<a href="%s">%s</a>' % (user_url, from_user.username), 'post_link': '<a href="%s">%s</a>' % (post_url, _(post.post_type)) + #'post_link': '%s <a href="%s">>>></a>' % (_(post.post_type), post_url) } can_reply = to_user.can_post_by_email() @@ -2959,6 +2960,10 @@ def format_instant_notification_email( reply_separator += '<p>' + \ const.REPLY_WITH_COMMENT_TEMPLATE % data reply_separator += '</p>' + else: + reply_separator = '<p>%s</p>' % reply_separator + + reply_separator += user_action else: reply_separator = user_action diff --git a/askbot/models/post.py b/askbot/models/post.py index 49220a63..57847088 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -225,7 +225,18 @@ class PostManager(BaseQuerySetManager): is_private = is_private or \ (thread and thread.requires_response_moderation(author)) - post.parse_and_save(author=author, is_private=is_private) + parse_results = post.parse_and_save(author=author, is_private=is_private) + + from askbot.models import signals + signals.post_updated.send( + post=post, + updated_by=author, + newly_mentioned_users=parse_results['newly_mentioned_users'], + timestamp=added_at, + created=True, + diff=parse_results['diff'], + sender=post.__class__ + ) post.add_revision( author = author, @@ -529,19 +540,6 @@ class Post(models.Model): timestamp = self.get_time_of_last_edit() - #todo: this is handled in signal because models for posts - #are too spread out - from askbot.models import signals - signals.post_updated.send( - post = self, - updated_by = author, - newly_mentioned_users = newly_mentioned_users, - timestamp = timestamp, - created = created, - diff = diff, - sender = self.__class__ - ) - try: from askbot.conf import settings as askbot_settings if askbot_settings.GOOGLE_SITEMAP_CODE != '': @@ -549,6 +547,8 @@ class Post(models.Model): except Exception: logging.debug('cannot ping google - did you register with them?') + return {'diff': diff, 'newly_mentioned_users': newly_mentioned_users} + def is_question(self): return self.post_type == 'question' @@ -840,15 +840,12 @@ class Post(models.Model): if quote_level > 0, the post will be indented that number of times todo: move to views? """ - from askbot.templatetags.extra_filters_jinja \ - import absolutize_urls_func from askbot.skins.loaders import get_template from django.template import Context template = get_template('email/quoted_post.html') data = { 'post': self, 'quote_level': quote_level, - #'html': absolutize_urls_func(self.html), 'is_leaf_post': is_leaf_post, 'format': format } @@ -1629,7 +1626,19 @@ class Post(models.Model): by_email = by_email ) - self.parse_and_save(author=edited_by, is_private=is_private) + parse_results = self.parse_and_save(author=edited_by, is_private=is_private) + + from askbot.models import signals + signals.post_updated.send( + post=self, + updated_by=edited_by, + newly_mentioned_users=parse_results['newly_mentioned_users'], + timestamp=edited_at, + created=False, + diff=parse_results['diff'], + sender=self.__class__ + ) + def _answer__apply_edit( self, @@ -1947,11 +1956,11 @@ class PostRevision(models.Model): ) post = models.ForeignKey('askbot.Post', related_name='revisions', null=True, blank=True) - revision = models.PositiveIntegerField() - author = models.ForeignKey('auth.User', related_name='%(class)ss') + revision = models.PositiveIntegerField() + author = models.ForeignKey('auth.User', related_name='%(class)ss') revised_at = models.DateTimeField() - summary = models.CharField(max_length=300, blank=True) - text = models.TextField() + summary = models.CharField(max_length=300, blank=True) + text = models.TextField() approved = models.BooleanField(default=False, db_index=True) approved_by = models.ForeignKey(User, null = True, blank = True) @@ -1961,8 +1970,8 @@ class PostRevision(models.Model): email_address = models.EmailField(null = True, blank = True) # Question-specific fields - title = models.CharField(max_length=300, blank=True, default='') - tagnames = models.CharField(max_length=125, blank=True, default='') + title = models.CharField(max_length=300, blank=True, default='') + tagnames = models.CharField(max_length=125, blank=True, default='') is_anonymous = models.BooleanField(default=False) objects = PostRevisionManager() @@ -2105,10 +2114,10 @@ class PostRevision(models.Model): # Determine the revision number, if not set if not self.revision: # TODO: Maybe use Max() aggregation? Or `revisions.count() + 1` - self.revision = self.parent().revisions.values_list('revision', flat=True)[0] + 1 - + self.revision = self.parent().revisions.values_list( + 'revision', flat=True + )[0] + 1 self.full_clean() - super(PostRevision, self).save(**kwargs) def get_absolute_url(self): diff --git a/askbot/models/question.py b/askbot/models/question.py index a4b3233a..6b233188 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -133,7 +133,8 @@ class ThreadManager(BaseQuerySetManager): question.wikified_at = added_at #this is kind of bad, but we save assign privacy groups to posts and thread - question.parse_and_save(author = author, is_private = is_private) + #this call is rather heavy, we should split into several functions + parse_results = question.parse_and_save(author=author, is_private=is_private) revision = question.add_revision( author=author, @@ -155,7 +156,19 @@ class ThreadManager(BaseQuerySetManager): thread.make_public() # INFO: Question has to be saved before update_tags() is called - thread.update_tags(tagnames = tagnames, user = author, timestamp = added_at) + thread.update_tags(tagnames=tagnames, user=author, timestamp=added_at) + + #todo: this is handled in signal because models for posts + #are too spread out + signals.post_updated.send( + post=question, + updated_by=author, + newly_mentioned_users=parse_results['newly_mentioned_users'], + timestamp=added_at, + created=True, + diff=parse_results['diff'], + sender=question.__class__ + ) return thread @@ -246,16 +259,19 @@ class ThreadManager(BaseQuerySetManager): if askbot_settings.TAG_SEARCH_INPUT_ENABLED: #todo: this may be gone or disabled per option #"tag_search_box_enabled" - existing_tags = set( - Tag.objects.filter( - name__in = tags - ).values_list( - 'name', - flat = True - ) - ) - - non_existing_tags = set(tags) - existing_tags + existing_tags = set() + non_existing_tags = set() + #we're using a one-by-one tag retreival, b/c + #we want to take advantage of case-insensitive search indexes + #in postgresql, plus it is most likely that there will be + #only one or two search tags anyway + for tag in tags: + try: + tag_record = Tag.objects.get(name__iexact=tag) + existing_tags.add(tag_record.name) + except Tag.DoesNotExist: + non_existing_tags.add(tag) + meta_data['non_existing_tags'] = list(non_existing_tags) tags = existing_tags else: diff --git a/askbot/models/user.py b/askbot/models/user.py index bdb94100..39bb8ea9 100644 --- a/askbot/models/user.py +++ b/askbot/models/user.py @@ -271,6 +271,13 @@ class EmailFeedSetting(models.Model): 'q_sel': 'n', 'm_and_c': 'n' } + MAX_EMAIL_SCHEDULE = { + 'q_ask': 'i', + 'q_ans': 'i', + 'q_all': 'i', + 'q_sel': 'i', + 'm_and_c': 'i' + } FEED_TYPE_CHOICES = ( ('q_all',_('Entire forum')), ('q_ask',_('Questions that I asked')), diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py index a257a9f0..8577281c 100644 --- a/askbot/setup_templates/settings.py +++ b/askbot/setup_templates/settings.py @@ -230,7 +230,7 @@ CSRF_COOKIE_NAME = 'askbot_csrf' #CSRF_COOKIE_DOMAIN = '' STATICFILES_DIRS = ( - ('default/media', os.path.join(ASKBOT_ROOT, 'media'), + ('default/media', os.path.join(ASKBOT_ROOT, 'media')), ) RECAPTCHA_USE_SSL = True diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache index 8ed69803..6a40969d 100644 --- a/askbot/setup_templates/settings.py.mustache +++ b/askbot/setup_templates/settings.py.mustache @@ -231,7 +231,7 @@ CSRF_COOKIE_NAME = '{{domain_name}}_csrf' STATIC_ROOT = os.path.join(PROJECT_ROOT, "static") STATICFILES_DIRS = ( - ('default/media', os.path.join(ASKBOT_ROOT, 'media'), + ('default/media', os.path.join(ASKBOT_ROOT, 'media')), ) RECAPTCHA_USE_SSL = True diff --git a/askbot/setup_templates/tinymce_sample_config.py b/askbot/setup_templates/tinymce_sample_config.py index c75170b0..ac49da68 100644 --- a/askbot/setup_templates/tinymce_sample_config.py +++ b/askbot/setup_templates/tinymce_sample_config.py @@ -1,7 +1,7 @@ TINYMCE_COMPRESSOR = True TINYMCE_SPELLCHECKER = False -TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, 'common/media/js/tinymce/') -TINYMCE_URL = STATIC_URL + 'common/media/js/tinymce/' +TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, 'default/media/js/tinymce/') +TINYMCE_URL = STATIC_URL + 'default/media/js/tinymce/' TINYMCE_DEFAULT_CONFIG = { 'plugins': 'askbot_imageuploader,askbot_attachment', 'theme': 'advanced', diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py index bfce701b..a33f8047 100644 --- a/askbot/startup_procedures.py +++ b/askbot/startup_procedures.py @@ -313,6 +313,19 @@ class SettingsTester(object): '\n\n* '.join(self.messages) ) + +def test_new_skins(): + """tests that there are no directories in the `askbot/skins` + because we've moved skin files a few levels up""" + askbot_root = askbot.get_install_directory() + for item in os.listdir(os.path.join(askbot_root, 'skins')): + item_path = os.path.join(askbot_root, 'skins', item) + if os.path.isdir(item_path): + raise AskbotConfigError( + ('Time to move skin files from %s.\n' + 'Now we have `askbot/templates` and `askbot/media`') % item_path + ) + def test_staticfiles(): """tests configuration of the staticfiles app""" errors = list() @@ -567,20 +580,28 @@ def test_tinymce(): if compressor_on is False: errors.append('add line: TINYMCE_COMPRESSOR = True') - #check js root setting + #check js root setting - before version 0.7.44 we used to have + #"common" skin and after we combined it into the default js_root = getattr(django_settings, 'TINYMCE_JS_ROOT', '') - relative_js_path = 'common/media/js/tinymce/' + old_relative_js_path = 'common/media/js/tinymce/' + relative_js_path = 'default/media/js/tinymce/' expected_js_root = os.path.join(django_settings.STATIC_ROOT, relative_js_path) + old_expected_js_root = os.path.join(django_settings.STATIC_ROOT, old_relative_js_path) if os.path.normpath(js_root) != os.path.normpath(expected_js_root): - js_root_template = "add line: TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, '%s')" - errors.append(js_root_template % relative_js_path) + error_tpl = "add line: TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, '%s')" + if os.path.normpath(js_root) == os.path.normpath(old_expected_js_root): + error_tpl += '\nNote: we have moved files from "common" into "default"' + errors.append(error_tpl % relative_js_path) #check url setting url = getattr(django_settings, 'TINYMCE_URL', '') expected_url = django_settings.STATIC_URL + relative_js_path + old_expected_url = django_settings.STATIC_URL + old_relative_js_path if urls_equal(url, expected_url) is False: - js_url_template = "add line: TINYMCE_URL = STATIC_URL + '%s'" - errors.append(js_url_template % relative_js_path) + error_tpl = "add line: TINYMCE_URL = STATIC_URL + '%s'" + if urls_equal(url, old_expected_url): + error_tpl += '\nNote: we have moved files from "common" into "default"' + errors.append(error_tpl % relative_js_path) if errors: header = 'Please add the tynymce editor configuration ' + \ @@ -623,6 +644,7 @@ def run_startup_tests(): #test_csrf_cookie_domain() test_tinymce() test_staticfiles() + test_new_skins() test_longerusername() test_avatar() settings_tester = SettingsTester({ diff --git a/askbot/templates/email/base_mail.html b/askbot/templates/email/base_mail.html index dfd252f2..eacbf87d 100644 --- a/askbot/templates/email/base_mail.html +++ b/askbot/templates/email/base_mail.html @@ -143,10 +143,10 @@ </tr> <tr> <td valign="top"> - <table border="0" align="center" cellspacing="0" cellpadding="0" style="background-color: #fff;" width=80%> + <table border="0" align="center" cellspacing="0" cellpadding="0" style="background-color: #fff;" width=90%> <tr> <td valign="top"> - <table border="0" align="center" cellspacing="0" cellpadding="0" width=80%> + <table border="0" align="center" cellspacing="0" cellpadding="0" width=95%> <tr> <td valign="top"> <h1>{%block headline%}{%endblock%}</h1> diff --git a/askbot/templates/email/instant_notification.html b/askbot/templates/email/instant_notification.html index 3e8533b6..d353450d 100644 --- a/askbot/templates/email/instant_notification.html +++ b/askbot/templates/email/instant_notification.html @@ -4,7 +4,8 @@ {{ reply_separator }} <div>{{ content_preview }}</div> {% trans %} -<p>Please note - you can easily <a href="{{user_subscriptions_url}}">change</a> +<p style="font-size:10px; font-style:italic;"> +Please note - you can easily <a href="{{user_subscriptions_url}}">change</a> how often you receive these notifications or unsubscribe. Thank you for your interest in our forum!</p> {% endtrans %} {%endblock%} diff --git a/askbot/templates/email/macros.html b/askbot/templates/email/macros.html index d7817bf9..f1b06fc8 100644 --- a/askbot/templates/email/macros.html +++ b/askbot/templates/email/macros.html @@ -9,7 +9,7 @@ {{ start_quote(quote_level) }} {% set author = post.author.username|escape %} {% if post.post_type == 'question' %} - <p> + <p style="font-size:10px; font-weight: bold;"> {% if format == 'parent_subthread' %} {% if is_leaf_post %} {% trans %}Question by {{ author }}:{% endtrans %} @@ -22,21 +22,21 @@ {% trans %}Question :{% endtrans %} {% endif %} {{ post.thread.title }} - </p> - <p> + </p> + <p style="font-size:10px; font-weight: bold;"> {% if format != 'parent_subthread' %} {% trans %}Asked by {{ author }}:{% endtrans %} {% endif %} - </p> + </p> {% set tag_names = post.get_tag_names() %} {% if tag_names %} - <p> + <p style="font-size:10px; font-style:italic;"> {% trans %}Tags:{% endtrans %} {{ tag_names|join(', ') }}. </p> {% endif %} {% elif post.post_type == 'answer' %} - <p> + <p style="font-size:10px; font-weight: bold;"> {% if format == 'parent_subthread' %} {% if is_leaf_post %} {% trans -%} @@ -52,7 +52,7 @@ {% endif %} </p> {% else %} - <p> + <p style="font-size:10px; font-weight: bold;"> {% if format == 'parent_subthread' %} {% if is_leaf_post %} {% trans -%} @@ -70,7 +70,7 @@ {% endif %} </p> {% endif %} - {{ post.html }} + {{ post.html|absolutize_urls }} {{ end_quote(quote_level) }} {% endspaceless %} {% endmacro %} diff --git a/askbot/templates/main_page/tag_search.html b/askbot/templates/main_page/tag_search.html index 0d81bf4e..45f12b2f 100644 --- a/askbot/templates/main_page/tag_search.html +++ b/askbot/templates/main_page/tag_search.html @@ -1,6 +1,5 @@ <div id="tagSearch" class="box"> <h2>{% trans %}Tag search{% endtrans %}</h2> - <label for="ab-tag-search">{% trans %}Please note that tag search is case sensitive!{% endtrans %}</label> <div class="inputs"> <input id="ab-tag-search" autocomplete="off" type="text"/> <input id="ab-tag-search-add" type="submit" value="{% trans %}search{% endtrans %}"/> diff --git a/askbot/templates/user_inbox/base.html b/askbot/templates/user_inbox/base.html index 890cb0f7..2f8b805e 100644 --- a/askbot/templates/user_inbox/base.html +++ b/askbot/templates/user_inbox/base.html @@ -13,10 +13,10 @@ <div id="re_sections"> {% trans %}Sections:{% endtrans %} {% set sep = joiner('|') %} - {{ sep() }} - <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=messages" + {#{ sep() }} + {<a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=messages" {% if inbox_section == 'messages' %}class="on"{% endif %} - >{% trans %}messages{% endtrans %}</a> + >{% trans %}messages{% endtrans %}</a>#} {% if re_count > 0 %}{{ sep() }} <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=forum" {% if inbox_section == 'forum' %}class="on"{% endif %} diff --git a/askbot/templatetags/extra_filters_jinja.py b/askbot/templatetags/extra_filters_jinja.py index 3643e3c9..62a41895 100644 --- a/askbot/templatetags/extra_filters_jinja.py +++ b/askbot/templatetags/extra_filters_jinja.py @@ -24,17 +24,19 @@ from django_countries import settings as countries_settings register = coffin_template.Library() -def absolutize_urls_func(text): +@register.filter +def absolutize_urls(text): url_re1 = re.compile(r'(?P<prefix><img[^<]+src=)"(?P<url>/[^"]+)"', re.I) url_re2 = re.compile(r"(?P<prefix><img[^<]+src=)'(?P<url>/[^']+)'", re.I) url_re3 = re.compile(r'(?P<prefix><a[^<]+href=)"(?P<url>/[^"]+)"', re.I) url_re4 = re.compile(r"(?P<prefix><a[^<]+href=)'(?P<url>/[^']+)'", re.I) + img_replacement = '\g<prefix>"%s\g<url>" style="max-width:500px;"' % askbot_settings.APP_URL replacement = '\g<prefix>"%s\g<url>"' % askbot_settings.APP_URL - text = url_re1.sub(replacement, text) - text = url_re2.sub(replacement, text) + text = url_re1.sub(img_replacement, text) + text = url_re2.sub(img_replacement, text) text = url_re3.sub(replacement, text) return url_re4.sub(replacement, text) -absolutize_urls = register.filter(absolutize_urls_func) + TIMEZONE_STR = pytz.timezone( django_settings.TIME_ZONE diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py index fa166ba7..ec4210e8 100644 --- a/askbot/tests/db_api_tests.py +++ b/askbot/tests/db_api_tests.py @@ -10,6 +10,7 @@ from django.conf import settings from django.contrib.auth.models import AnonymousUser from django import forms from askbot.tests.utils import AskbotTestCase +from askbot.tests.utils import with_settings from askbot import models from askbot import const from askbot.conf import settings as askbot_settings @@ -164,6 +165,19 @@ class DBApiTests(AskbotTestCase): count = models.Tag.objects.filter(name='one-tag').count() self.assertEquals(count, 0) + + @with_settings({ + 'MAX_TAG_LENGTH': 200, + 'MAX_TAGS_PER_POST': 50 + }) + def test_retag_tags_too_long_raises(self): + tags = "aoaoesuouooeueooeuoaeuoeou aostoeuoaethoeastn oasoeoa nuhoasut oaeeots aoshootuheotuoehao asaoetoeatuoasu o aoeuethut aoaoe uou uoetu uouuou ao aouosutoeh" + question = self.post_question(user=self.user) + self.assertRaises( + exceptions.ValidationError, + self.user.retag_question, + question=question, tags=tags + ) def test_search_with_apostrophe_works(self): self.post_question( diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py index 1937da6f..f5b5e43b 100644 --- a/askbot/tests/email_alert_tests.py +++ b/askbot/tests/email_alert_tests.py @@ -10,6 +10,7 @@ from django.core.urlresolvers import reverse from django.test import TestCase from django.test.client import Client from askbot.tests import utils +from askbot.tests.utils import with_settings from askbot import models from askbot import mail from askbot.conf import settings as askbot_settings @@ -954,6 +955,29 @@ class EmailFeedSettingTests(utils.AskbotTestCase): data_after = TO_JSON(self.get_user_feeds()) self.assertEquals(data_before, data_after) + +class EmailAlertTestsWithGroupsEnabled(utils.AskbotTestCase): + + def setUp(self): + self.backup = askbot_settings.GROUPS_ENABLED + askbot_settings.update('GROUPS_ENABLED', True) + + def tearDown(self): + askbot_settings.update('GROUPS_ENABLED', self.backup) + + @with_settings({'MIN_REP_TO_TRIGGER_EMAIL': 1}) + def test_notification_for_global_group_works(self): + sender = self.create_user('sender') + recipient = self.create_user( + 'recipient', + notification_schedule=models.EmailFeedSetting.MAX_EMAIL_SCHEDULE + ) + self.post_question(user=sender) + outbox = django.core.mail.outbox + self.assertEqual(len(outbox), 1) + self.assertEqual(outbox[0].recipients(), [recipient.email]) + + class PostApprovalTests(utils.AskbotTestCase): """test notifications sent to authors when their posts are approved or published""" diff --git a/askbot/tests/templatefilter_tests.py b/askbot/tests/templatefilter_tests.py index a82737a4..090be956 100644 --- a/askbot/tests/templatefilter_tests.py +++ b/askbot/tests/templatefilter_tests.py @@ -7,14 +7,16 @@ class AbsolutizeUrlsTests(TestCase): askbot_settings.update('APP_URL', 'http://example.com') def test_absolutize_image_urls(self): text = """<img class="junk" src="/some.gif"> <IMG SRC='/some.png'>""" - output = filters.absolutize_urls_func(text) + #jinja register.filter decorator works in a weird way + output = filters.absolutize_urls[0](text) self.assertEqual( output, - '<img class="junk" src="http://example.com/some.gif"> <IMG SRC="http://example.com/some.png">' + '<img class="junk" src="http://example.com/some.gif" style="max-width:500px;"> <IMG SRC="http://example.com/some.png" style="max-width:500px;">' ) def test_absolutize_anchor_urls(self): text = """<a class="junk" href="/something">link</a> <A HREF='/something'>link</A>""" - output = filters.absolutize_urls_func(text) + #jinja register.filter decorator works in a weird way + output = filters.absolutize_urls[0](text) self.assertEqual( output, '<a class="junk" href="http://example.com/something">link</a> <A HREF="http://example.com/something">link</A>' diff --git a/askbot/utils/decorators.py b/askbot/utils/decorators.py index 0042df8f..da1cd7db 100644 --- a/askbot/utils/decorators.py +++ b/askbot/utils/decorators.py @@ -87,6 +87,7 @@ def ajax_only(view_func): if data is None: data = {} except Exception, e: + #todo: also check field called "message" if hasattr(e, 'messages'): if len(e.messages) > 1: message = u'<ul>' + \ diff --git a/askbot/utils/forms.py b/askbot/utils/forms.py index 6c3af066..6f57f71f 100644 --- a/askbot/utils/forms.py +++ b/askbot/utils/forms.py @@ -47,6 +47,18 @@ def get_db_object_or_404(params): #need catch-all b/c of the nature of the function raise Http404 +def format_errors(error_list): + """If there is only one error - returns a string + corresponding to that error, to remove the <ul> tag. + + If there is > 1 error - then convert the error_list into + a string. + """ + if len(error_list) == 1: + return unicode(error_list[0]) + else: + return unicode(error_list) + class StrippedNonEmptyCharField(forms.CharField): def clean(self, value): value = value.strip() diff --git a/askbot/views/writers.py b/askbot/views/writers.py index 85a92d23..4024b4b0 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -31,6 +31,7 @@ from askbot import models from askbot.conf import settings as askbot_settings from askbot.skins.loaders import render_into_skin from askbot.utils import decorators +from askbot.utils.forms import format_errors from askbot.utils.functions import diff_date from askbot.utils import url_utils from askbot.utils.file_utils import store_file @@ -314,6 +315,7 @@ def retag_question(request, id): request.user.assert_can_retag_question(question) if request.method == 'POST': form = forms.RetagQuestionForm(question, request.POST) + if form.is_valid(): if form.has_changed(): request.user.retag_question(question=question, tags=form.cleaned_data['tags']) @@ -334,7 +336,7 @@ def retag_question(request, id): return HttpResponseRedirect(question.get_absolute_url()) elif request.is_ajax(): response_data = { - 'message': unicode(form.errors['tags']), + 'message': format_errors(form.errors['tags']), 'success': False } data = simplejson.dumps(response_data) |