summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-10-04 01:06:09 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-10-04 01:06:09 -0400
commit2b692ca9927e41382fa2a384b901a317a64618d2 (patch)
tree1a65952754a9e6556928b1e6163c0dd45469ed6e
parent80df52cb982b6858d3d20b04e1a32073b1a530c4 (diff)
parent5ea290aaa7da29555a82cb0ba1fc9c8531ea5961 (diff)
downloadaskbot-2b692ca9927e41382fa2a384b901a317a64618d2.tar.gz
askbot-2b692ca9927e41382fa2a384b901a317a64618d2.tar.bz2
askbot-2b692ca9927e41382fa2a384b901a317a64618d2.zip
Merge branch 'master' into group-messaging
-rw-r--r--askbot/conf/minimum_reputation.py9
-rw-r--r--askbot/doc/source/changelog.rst2
-rw-r--r--askbot/mail/__init__.py3
-rw-r--r--askbot/mail/lamson_handlers.py21
-rw-r--r--askbot/media/js/group_messaging.js2
-rw-r--r--askbot/media/js/post.js8
-rw-r--r--askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js2
-rw-r--r--askbot/media/js/utils.js16
-rw-r--r--askbot/migrations/0149_auto__add_field_group_is_vip.py381
-rw-r--r--askbot/models/__init__.py114
-rw-r--r--askbot/models/post.py24
-rw-r--r--askbot/models/user.py2
-rw-r--r--askbot/setup_templates/tinymce_sample_config.py1
-rw-r--r--askbot/startup_procedures.py27
-rw-r--r--askbot/tasks.py69
-rw-r--r--askbot/templates/base.html9
-rw-r--r--askbot/templates/email/macros.html2
-rw-r--r--askbot/templates/group_messaging/threads_list.html2
-rw-r--r--askbot/templates/meta/html_head_meta.html8
-rw-r--r--askbot/templates/user_inbox/base.html4
-rw-r--r--askbot/templates/widgets/group_info.html11
-rw-r--r--askbot/templatetags/extra_filters_jinja.py18
-rw-r--r--askbot/tests/__init__.py1
-rw-r--r--askbot/tests/db_api_tests.py16
-rw-r--r--askbot/tests/email_alert_tests.py37
-rw-r--r--askbot/tests/page_load_tests.py24
-rw-r--r--askbot/tests/permission_assertion_tests.py12
-rw-r--r--askbot/tests/templatefilter_tests.py23
-rw-r--r--askbot/tests/utils.py2
-rw-r--r--askbot/tests/utils_tests.py40
-rw-r--r--askbot/utils/html.py18
-rw-r--r--askbot/views/commands.py6
32 files changed, 737 insertions, 177 deletions
diff --git a/askbot/conf/minimum_reputation.py b/askbot/conf/minimum_reputation.py
index 2c3a3496..152a2079 100644
--- a/askbot/conf/minimum_reputation.py
+++ b/askbot/conf/minimum_reputation.py
@@ -53,6 +53,15 @@ settings.register(
settings.register(
livesettings.IntegerValue(
MIN_REP,
+ 'MIN_REP_TO_ACCEPT_ANY_ANSWER',
+ default=500,
+ description=_('Accept any answer')
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ MIN_REP,
'MIN_REP_TO_FLAG_OFFENSIVE',
default=5,
description=_('Flag offensive')
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index 82f46e32..d77e11ab 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,6 +3,8 @@ Changes in Askbot
Development version
-------------------
+* Added minimum reputation setting to accept any answer as correct (Evgeny)
+* Added "VIP" option to groups - if checked, all posts belong to the group and users of that group in the future will be able to moderate those posts. Moderation features for VIP group are in progress (Evgeny)
* Added setting `NOTIFICATION_DELAY_TIME` to use with enabled celery daemon (Adolfo)
* Added setting `ASKBOT_INTERNAL_IPS` - to allow anonymous access to
closed sites from dedicated IP addresses (Evgeny)
diff --git a/askbot/mail/__init__.py b/askbot/mail/__init__.py
index 2d314dbc..74aa27e9 100644
--- a/askbot/mail/__init__.py
+++ b/askbot/mail/__init__.py
@@ -17,6 +17,7 @@ from askbot import const
from askbot.conf import settings as askbot_settings
from askbot.utils import url_utils
from askbot.utils.file_utils import store_file
+from askbot.utils.html import absolutize_urls
from bs4 import BeautifulSoup
#todo: maybe send_mail functions belong to models
@@ -116,6 +117,7 @@ def send_mail(
if raise_on_failure is True, exceptions.EmailNotSent is raised
"""
+ body_text = absolutize_urls(body_text)
try:
assert(subject_line is not None)
subject_line = prefix_the_subject_line(subject_line)
@@ -143,6 +145,7 @@ def mail_moderators(
):
"""sends email to forum moderators and admins
"""
+ body_text = absolutize_urls(body_text)
from django.db.models import Q
from askbot.models import User
recipient_list = User.objects.filter(
diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py
index 59d707c7..da09eec2 100644
--- a/askbot/mail/lamson_handlers.py
+++ b/askbot/mail/lamson_handlers.py
@@ -11,7 +11,6 @@ from askbot import mail
from askbot.conf import settings as askbot_settings
from askbot.skins.loaders import get_template
-
#we might end up needing to use something like this
#to distinguish the reply text from the quoted original message
"""
@@ -66,7 +65,7 @@ def is_inline_attachment(part):
def format_attachment(part):
"""takes message part and turns it into SimpleUploadedFile object"""
- att_info = get_attachment_info(part)
+ att_info = get_attachment_info(part)
name = att_info.get('filename', None)
content_type = get_content_type(part)
return SimpleUploadedFile(name, part.body, content_type)
@@ -127,10 +126,11 @@ def process_reply(func):
"""processes forwarding rules, and run the handler
in the case of error, send a bounce email
"""
+
try:
for rule in django_settings.LAMSON_FORWARD:
if re.match(rule['pattern'], message.base['to']):
- relay = Relay(host=rule['host'],
+ relay = Relay(host=rule['host'],
port=rule['port'], debug=1)
relay.deliver(message)
return
@@ -138,6 +138,7 @@ def process_reply(func):
pass
error = None
+
try:
reply_address = ReplyAddress.objects.get(
address = address,
@@ -145,10 +146,18 @@ def process_reply(func):
)
#here is the business part of this function
+ parts = get_parts(message)
+ for part_type, content in parts:
+ if part_type == 'body':
+ print '==============================='
+ print 'message :', content
+ break
+ else:
+ continue
func(
from_address = message.From,
subject_line = message['Subject'],
- parts = get_parts(message),
+ parts = parts,
reply_address_object = reply_address
)
@@ -169,7 +178,7 @@ def process_reply(func):
subject_line = "Error posting your reply",
body_text = body_text,
recipient_list = [message.From],
- )
+ )
return wrapped
@@ -265,7 +274,7 @@ def PROCESS(
"""handler to process the emailed message
and make a post to askbot based on the contents of
the email, including the text body and the file attachments"""
- #1) get actual email content
+ #1) get actual email content
# todo: factor this out into the process_reply decorator
reply_code = reply_address_object.address
body_text, stored_files, signature = mail.process_parts(parts, reply_code)
diff --git a/askbot/media/js/group_messaging.js b/askbot/media/js/group_messaging.js
index 04d5086c..08d9056e 100644
--- a/askbot/media/js/group_messaging.js
+++ b/askbot/media/js/group_messaging.js
@@ -305,7 +305,7 @@ NewThreadComposer.prototype.createDom = function() {
var usersAc = new AutoCompleter({
url: '/get-users-info/',//askbot['urls']['get_users_info'],
- preloadData: true,
+ preloadData: false,
minChars: 1,
useCache: true,
matchInside: true,
diff --git a/askbot/media/js/post.js b/askbot/media/js/post.js
index 483973ec..3ed61182 100644
--- a/askbot/media/js/post.js
+++ b/askbot/media/js/post.js
@@ -2654,6 +2654,14 @@ UserGroupProfileEditor.prototype.decorate = function(element){
var btn = element.find('#moderate-answers-to-enquirers');
moderate_publishing_replies_toggle.decorate(btn);
+ var vip_toggle = new TwoStateToggle();
+ vip_toggle.setPostData({
+ group_id: this.getTagId(),
+ property_name: 'is_vip'
+ });
+ var btn = element.find('#vip-toggle');
+ vip_toggle.decorate(btn);
+
var opennessSelector = new DropdownSelect();
var selectorElement = element.find('#group-openness-selector');
opennessSelector.setPostData({
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 717a4716..d1ef13b4 100644
--- a/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js
+++ b/askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js
@@ -14,7 +14,7 @@
if (description) {
content = content + '" title="' + description;
}
- content = content + '"/>';
+ content = content + '">file attached</a>';
tinyMCE.activeEditor.focus();
if (document.selection) {
diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js
index f6b46de5..2534fb21 100644
--- a/askbot/media/js/utils.js
+++ b/askbot/media/js/utils.js
@@ -2108,7 +2108,9 @@ AutoCompleter.prototype.decorate = function(element){
/**
* Set prompt text
*/
- this.setPrompt();
+ if (this.options['promptText']) {
+ this.setPrompt();
+ }
/**
* Create DOM element to hold results
@@ -2309,14 +2311,10 @@ AutoCompleter.prototype.activateNow = function() {
};
AutoCompleter.prototype.fetchData = function(value) {
- if (this.options.data) {
- this.filterAndShowResults(this.options.data, value);
- } else {
- var self = this;
- this.fetchRemoteData(value, function(remoteData) {
- self.filterAndShowResults(remoteData, value);
- });
- }
+ var self = this;
+ this.fetchRemoteData(value, function(remoteData) {
+ self.filterAndShowResults(remoteData, value);
+ });
};
AutoCompleter.prototype.fetchRemoteData = function(filter, callback) {
diff --git a/askbot/migrations/0149_auto__add_field_group_is_vip.py b/askbot/migrations/0149_auto__add_field_group_is_vip.py
new file mode 100644
index 00000000..377eb1f7
--- /dev/null
+++ b/askbot/migrations/0149_auto__add_field_group_is_vip.py
@@ -0,0 +1,381 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'Group.is_vip'
+ db.add_column('askbot_group', 'is_vip',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ def backwards(self, orm):
+ # Deleting field 'Group.is_vip'
+ db.delete_column('askbot_group', 'is_vip')
+
+ models = {
+ 'askbot.activity': {
+ 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},
+ 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True'}),
+ 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'symmetrical': 'False', 'through': "orm['askbot.ActivityAuditStatus']", 'to': "orm['auth.User']"}),
+ 'summary': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.activityauditstatus': {
+ 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'},
+ 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.anonymousanswer': {
+ 'Meta': {'object_name': 'AnonymousAnswer'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Post']"}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'askbot.anonymousquestion': {
+ 'Meta': {'object_name': 'AnonymousQuestion'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),
+ 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'askbot.askwidget': {
+ 'Meta': {'object_name': 'AskWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'include_text_field': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'inner_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'outer_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']", 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'askbot.award': {
+ 'Meta': {'object_name': 'Award', 'db_table': "u'award'"},
+ 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.badgedata': {
+ 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'},
+ 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'})
+ },
+ 'askbot.draftanswer': {
+ 'Meta': {'object_name': 'DraftAnswer'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['askbot.Thread']"})
+ },
+ 'askbot.draftquestion': {
+ 'Meta': {'object_name': 'DraftQuestion'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125', 'null': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'})
+ },
+ 'askbot.emailfeedsetting': {
+ 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.favoritequestion': {
+ 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.group': {
+ 'Meta': {'object_name': 'Group', '_ormbases': ['auth.Group']},
+ 'description': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'described_group'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
+ 'is_vip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_answers_to_enquirers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'openness': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}),
+ 'preapproved_email_domains': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+ 'preapproved_emails': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
+ },
+ 'askbot.groupmembership': {
+ 'Meta': {'object_name': 'GroupMembership', '_ormbases': ['auth.AuthUserGroups']},
+ 'authusergroups_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.AuthUserGroups']", 'unique': 'True', 'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+ },
+ 'askbot.markedtag': {
+ 'Meta': {'object_name': 'MarkedTag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.post': {
+ 'Meta': {'object_name': 'Post'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}),
+ 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_posts'", 'symmetrical': 'False', 'through': "orm['askbot.PostToGroup']", 'to': "orm['askbot.Group']"}),
+ 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'posts'", 'null': 'True', 'blank': 'True', 'to': "orm['askbot.Thread']"}),
+ 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'askbot.postflagreason': {
+ 'Meta': {'object_name': 'PostFlagReason'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'details': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_reject_reasons'", 'to': "orm['askbot.Post']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'askbot.postrevision': {
+ 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'},
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'approved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'approved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}),
+ 'by_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'revised_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'})
+ },
+ 'askbot.posttogroup': {
+ 'Meta': {'unique_together': "(('post', 'group'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"})
+ },
+ 'askbot.questionview': {
+ 'Meta': {'object_name': 'QuestionView'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {}),
+ 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.questionwidget': {
+ 'Meta': {'object_name': 'QuestionWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order_by': ('django.db.models.fields.CharField', [], {'default': "'-added_at'", 'max_length': '18'}),
+ 'question_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '7'}),
+ 'search_query': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'style': ('django.db.models.fields.TextField', [], {'default': '"\\n@import url(\'http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700\');\\nbody {\\n overflow: hidden;\\n}\\n\\n#container {\\n width: 200px;\\n height: 350px;\\n}\\nul {\\n list-style: none;\\n padding: 5px;\\n margin: 5px;\\n}\\nli {\\n border-bottom: #CCC 1px solid;\\n padding-bottom: 5px;\\n padding-top: 5px;\\n}\\nli:last-child {\\n border: none;\\n}\\na {\\n text-decoration: none;\\n color: #464646;\\n font-family: \'Yanone Kaffeesatz\', sans-serif;\\n font-size: 15px;\\n}\\n"', 'blank': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'askbot.replyaddress': {
+ 'Meta': {'object_name': 'ReplyAddress'},
+ 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}),
+ 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'reply_action': ('django.db.models.fields.CharField', [], {'default': "'auto_answer_or_comment'", 'max_length': '32'}),
+ 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.repute': {
+ 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"},
+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.tag': {
+ 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"},
+ 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'suggested_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'suggested_tags'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'tag_wiki': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'described_tag'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+ },
+ 'askbot.thread': {
+ 'Meta': {'object_name': 'Thread'},
+ 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}),
+ 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_threads'", 'symmetrical': 'False', 'through': "orm['askbot.ThreadToGroup']", 'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}),
+ 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),
+ 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+ },
+ 'askbot.threadtogroup': {
+ 'Meta': {'unique_together': "(('thread', 'group'),)", 'object_name': 'ThreadToGroup', 'db_table': "'askbot_thread_groups'"},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}),
+ 'visibility': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+ },
+ 'askbot.vote': {
+ 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}),
+ 'vote': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"})
+ },
+ 'auth.authusergroups': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'AuthUserGroups', 'db_table': "'auth_user_groups'", 'managed': 'False'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
+ 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+ 'email_signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['askbot'] \ No newline at end of file
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index 9c777ea7..83e67bb9 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -15,6 +15,8 @@ import hashlib
import logging
import urllib
import uuid
+from celery import states
+from celery.task import task
from django.core.urlresolvers import reverse, NoReverseMatch
from django.db.models import signals as django_signals
from django.template import Context
@@ -479,6 +481,8 @@ def _assert_user_can(
error_message = suspended_error_message
elif user.is_administrator() or user.is_moderator():
return
+ elif user.is_post_moderator(post):
+ return
elif low_rep_error_message and user.reputation < min_rep_setting:
raise askbot_exceptions.InsufficientReputation(low_rep_error_message)
else:
@@ -507,6 +511,7 @@ def user_assert_can_unaccept_best_answer(self, answer = None):
'Sorry, you cannot accept or unaccept best answers '
'because your account is suspended'
)
+
if self.is_blocked():
error_message = blocked_error_message
elif self.is_suspended():
@@ -530,7 +535,9 @@ def user_assert_can_unaccept_best_answer(self, answer = None):
)
return # success
- elif self.is_administrator() or self.is_moderator():
+ elif self.reputation >= askbot_settings.MIN_REP_TO_ACCEPT_ANY_ANSWER or \
+ self.is_administrator() or self.is_moderator() or self.is_post_moderator(answer):
+
will_be_able_at = (
answer.added_at +
datetime.timedelta(
@@ -1965,6 +1972,16 @@ def user_add_missing_askbot_subscriptions(self):
def user_is_moderator(self):
return (self.status == 'm' and self.is_administrator() == False)
+def user_is_post_moderator(self, post):
+ """True, if user and post have common groups
+ with moderation privilege"""
+ if askbot_settings.GROUPS_ENABLED:
+ group_ids = self.get_groups().values_list('id', flat=True)
+ post_groups = PostToGroup.objects.filter(post=post, group__id__in=group_ids)
+ return post_groups.filter(group__is_vip=True).count() > 0
+ else:
+ return False
+
def user_is_administrator_or_moderator(self):
return (self.is_administrator() or self.is_moderator())
@@ -2691,9 +2708,18 @@ def user_leave_group(self, group):
self.edit_group_membership(group=group, user=self, action='remove')
def user_is_group_member(self, group=None):
- return GroupMembership.objects.filter(
- user=self, group=group
- ).count() == 1
+ """True if user is member of group,
+ where group can be instance of Group
+ or name of group as string
+ """
+ if isinstance(group, str):
+ return GroupMembership.objects.filter(
+ user=self, group__name=group
+ ).count() == 1
+ else:
+ return GroupMembership.objects.filter(
+ user=self, group=group
+ ).count() == 1
User.add_to_class(
'add_missing_askbot_subscriptions',
@@ -2780,6 +2806,7 @@ User.add_to_class('leave_group', user_leave_group)
User.add_to_class('is_group_member', user_is_group_member)
User.add_to_class('remove_admin_status', user_remove_admin_status)
User.add_to_class('is_moderator', user_is_moderator)
+User.add_to_class('is_post_moderator', user_is_post_moderator)
User.add_to_class('is_approved', user_is_approved)
User.add_to_class('is_watched', user_is_watched)
User.add_to_class('is_suspended', user_is_suspended)
@@ -3031,24 +3058,79 @@ def get_reply_to_addresses(user, post):
return primary_addr, secondary_addr
#todo: action
+@task()
def send_instant_notifications_about_activity_in_post(
update_activity = None,
post = None,
recipients = None,
):
- if not django_settings.CELERY_ALWAYS_EAGER:
- cache_key = 'instant-notification-%d' % post.thread.id
- old_task_id = cache.cache.get(cache_key)
- if old_task_id:
- from celery.task.control import revoke
- revoke(old_task_id, terminate=True)
+ #reload object from the database
+ post = Post.objects.get(id=post.id)
+ if post.is_approved() is False:
+ return
+
+ if recipients is None:
+ return
+
+ acceptable_types = const.RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS
+
+ if update_activity.activity_type not in acceptable_types:
+ return
+
+ #calculate some variables used in the loop below
+ from askbot.skins.loaders import get_template
+ update_type_map = const.RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES
+ update_type = update_type_map[update_activity.activity_type]
+ origin_post = post.get_origin_post()
+ headers = mail.thread_headers(
+ post,
+ origin_post,
+ update_activity.activity_type
+ )
+
+ logger = logging.getLogger()
+ if logger.getEffectiveLevel() <= logging.DEBUG:
+ log_id = uuid.uuid1()
+ message = 'email-alert %s, logId=%s' % (post.get_absolute_url(), log_id)
+ logger.debug(message)
+ else:
+ log_id = None
+
+
+ for user in recipients:
+ if user.is_blocked():
+ continue
+
+ reply_address, alt_reply_address = get_reply_to_addresses(user, post)
+
+ subject_line, body_text = format_instant_notification_email(
+ to_user = user,
+ from_user = update_activity.user,
+ post = post,
+ reply_address = reply_address,
+ alt_reply_address = alt_reply_address,
+ update_type = update_type,
+ template = get_template('email/instant_notification.html')
+ )
+
+ headers['Reply-To'] = reply_address
+ try:
+ mail.send_mail(
+ subject_line=subject_line,
+ body_text=body_text,
+ recipient_list=[user.email],
+ related_object=origin_post,
+ activity_type=const.TYPE_ACTIVITY_EMAIL_UPDATE_SENT,
+ headers=headers,
+ raise_on_failure=True
+ )
+ except askbot_exceptions.EmailNotSent, error:
+ logger.debug(
+ '%s, error=%s, logId=%s' % (user.email, error, log_id)
+ )
+ else:
+ logger.debug('success %s, logId=%s' % (user.email, log_id))
- from askbot import tasks
- result = tasks.send_instant_nofications.apply_async((update_activity,
- post, recipients),
- countdown = django_settings.NOTIFICATION_DELAY_TIME)
- if not django_settings.CELERY_ALWAYS_EAGER:
- cache.cache.set(cache_key, result.task_id, django_settings.NOTIFICATION_DELAY_TIME)
def notify_author_of_published_revision(
revision = None, was_approved = None, **kwargs
diff --git a/askbot/models/post.py b/askbot/models/post.py
index 57847088..f5481d23 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -16,6 +16,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.utils.http import urlquote as django_urlquote
from django.core import exceptions as django_exceptions
+from django.core import cache
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.contrib.contenttypes.models import ContentType
@@ -577,6 +578,12 @@ class Post(models.Model):
return self.groups.filter(id=group.id).exists()
def add_to_groups(self, groups):
+ """associates post with groups"""
+ #this is likely to be temporary - we add
+ #vip groups to the list behind the scenes.
+ groups = list(groups)
+ vips = Group.objects.filter(is_vip=True)
+ groups.extend(vips)
#todo: use bulk-creation
for group in groups:
PostToGroup.objects.get_or_create(post=self, group=group)
@@ -653,11 +660,18 @@ class Post(models.Model):
notify_sets['for_email'] = \
[u for u in notify_sets['for_email'] if u.is_administrator()]
+ if not settings.CELERY_ALWAYS_EAGER:
+ cache_key = 'instant-notification-%d-%d' % (self.thread.id, updated_by.id)
+ if cache.cache.get(cache_key):
+ return
+ cache.cache.set(cache_key, True, settings.NOTIFICATION_DELAY_TIME)
+
from askbot.models import send_instant_notifications_about_activity_in_post
- send_instant_notifications_about_activity_in_post(
- update_activity=update_activity,
- post=self,
- recipients=notify_sets['for_email'],
+ send_instant_notifications_about_activity_in_post.apply_async((
+ update_activity,
+ self,
+ notify_sets['for_email']),
+ countdown = settings.NOTIFICATION_DELAY_TIME
)
def make_private(self, user, group_id=None):
@@ -834,7 +848,7 @@ class Post(models.Model):
return filtered_candidates
def format_for_email(
- self, quote_level = 0, is_leaf_post = False, format = None
+ self, quote_level=0, is_leaf_post=False, format=None
):
"""format post for the output in email,
if quote_level > 0, the post will be indented that number of times
diff --git a/askbot/models/user.py b/askbot/models/user.py
index 39bb8ea9..e5cccbf3 100644
--- a/askbot/models/user.py
+++ b/askbot/models/user.py
@@ -485,6 +485,8 @@ class Group(AuthGroup):
null = True, blank = True, default = ''
)
+ is_vip = models.BooleanField(default=False)
+
objects = GroupManager()
class Meta:
diff --git a/askbot/setup_templates/tinymce_sample_config.py b/askbot/setup_templates/tinymce_sample_config.py
index ac49da68..11085212 100644
--- a/askbot/setup_templates/tinymce_sample_config.py
+++ b/askbot/setup_templates/tinymce_sample_config.py
@@ -3,6 +3,7 @@ TINYMCE_SPELLCHECKER = False
TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, 'default/media/js/tinymce/')
TINYMCE_URL = STATIC_URL + 'default/media/js/tinymce/'
TINYMCE_DEFAULT_CONFIG = {
+ 'convert_urls': False,
'plugins': 'askbot_imageuploader,askbot_attachment',
'theme': 'advanced',
'content_css': STATIC_URL + 'default/media/style/tinymce/content.css',
diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py
index 091338e5..087fc957 100644
--- a/askbot/startup_procedures.py
+++ b/askbot/startup_procedures.py
@@ -598,6 +598,31 @@ def test_tinymce():
compressor_on = getattr(django_settings, 'TINYMCE_COMPRESSOR', False)
if compressor_on is False:
errors.append('add line: TINYMCE_COMPRESSOR = True')
+ #todo: add pointer to instructions on how to debug tinymce:
+ #1) add ('tiny_mce', os.path.join(ASKBOT_ROOT, 'media/js/tinymce')),
+ # to STATIFILES_DIRS
+ #2) add this to the main urlconf:
+ # (
+ # r'^m/(?P<path>.*)$',
+ # 'django.views.static.serve',
+ # {'document_root': static_root}
+ # ),
+ #3) set `TINYMCE_COMPRESSOR = False`
+ #4) set DEBUG = False
+ #then - tinymce compressing will be disabled and it will
+ #be possible to debug custom tinymce plugins that are used with askbot
+
+
+ config = getattr(django_settings, 'TINYMCE_DEFAULT_CONFIG', None)
+ if config:
+ if 'convert_urls' in config:
+ if config['convert_urls'] is not False:
+ message = "set 'convert_urls':False in TINYMCE_DEFAULT_CONFIG"
+ errors.append(message)
+ else:
+ message = "add to TINYMCE_DEFAULT_CONFIG\n'convert_urls': False,"
+ errors.append(message)
+
#check js root setting - before version 0.7.44 we used to have
#"common" skin and after we combined it into the default
@@ -661,7 +686,7 @@ def run_startup_tests():
test_middleware()
test_celery()
#test_csrf_cookie_domain()
- test_tinymce()
+ #test_tinymce()
test_staticfiles()
test_new_skins()
test_longerusername()
diff --git a/askbot/tasks.py b/askbot/tasks.py
index 01cd3223..650b7aeb 100644
--- a/askbot/tasks.py
+++ b/askbot/tasks.py
@@ -168,72 +168,3 @@ def record_question_visit(
actor = user,
context_object = question_post,
)
-
-@task()
-def send_instant_nofications(update_activity=None,
- post=None, recipients=None):
-
- if post.is_approved() is False:
- return
-
- if recipients is None:
- return
-
- acceptable_types = const.RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS
-
- if update_activity.activity_type not in acceptable_types:
- return
-
- #calculate some variables used in the loop below
- from askbot.skins.loaders import get_template
- update_type_map = const.RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES
- update_type = update_type_map[update_activity.activity_type]
- origin_post = post.get_origin_post()
- headers = mail.thread_headers(
- post,
- origin_post,
- update_activity.activity_type
- )
-
- logger = logging.getLogger()
- if logger.getEffectiveLevel() <= logging.DEBUG:
- log_id = uuid.uuid1()
- message = 'email-alert %s, logId=%s' % (post.get_absolute_url(), log_id)
- logger.debug(message)
- else:
- log_id = None
-
-
- for user in recipients:
- if user.is_blocked():
- continue
-
- reply_address, alt_reply_address = get_reply_to_addresses(user, post)
-
- subject_line, body_text = format_instant_notification_email(
- to_user = user,
- from_user = update_activity.user,
- post = post,
- reply_address = reply_address,
- alt_reply_address = alt_reply_address,
- update_type = update_type,
- template = get_template('email/instant_notification.html')
- )
-
- headers['Reply-To'] = reply_address
- try:
- mail.send_mail(
- subject_line=subject_line,
- body_text=body_text,
- recipient_list=[user.email],
- related_object=origin_post,
- activity_type=const.TYPE_ACTIVITY_EMAIL_UPDATE_SENT,
- headers=headers,
- raise_on_failure=True
- )
- except askbot_exceptions.EmailNotSent, error:
- logger.debug(
- '%s, error=%s, logId=%s' % (user.email, error, log_id)
- )
- else:
- logger.debug('success %s, logId=%s' % (user.email, log_id))
diff --git a/askbot/templates/base.html b/askbot/templates/base.html
index eaf2261d..63d7115f 100644
--- a/askbot/templates/base.html
+++ b/askbot/templates/base.html
@@ -2,7 +2,14 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{% block title %}{% endblock %} - {{ settings.APP_TITLE|escape }}</title>
- {% include "meta/html_head_meta.html" %}
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ {% block meta_description %}
+ <meta name="description" content="{{settings.APP_DESCRIPTION|escape}}" />
+ {% endblock %}
+ <meta name="keywords" content="{%block keywords%}{%endblock%},{{settings.APP_KEYWORDS|escape}}" />
+ {% if settings.GOOGLE_SITEMAP_CODE %}
+ <meta name="google-site-verification" content="{{settings.GOOGLE_SITEMAP_CODE}}" />
+ {% endif %}
<link rel="shortcut icon" href="{{ settings.SITE_FAVICON|media }}" />
{% block before_css %}{% endblock %}
{% include "meta/html_head_stylesheets.html" %}
diff --git a/askbot/templates/email/macros.html b/askbot/templates/email/macros.html
index f1b06fc8..125705e2 100644
--- a/askbot/templates/email/macros.html
+++ b/askbot/templates/email/macros.html
@@ -70,7 +70,7 @@
{% endif %}
</p>
{% endif %}
- {{ post.html|absolutize_urls }}
+ {{ post.html }}
{{ end_quote(quote_level) }}
{% endspaceless %}
{% endmacro %}
diff --git a/askbot/templates/group_messaging/threads_list.html b/askbot/templates/group_messaging/threads_list.html
index 224cf4f1..43492402 100644
--- a/askbot/templates/group_messaging/threads_list.html
+++ b/askbot/templates/group_messaging/threads_list.html
@@ -15,7 +15,7 @@
{% endfor %}
{% else %}
<tr>
- <td class="empty" colspan="3">{% trans %}there are no messages yet...{% endtrans %}<td>
+ <td class="empty" colspan="3">{% trans %}there are no messages yet...{% endtrans %}</td>
</tr>
{% endif %}
</table>
diff --git a/askbot/templates/meta/html_head_meta.html b/askbot/templates/meta/html_head_meta.html
deleted file mode 100644
index 352ffb53..00000000
--- a/askbot/templates/meta/html_head_meta.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-{% block meta_description %}
-<meta name="description" content="{{settings.APP_DESCRIPTION|escape}}" />
-{% endblock %}
-<meta name="keywords" content="{%block keywords%}{%endblock%},{{settings.APP_KEYWORDS|escape}}" />
-{% if settings.GOOGLE_SITEMAP_CODE %}
-<meta name="google-site-verification" content="{{settings.GOOGLE_SITEMAP_CODE}}" />
-{% endif %}
diff --git a/askbot/templates/user_inbox/base.html b/askbot/templates/user_inbox/base.html
index 890cb0f7..8beababc 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() }}
+ {#{ sep() }}
<a href="{{request.user.get_absolute_url()}}?sort=inbox&section=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&section=forum"
{% if inbox_section == 'forum' %}class="on"{% endif %}
diff --git a/askbot/templates/widgets/group_info.html b/askbot/templates/widgets/group_info.html
index cba8177a..1ea1539f 100644
--- a/askbot/templates/widgets/group_info.html
+++ b/askbot/templates/widgets/group_info.html
@@ -77,6 +77,17 @@
{% endfor %}
</select>
<br/>
+ <input
+ type="checkbox"
+ id="vip-toggle"
+ {% if group.is_vip %}checked="checked"{% endif %}
+ data-toggle-url="{% url toggle_group_profile_property %}"
+ />
+ <label for="vip-toggle">
+ {% trans %}Make group VIP{% endtrans %}
+ </label>
+ <br/>
+
<a
id="preapproved-emails"
title="{% trans %}list of email addresses of pre-approved users{% endtrans %}"
diff --git a/askbot/templatetags/extra_filters_jinja.py b/askbot/templatetags/extra_filters_jinja.py
index ba13166b..146de6d1 100644
--- a/askbot/templatetags/extra_filters_jinja.py
+++ b/askbot/templatetags/extra_filters_jinja.py
@@ -14,6 +14,7 @@ from askbot import exceptions as askbot_exceptions
from askbot.conf import settings as askbot_settings
from django.conf import settings as django_settings
from askbot.skins import utils as skin_utils
+from askbot.utils.html import absolutize_urls
from askbot.utils import functions
from askbot.utils import url_utils
from askbot.utils.slug import slugify
@@ -24,22 +25,7 @@ from django_countries import settings as countries_settings
register = coffin_template.Library()
-@register.filter
-def absolutize_urls(text):
- #temporal fix for bad regex with wysiwyg editor
- 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(img_replacement, text)
- text = url_re2.sub(img_replacement, text)
- text = url_re3.sub(replacement, text)
- #temporal fix for bad regex with wysiwyg editor
- return url_re4.sub(replacement, text).replace('%s//' % askbot_settings.APP_URL,
- '%s/' % askbot_settings.APP_URL)
-
+absolutize_urls = register.filter(absolutize_urls)
TIMEZONE_STR = pytz.timezone(
django_settings.TIME_ZONE
diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py
index 8a018ea1..fcef288b 100644
--- a/askbot/tests/__init__.py
+++ b/askbot/tests/__init__.py
@@ -10,7 +10,6 @@ from askbot.tests.management_command_tests import *
from askbot.tests.search_state_tests import *
from askbot.tests.form_tests import *
from askbot.tests.follow_tests import *
-from askbot.tests.templatefilter_tests import *
from askbot.tests.markup_test import *
from askbot.tests.post_model_tests import *
from askbot.tests.thread_model_tests import *
diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py
index ec4210e8..25a2407f 100644
--- a/askbot/tests/db_api_tests.py
+++ b/askbot/tests/db_api_tests.py
@@ -166,10 +166,7 @@ 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
- })
+ @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)
@@ -445,6 +442,17 @@ class GroupTests(AskbotTestCase):
'answer_comment': answer_comment
}
+ def test_is_group_member(self):
+ group1 = models.Group.objects.create(
+ name='somegroup', openness=models.Group.OPEN
+ )
+ self.u1.join_group(group1)
+ group2 = models.Group.objects.create(name='othergroup')
+ self.assertEqual(self.u1.is_group_member(group1), True)
+ self.assertEqual(self.u1.is_group_member('somegroup'), True)
+ self.assertEqual(self.u1.is_group_member(group2), False)
+ self.assertEqual(self.u1.is_group_member('othergroup'), False)
+
def test_posts_added_to_global_group(self):
q = self.post_question(user=self.u1)
group_name = askbot_settings.GLOBAL_GROUP_NAME
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index f5b5e43b..f4ae052b 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -1,6 +1,7 @@
+import bs4
+import copy
import datetime
import functools
-import copy
import time
from django.conf import settings as django_settings
from django.core import management
@@ -965,7 +966,7 @@ class EmailAlertTestsWithGroupsEnabled(utils.AskbotTestCase):
def tearDown(self):
askbot_settings.update('GROUPS_ENABLED', self.backup)
- @with_settings({'MIN_REP_TO_TRIGGER_EMAIL': 1})
+ @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(
@@ -1035,6 +1036,38 @@ class PostApprovalTests(utils.AskbotTestCase):
#self.assertEquals(outbox[1].recipients(), [u1.email,])#approval
+class AbsolutizeUrlsInEmailsTests(utils.AskbotTestCase):
+ @with_settings(MIN_REP_TO_TRIGGER_EMAIL=1, APP_URL='http://example.com/')
+ def test_urls_are_absolute(self):
+ u1 = self.create_user('u1')
+ max_email = models.EmailFeedSetting.MAX_EMAIL_SCHEDULE
+ u2 = self.create_user('u2', notification_schedule=max_email)
+ text = '<a href="/index.html">home</a>' + \
+ '<img alt="an image" src=\'/img.png\'><a href="https://example.com"><img src="/img.png"/></a>'
+ question = self.post_question(user=u1, body_text=text)
+ outbox = django.core.mail.outbox
+ html_message = outbox[0].alternatives[0][0]
+ content_type = outbox[0].alternatives[0][1]
+ self.assertEqual(content_type, 'text/html')
+
+ soup = bs4.BeautifulSoup(html_message)
+ links = soup.find_all('a')
+ url_bits = {}
+ for link in links:
+ url_bits[link.attrs['href'][:4]] = 1
+
+ self.assertEqual(len(url_bits.keys()), 1)
+ self.assertEqual(url_bits.keys()[0], 'http')
+
+ images = soup.find_all('img')
+ url_bits = {}
+ for img in images:
+ url_bits[img.attrs['src'][:4]] = 1
+
+ self.assertEqual(len(url_bits.keys()), 1)
+ self.assertEqual(url_bits.keys()[0], 'http')
+
+
class MailMessagesTests(utils.AskbotTestCase):
def test_ask_for_signature(self):
from askbot.mail import messages
diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py
index 0f102975..3805d012 100644
--- a/askbot/tests/page_load_tests.py
+++ b/askbot/tests/page_load_tests.py
@@ -9,6 +9,7 @@ from django.utils import simplejson
import coffin
import coffin.template
+from bs4 import BeautifulSoup
from askbot import models
from askbot.utils.slug import slugify
@@ -151,14 +152,14 @@ class PageLoadTestCase(AskbotTestCase):
def test_ask_page_allowed_anonymous(self):
self.proto_test_ask_page(True, 200)
- @with_settings({'GROUPS_ENABLED': False})
+ @with_settings(GROUPS_ENABLED=False)
def test_api_get_questions_groups_disabled(self):
data = {'query': 'Question'}
response = self.client.get(reverse('api_get_questions'), data)
data = simplejson.loads(response.content)
self.assertTrue(len(data) > 1)
- @with_settings({'GROUPS_ENABLED': True})
+ @with_settings(GROUPS_ENABLED=True)
def test_api_get_questions_groups_enabled(self):
group = models.Group(name='secret group', openness=models.Group.OPEN)
@@ -442,7 +443,7 @@ class PageLoadTestCase(AskbotTestCase):
@skipIf('askbot.middleware.forum_mode.ForumModeMiddleware' \
not in settings.MIDDLEWARE_CLASSES,
'no ForumModeMiddleware set')
- @with_settings({'ASKBOT_CLOSED_FORUM_MODE': True})
+ @with_settings(ASKBOT_CLOSED_FORUM_MODE=True)
def test_non_user_urls_in_closed_forum_mode(self):
self.proto_test_non_user_urls(status_code=302)
@@ -513,7 +514,7 @@ class PageLoadTestCase(AskbotTestCase):
@skipIf('askbot.middleware.forum_mode.ForumModeMiddleware' \
not in settings.MIDDLEWARE_CLASSES,
'no ForumModeMiddleware set')
- @with_settings({'ASKBOT_CLOSED_FORUM_MODE': True})
+ @with_settings(ASKBOT_CLOSED_FORUM_MODE=True)
def test_user_urls_in_closed_forum_mode(self):
self.proto_test_user_urls(status_code=302)
@@ -549,11 +550,11 @@ class PageLoadTestCase(AskbotTestCase):
template='user_inbox/responses_and_flags.html',
)
- @with_settings({'GROUPS_ENABLED': True})
+ @with_settings(GROUPS_ENABLED=True)
def test_user_page_with_groups_enabled(self):
self.try_url('users', status_code=302)
- @with_settings({'GROUPS_ENABLED': False})
+ @with_settings(GROUPS_ENABLED=False)
def test_user_page_with_groups_disabled(self):
self.try_url('users', status_code=200)
@@ -568,6 +569,17 @@ class AvatarTests(AskbotTestCase):
)
+class QuestionViewTests(AskbotTestCase):
+ def test_meta_description_has_question_summary(self):
+ user = self.create_user('user')
+ text = 'this is a question'
+ question = self.post_question(user=user, body_text=text)
+ response = self.client.get(question.get_absolute_url())
+ soup = BeautifulSoup(response.content)
+ meta_descr = soup.find_all('meta', attrs={'name': 'description'})[0]
+ self.assertTrue(text in meta_descr.attrs['content'])
+
+
class QuestionPageRedirectTests(AskbotTestCase):
def setUp(self):
diff --git a/askbot/tests/permission_assertion_tests.py b/askbot/tests/permission_assertion_tests.py
index 3849ce90..7f580dda 100644
--- a/askbot/tests/permission_assertion_tests.py
+++ b/askbot/tests/permission_assertion_tests.py
@@ -5,6 +5,7 @@ from django.conf import settings
from django.test import TestCase
from django.core import exceptions
from askbot.tests import utils
+from askbot.tests.utils import with_settings
from askbot.conf import settings as askbot_settings
from askbot import models
from askbot.templatetags import extra_filters_jinja as template_filters
@@ -1367,12 +1368,19 @@ class AcceptBestAnswerPermissionAssertionTests(utils.AskbotTestCase):
self.user_post_answer()
self.assert_user_cannot()
- def test_high_rep_other_user_cannot_accept_answer(self):
+ def test_low_rep_other_user_cannot_accept_answer(self):
self.other_post_answer()
self.create_user(username = 'third_user')
- self.third_user.reputation = 1000000
+ self.third_user.reputation = askbot_settings.MIN_REP_TO_ACCEPT_ANY_ANSWER - 1
self.assert_user_cannot(user = self.third_user)
+ @with_settings(MIN_DAYS_FOR_STAFF_TO_ACCEPT_ANSWER=0)
+ def test_high_rep_other_user_can_accept_answer(self):
+ self.other_post_answer()
+ self.create_user(username = 'third_user')
+ self.third_user.reputation = askbot_settings.MIN_REP_TO_ACCEPT_ANY_ANSWER
+ self.assert_user_can(user = self.third_user)
+
def test_moderator_cannot_accept_own_answer(self):
self.other_post_answer()
self.other_user.set_status('m')
diff --git a/askbot/tests/templatefilter_tests.py b/askbot/tests/templatefilter_tests.py
deleted file mode 100644
index 3902aad4..00000000
--- a/askbot/tests/templatefilter_tests.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from unittest import TestCase
-from askbot.templatetags import extra_filters_jinja as filters
-from askbot.conf import settings as askbot_settings
-
-class AbsolutizeUrlsTests(TestCase):
- def setUp(self):
- askbot_settings.update('APP_URL', 'http://example.com')
- def test_absolutize_image_urls(self):
- text = """<img class="junk" src="/some.gif"> <img class="junk" src="../../cat.gif"> <IMG SRC='/some.png'>"""
- #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" style="max-width:500px;"> <img class="junk" src="http://example.com/../../cat.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>"""
- #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/tests/utils.py b/askbot/tests/utils.py
index 1cd174c1..ee3cd37e 100644
--- a/askbot/tests/utils.py
+++ b/askbot/tests/utils.py
@@ -4,7 +4,7 @@ from django.test import TestCase
from functools import wraps
from askbot import models
-def with_settings(settings_dict):
+def with_settings(**settings_dict):
"""a decorator that will run function with settings
then apply previous settings and return the result
of the function.
diff --git a/askbot/tests/utils_tests.py b/askbot/tests/utils_tests.py
index 7f252b69..c8526ad1 100644
--- a/askbot/tests/utils_tests.py
+++ b/askbot/tests/utils_tests.py
@@ -1,5 +1,8 @@
from django.test import TestCase
+from askbot.tests.utils import with_settings
from askbot.utils.url_utils import urls_equal
+from askbot.utils.html import absolutize_urls
+from askbot.conf import settings as askbot_settings
class UrlUtilsTests(TestCase):
@@ -15,3 +18,40 @@ class UrlUtilsTests(TestCase):
self.assertTrue(e('http://cnn.com/path', 'http://cnn.com/path/', True))
self.assertFalse(e('http://cnn.com/path', 'http://cnn.com/path/'))
+
+class HTMLUtilsTests(TestCase):
+ """tests for :mod:`askbot.utils.html` module"""
+
+ @with_settings(APP_URL='http://example.com')
+ def test_absolutize_urls(self):
+ text = """<img class="junk" src="/some.gif"> <img class="junk" src="/cat.gif"> <IMG SRC='/some.png'>"""
+ #jinja register.filter decorator works in a weird way
+ self.assertEqual(
+ absolutize_urls(text),
+ '<img class="junk" src="http://example.com/some.gif" style="max-width:500px;"> <img class="junk" src="http://example.com/cat.gif" style="max-width:500px;"> <IMG SRC="http://example.com/some.png" style="max-width:500px;">'
+ )
+
+ text = """<a class="junk" href="/something">link</a> <A HREF='/something'>link</A>"""
+ #jinja register.filter decorator works in a weird way
+ self.assertEqual(
+ absolutize_urls(text),
+ '<a class="junk" href="http://example.com/something">link</a> <A HREF="http://example.com/something">link</A>'
+ )
+
+ text = '<img src="/upfiles/13487900323638005.png" alt="" />'
+ self.assertEqual(
+ absolutize_urls(text),
+ '<img src="http://example.com/upfiles/13487900323638005.png" style="max-width:500px;" alt="" />'
+ )
+
+ text = 'ohaouhaosthoanstoahuaou<br /><img src="/upfiles/13487906221942257.png" alt="" /><img class="gravatar" title="Evgeny4" src="http://kp-dev.askbot.com/avatar/render_primary/5/32/" alt="Evgeny4 gravatar image" width="32" height="32" />'
+ self.assertEqual(
+ absolutize_urls(text),
+ 'ohaouhaosthoanstoahuaou<br /><img src="http://example.com/upfiles/13487906221942257.png" style="max-width:500px;" alt="" /><img class="gravatar" title="Evgeny4" src="http://kp-dev.askbot.com/avatar/render_primary/5/32/" alt="Evgeny4 gravatar image" width="32" height="32" />'
+ )
+
+ text = '<a href="/upfiles/13487909784287052.png"><img src="/upfiles/13487909942351405.png" alt="" /></a><img src="http://i2.cdn.turner.com/cnn/dam/assets/120927033530-ryder-cup-captains-wall-4-tease.jpg" alt="" width="160" height="90" border="0" />and some text<br />aouaosutoaehut'
+ self.assertEqual(
+ absolutize_urls(text),
+ '<a href="http://example.com/upfiles/13487909784287052.png"><img src="http://example.com/upfiles/13487909942351405.png" style="max-width:500px;" alt="" /></a><img src="http://i2.cdn.turner.com/cnn/dam/assets/120927033530-ryder-cup-captains-wall-4-tease.jpg" alt="" width="160" height="90" border="0" />and some text<br />aouaosutoaehut'
+ )
diff --git a/askbot/utils/html.py b/askbot/utils/html.py
index 44e3f1df..49eddee2 100644
--- a/askbot/utils/html.py
+++ b/askbot/utils/html.py
@@ -6,6 +6,7 @@ import htmlentitydefs
from urlparse import urlparse
from django.core.urlresolvers import reverse
from django.utils.html import escape
+from askbot.conf import settings as askbot_settings
class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big',
@@ -43,6 +44,23 @@ class HTMLSanitizer(tokenizer.HTMLTokenizer, HTMLSanitizerMixin):
if token:
yield token
+def absolutize_urls(html):
+ """turns relative urls in <img> and <a> tags to absolute,
+ starting with the ``askbot_settings.APP_URL``"""
+ #temporal fix for bad regex with wysiwyg editor
+ 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
+ html = url_re1.sub(img_replacement, html)
+ html= url_re2.sub(img_replacement, html)
+ html = url_re3.sub(replacement, html)
+ #temporal fix for bad regex with wysiwyg editor
+ return url_re4.sub(replacement, html).replace('%s//' % askbot_settings.APP_URL,
+ '%s/' % askbot_settings.APP_URL)
+
def sanitize_html(html):
"""Sanitizes an HTML fragment."""
p = html5lib.HTMLParser(tokenizer=HTMLSanitizer,
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index f05cc9e2..f02061cd 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -985,7 +985,11 @@ def toggle_group_profile_property(request):
#todo: this might be changed to more general "toggle object property"
group_id = IntegerField().clean(int(request.POST['group_id']))
property_name = CharField().clean(request.POST['property_name'])
- assert property_name in ('moderate_email', 'moderate_answers_to_enquirers')
+ assert property_name in (
+ 'moderate_email',
+ 'moderate_answers_to_enquirers',
+ 'is_vip'
+ )
group = models.Group.objects.get(id = group_id)
new_value = not getattr(group, property_name)
setattr(group, property_name, new_value)