From 2fe61a156cae835c886bb07f932b3da4a15d68c7 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 26 Sep 2012 16:09:48 -0400 Subject: hopefully fixed the uploaded image urls for tinymce --- askbot/setup_templates/tinymce_sample_config.py | 1 + askbot/startup_procedures.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) 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..b9146189 100644 --- a/askbot/startup_procedures.py +++ b/askbot/startup_procedures.py @@ -598,6 +598,28 @@ 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.*)$', + # 'django.views.static.serve', + # {'document_root': static_root} + # ), + #3) disable `compressor_on` check above + #then - tinymce compressing will be disabled and it will + #be possible to debug custom tinymce plugins that are used with askbot + + + default_config = getattr(django_settings, 'TINYMCE_DEFAULT_CONFIG', None) + if default_config: + if 'convert_urls' in default_config: + message = "set 'convert_urls':False in TINYMCE_DEFAULT_CONFIG" + 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 -- cgit v1.2.3-1-g7c22 From 1fb8a774c00f8ce38ee97dbe20dea98e47a470f6 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 26 Sep 2012 16:22:13 -0400 Subject: one more fix to tinymce setup error checker --- askbot/startup_procedures.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py index b9146189..105500b8 100644 --- a/askbot/startup_procedures.py +++ b/askbot/startup_procedures.py @@ -612,13 +612,15 @@ def test_tinymce(): #be possible to debug custom tinymce plugins that are used with askbot - default_config = getattr(django_settings, 'TINYMCE_DEFAULT_CONFIG', None) - if default_config: - if 'convert_urls' in default_config: - message = "set 'convert_urls':False in TINYMCE_DEFAULT_CONFIG" + 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) + errors.append(message) #check js root setting - before version 0.7.44 we used to have -- cgit v1.2.3-1-g7c22 From 56f56f2077d4e4f5fc149eb222e33801ad036033 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 26 Sep 2012 19:21:05 -0400 Subject: fixed the meta description tag for the question pages --- askbot/templates/base.html | 9 ++++++++- askbot/templates/meta/html_head_meta.html | 8 -------- askbot/tests/page_load_tests.py | 12 ++++++++++++ 3 files changed, 20 insertions(+), 9 deletions(-) delete mode 100644 askbot/templates/meta/html_head_meta.html 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 @@ {% block title %}{% endblock %} - {{ settings.APP_TITLE|escape }} - {% include "meta/html_head_meta.html" %} + + {% block meta_description %} + + {% endblock %} + + {% if settings.GOOGLE_SITEMAP_CODE %} + + {% endif %} {% block before_css %}{% endblock %} {% include "meta/html_head_stylesheets.html" %} 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 @@ - -{% block meta_description %} - -{% endblock %} - -{% if settings.GOOGLE_SITEMAP_CODE %} - -{% endif %} diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py index 0f102975..293cb78d 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 @@ -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): -- cgit v1.2.3-1-g7c22 From 321ff687646975f6d91116ec75b56db7a565be09 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Thu, 27 Sep 2012 12:03:22 -0400 Subject: code formatting --- askbot/models/__init__.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 9c777ea7..82bc87d4 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -3044,11 +3044,16 @@ def send_instant_notifications_about_activity_in_post( revoke(old_task_id, terminate=True) from askbot import tasks - result = tasks.send_instant_nofications.apply_async((update_activity, - post, recipients), - countdown = django_settings.NOTIFICATION_DELAY_TIME) + 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) + 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 -- cgit v1.2.3-1-g7c22 From 1e47fb23634dbdf406a3e8893120793b7e065da5 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Thu, 27 Sep 2012 15:09:37 -0400 Subject: refactored the absolutize_urls function and the with_settings decorator for the test cases --- askbot/models/post.py | 2 +- askbot/templatetags/extra_filters_jinja.py | 18 ++---------------- askbot/tests/__init__.py | 1 - askbot/tests/db_api_tests.py | 5 +---- askbot/tests/email_alert_tests.py | 2 +- askbot/tests/page_load_tests.py | 12 ++++++------ askbot/tests/templatefilter_tests.py | 23 ----------------------- askbot/tests/utils.py | 2 +- askbot/tests/utils_tests.py | 24 ++++++++++++++++++++++++ askbot/utils/html.py | 18 ++++++++++++++++++ 10 files changed, 54 insertions(+), 53 deletions(-) delete mode 100644 askbot/tests/templatefilter_tests.py diff --git a/askbot/models/post.py b/askbot/models/post.py index 57847088..c65caeea 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -834,7 +834,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/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[/\..][^"]+)"', re.I) - url_re2 = re.compile(r"(?P[/\..][^']+)'", re.I) - url_re3 = re.compile(r'(?P/[^"]+)"', re.I) - url_re4 = re.compile(r"(?P/[^']+)'", re.I) - img_replacement = '\g"%s/\g" style="max-width:500px;"' % askbot_settings.APP_URL - replacement = '\g"%s\g"' % 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..65b0a950 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) diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py index f5b5e43b..2618cf9a 100644 --- a/askbot/tests/email_alert_tests.py +++ b/askbot/tests/email_alert_tests.py @@ -965,7 +965,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( diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py index 293cb78d..3805d012 100644 --- a/askbot/tests/page_load_tests.py +++ b/askbot/tests/page_load_tests.py @@ -152,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) @@ -443,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) @@ -514,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) @@ -550,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) 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 = """ """ - #jinja register.filter decorator works in a weird way - output = filters.absolutize_urls[0](text) - self.assertEqual( - output, - ' ' - ) - def test_absolutize_anchor_urls(self): - text = """link link""" - #jinja register.filter decorator works in a weird way - output = filters.absolutize_urls[0](text) - self.assertEqual( - output, - 'link link' - ) 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..bc3bb0bb 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,24 @@ 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_image_urls(self): + text = """ """ + #jinja register.filter decorator works in a weird way + self.assertEqual( + absolutize_urls(text), + ' ' + ) + + @with_settings(APP_URL='http://example.com') + def test_absolutize_anchor_urls(self): + text = """link link""" + #jinja register.filter decorator works in a weird way + self.assertEqual( + absolutize_urls(text), + 'link link' + ) 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 and tags to absolute, + starting with the ``askbot_settings.APP_URL``""" + #temporal fix for bad regex with wysiwyg editor + url_re1 = re.compile(r'(?P/[^"]+)"', re.I) + url_re2 = re.compile(r"(?P/[^']+)'", re.I) + url_re3 = re.compile(r'(?P/[^"]+)"', re.I) + url_re4 = re.compile(r"(?P/[^']+)'", re.I) + img_replacement = '\g"%s/\g" style="max-width:500px;"' % askbot_settings.APP_URL + replacement = '\g"%s\g"' % 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, -- cgit v1.2.3-1-g7c22 From 364633a40e2bfb820e8ddb48252eb614c2985753 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Fri, 28 Sep 2012 00:29:02 -0300 Subject: hopefully fixed the image urls in the email alerts --- askbot/mail/__init__.py | 3 +++ askbot/templates/email/macros.html | 2 +- askbot/tests/email_alert_tests.py | 35 ++++++++++++++++++++++++++++++++++- askbot/tests/utils_tests.py | 24 ++++++++++++++++++++---- 4 files changed, 58 insertions(+), 6 deletions(-) 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/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 %}

{% endif %} - {{ post.html|absolutize_urls }} + {{ post.html }} {{ end_quote(quote_level) }} {% endspaceless %} {% endmacro %} diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py index 2618cf9a..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 @@ -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 = '
home' + \ + 'an image' + 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/utils_tests.py b/askbot/tests/utils_tests.py index bc3bb0bb..c8526ad1 100644 --- a/askbot/tests/utils_tests.py +++ b/askbot/tests/utils_tests.py @@ -21,9 +21,9 @@ class UrlUtilsTests(TestCase): class HTMLUtilsTests(TestCase): """tests for :mod:`askbot.utils.html` module""" - + @with_settings(APP_URL='http://example.com') - def test_absolutize_image_urls(self): + def test_absolutize_urls(self): text = """ """ #jinja register.filter decorator works in a weird way self.assertEqual( @@ -31,11 +31,27 @@ class HTMLUtilsTests(TestCase): ' ' ) - @with_settings(APP_URL='http://example.com') - def test_absolutize_anchor_urls(self): text = """link link""" #jinja register.filter decorator works in a weird way self.assertEqual( absolutize_urls(text), 'link link' ) + + text = '' + self.assertEqual( + absolutize_urls(text), + '' + ) + + text = 'ohaouhaosthoanstoahuaou
Evgeny4 gravatar image' + self.assertEqual( + absolutize_urls(text), + 'ohaouhaosthoanstoahuaou
Evgeny4 gravatar image' + ) + + text = 'and some text
aouaosutoaehut' + self.assertEqual( + absolutize_urls(text), + 'and some text
aouaosutoaehut' + ) -- cgit v1.2.3-1-g7c22 From 44c2bd464ca42317a57555d0008737b102bc28d1 Mon Sep 17 00:00:00 2001 From: Adolfo Fitoria Date: Thu, 27 Sep 2012 22:06:16 -0600 Subject: fixes bug in notifications delay --- askbot/models/__init__.py | 80 ++++++++++++++++++++++++++++++++++++++++++----- askbot/models/post.py | 13 +++++--- askbot/tasks.py | 69 ---------------------------------------- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 9c777ea7..e7e9b013 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 @@ -3031,6 +3033,7 @@ 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, @@ -3040,15 +3043,76 @@ def send_instant_notifications_about_activity_in_post( 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) + if not send_instant_notifications_about_activity_in_post.ignore_result: + send_instant_notifications_about_activity_in_post.update_state(state=states.REVOKED) + return + + + 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..edf88628 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 @@ -653,11 +654,15 @@ 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' % self.thread.id + 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): 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)) -- cgit v1.2.3-1-g7c22 From 55ccecb8f4c5e3ea77d105ad4287643ee8123297 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Fri, 28 Sep 2012 02:38:24 -0300 Subject: restored the file attachment function to tinymce --- askbot/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js | 2 +- askbot/startup_procedures.py | 5 +++-- askbot/views/writers.py | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) 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'; tinyMCE.activeEditor.focus(); if (document.selection) { diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py index 105500b8..087fc957 100644 --- a/askbot/startup_procedures.py +++ b/askbot/startup_procedures.py @@ -607,7 +607,8 @@ def test_tinymce(): # 'django.views.static.serve', # {'document_root': static_root} # ), - #3) disable `compressor_on` check above + #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 @@ -685,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/views/writers.py b/askbot/views/writers.py index 4024b4b0..844a3ead 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -212,6 +212,9 @@ def ask(request):#view used to ask a new question must login/register in order for the question go be shown """ form = forms.AskForm(request.REQUEST) + print '=====================================' + print request.REQUEST.get('text', '') + print '=====================================' if request.method == 'POST': if form.is_valid(): timestamp = datetime.datetime.now() -- cgit v1.2.3-1-g7c22 From d3883448985772e1de79bcf917fa27d17698f484 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Fri, 28 Sep 2012 03:26:19 -0300 Subject: tried to fix the delayed notification issue --- askbot/models/__init__.py | 11 ++--------- askbot/models/post.py | 5 ++++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index e7e9b013..c16087f7 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -3039,15 +3039,8 @@ def send_instant_notifications_about_activity_in_post( 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: - if not send_instant_notifications_about_activity_in_post.ignore_result: - send_instant_notifications_about_activity_in_post.update_state(state=states.REVOKED) - return - - + #reload object from the database + post = Post.objects.get(id=post.id) if post.is_approved() is False: return diff --git a/askbot/models/post.py b/askbot/models/post.py index 7073bf80..8b8ff76a 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -655,8 +655,11 @@ class Post(models.Model): [u for u in notify_sets['for_email'] if u.is_administrator()] if not settings.CELERY_ALWAYS_EAGER: - cache_key = 'instant-notification-%d' % self.thread.id + 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.apply_async(( update_activity, -- cgit v1.2.3-1-g7c22 From 43ebad790274881b4efa22c3b450475d4d725a39 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Fri, 28 Sep 2012 03:55:22 -0300 Subject: commented out link to private messages again --- askbot/templates/user_inbox/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 @@
{% trans %}Sections:{% endtrans %} {% set sep = joiner('|') %} - {{ sep() }} + {#{ sep() }} {% trans %}messages{% endtrans %} + >{% trans %}messages{% endtrans %}#} {% if re_count > 0 %}{{ sep() }} Date: Fri, 28 Sep 2012 12:18:23 -0600 Subject: added logging statement to lamson decorator to store replies --- askbot/mail/lamson_handlers.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py index 59d707c7..e3251ee1 100644 --- a/askbot/mail/lamson_handlers.py +++ b/askbot/mail/lamson_handlers.py @@ -11,6 +11,8 @@ from askbot import mail from askbot.conf import settings as askbot_settings from askbot.skins.loaders import get_template +import logging + #we might end up needing to use something like this #to distinguish the reply text from the quoted original message @@ -66,7 +68,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 +129,13 @@ def process_reply(func): """processes forwarding rules, and run the handler in the case of error, send a bounce email """ + + logging.info("logged-message: %s", message.split('\n')[:5]) + 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 +143,7 @@ def process_reply(func): pass error = None + try: reply_address = ReplyAddress.objects.get( address = address, @@ -169,7 +175,7 @@ def process_reply(func): subject_line = "Error posting your reply", body_text = body_text, recipient_list = [message.From], - ) + ) return wrapped @@ -265,7 +271,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) -- cgit v1.2.3-1-g7c22 From c10f919d6c98898316d993a9ebe6d583c2425e64 Mon Sep 17 00:00:00 2001 From: Adolfo Fitoria Date: Fri, 28 Sep 2012 15:29:05 -0600 Subject: fixes logging body of email --- askbot/mail/lamson_handlers.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py index e3251ee1..3c937014 100644 --- a/askbot/mail/lamson_handlers.py +++ b/askbot/mail/lamson_handlers.py @@ -11,9 +11,6 @@ from askbot import mail from askbot.conf import settings as askbot_settings from askbot.skins.loaders import get_template -import logging - - #we might end up needing to use something like this #to distinguish the reply text from the quoted original message """ @@ -130,8 +127,6 @@ def process_reply(func): in the case of error, send a bounce email """ - logging.info("logged-message: %s", message.split('\n')[:5]) - try: for rule in django_settings.LAMSON_FORWARD: if re.match(rule['pattern'], message.base['to']): @@ -151,10 +146,17 @@ 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 'message :', content.split('\n')[:10] + break + else: + continue func( from_address = message.From, subject_line = message['Subject'], - parts = get_parts(message), + parts = parts, reply_address_object = reply_address ) -- cgit v1.2.3-1-g7c22 From 19e3cea75d091137b8f29552cd942e6397ed7e54 Mon Sep 17 00:00:00 2001 From: Adolfo Fitoria Date: Fri, 28 Sep 2012 15:42:09 -0600 Subject: fixed error and added separator --- askbot/mail/lamson_handlers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py index 3c937014..91a09d2f 100644 --- a/askbot/mail/lamson_handlers.py +++ b/askbot/mail/lamson_handlers.py @@ -146,9 +146,10 @@ def process_reply(func): ) #here is the business part of this function - parts = get_parts(message), + parts = get_parts(message) for part_type, content in parts: if part_type == 'body': + print '===============================' print 'message :', content.split('\n')[:10] break else: -- cgit v1.2.3-1-g7c22 From 5653a243291531fd9fed4c21cb9e809232071b6a Mon Sep 17 00:00:00 2001 From: Adolfo Fitoria Date: Fri, 28 Sep 2012 16:42:01 -0600 Subject: storing the full message --- askbot/mail/lamson_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py index 91a09d2f..da09eec2 100644 --- a/askbot/mail/lamson_handlers.py +++ b/askbot/mail/lamson_handlers.py @@ -150,7 +150,7 @@ def process_reply(func): for part_type, content in parts: if part_type == 'body': print '===============================' - print 'message :', content.split('\n')[:10] + print 'message :', content break else: continue -- cgit v1.2.3-1-g7c22 From 28d1f0b6ddaed10fcc5209a4a7a9e03d0f2002da Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Mon, 1 Oct 2012 19:56:39 -0300 Subject: disabled pre-load data in the user autocompleter, fixes the missing names in the autocompleter --- askbot/media/js/group_messaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askbot/media/js/group_messaging.js b/askbot/media/js/group_messaging.js index 2ab677ff..2522f72e 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, -- cgit v1.2.3-1-g7c22 From 8559f348eab0f59aa32d037a43d4a730112bab3c Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Mon, 1 Oct 2012 20:28:06 -0300 Subject: fixed a bug in the autocompleter where autocompletion data was loaded only once --- askbot/media/js/utils.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js index f6b46de5..1dddc07d 100644 --- a/askbot/media/js/utils.js +++ b/askbot/media/js/utils.js @@ -2309,14 +2309,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) { -- cgit v1.2.3-1-g7c22 From 788bbce91e05373e8801e99ec60ec90699b1b972 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 3 Oct 2012 16:00:48 -0400 Subject: added "vip" option to groups --- askbot/doc/source/changelog.rst | 1 + askbot/media/js/post.js | 8 + .../0149_auto__add_field_group_is_vip.py | 381 +++++++++++++++++++++ askbot/models/__init__.py | 15 +- askbot/models/post.py | 6 + askbot/models/user.py | 2 + askbot/templates/widgets/group_info.html | 11 + askbot/views/commands.py | 6 +- 8 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 askbot/migrations/0149_auto__add_field_group_is_vip.py diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index 82f46e32..1b5a6c2b 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -3,6 +3,7 @@ Changes in Askbot Development version ------------------- +* 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/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/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 c16087f7..4a593d17 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -481,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: @@ -532,7 +534,7 @@ def user_assert_can_unaccept_best_answer(self, answer = None): ) return # success - elif self.is_administrator() or self.is_moderator(): + elif self.is_administrator() or self.is_moderator() or self.is_post_moderator(answer): will_be_able_at = ( answer.added_at + datetime.timedelta( @@ -1967,6 +1969,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()) @@ -2782,6 +2794,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) diff --git a/askbot/models/post.py b/askbot/models/post.py index 8b8ff76a..f5481d23 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -578,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) 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/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 %}
+ + +
+
Date: Wed, 3 Oct 2012 23:24:21 -0400 Subject: added minimum karma to accept any answer --- askbot/conf/minimum_reputation.py | 9 +++++++++ askbot/doc/source/changelog.rst | 1 + askbot/models/__init__.py | 20 ++++++++++++++++---- askbot/tests/db_api_tests.py | 11 +++++++++++ askbot/tests/permission_assertion_tests.py | 12 ++++++++++-- 5 files changed, 47 insertions(+), 6 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 @@ -50,6 +50,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, diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index 1b5a6c2b..d77e11ab 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -3,6 +3,7 @@ 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 diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 4a593d17..83e67bb9 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -511,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(): @@ -534,7 +535,9 @@ def user_assert_can_unaccept_best_answer(self, answer = None): ) return # success - elif self.is_administrator() or self.is_moderator() or self.is_post_moderator(answer): + 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( @@ -2705,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', diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py index 65b0a950..25a2407f 100644 --- a/askbot/tests/db_api_tests.py +++ b/askbot/tests/db_api_tests.py @@ -442,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/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') -- cgit v1.2.3-1-g7c22 From 2ef2be1176d0d9617256554e6c1fc1934201993f Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Wed, 3 Oct 2012 23:50:10 -0400 Subject: fixed a bug in an autocompleter where initial value of the field could be erased --- askbot/media/js/utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js index 1dddc07d..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 -- cgit v1.2.3-1-g7c22 From 5ea290aaa7da29555a82cb0ba1fc9c8531ea5961 Mon Sep 17 00:00:00 2001 From: Evgeny Fadeev Date: Thu, 4 Oct 2012 00:45:14 -0400 Subject: fixed issue in html as reported by Niki --- askbot/templates/group_messaging/threads_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askbot/templates/group_messaging/threads_list.html b/askbot/templates/group_messaging/threads_list.html index 8469198c..bc0af802 100644 --- a/askbot/templates/group_messaging/threads_list.html +++ b/askbot/templates/group_messaging/threads_list.html @@ -12,7 +12,7 @@ {% endfor %} {% else %} - {% trans %}there are no messages yet...{% endtrans %} + {% trans %}there are no messages yet...{% endtrans %} {% endif %} -- cgit v1.2.3-1-g7c22