summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-08-10 16:00:44 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2012-08-10 16:00:44 -0400
commit72210f4fef0b81decc90738b0fe5ac407455eaa0 (patch)
tree6f8dab769b57e499792d38b3b602381fd72d4d68
parent460d32a4cc2cd77ea95d7eab1d08daafd7638dd9 (diff)
downloadaskbot-72210f4fef0b81decc90738b0fe5ac407455eaa0.tar.gz
askbot-72210f4fef0b81decc90738b0fe5ac407455eaa0.tar.bz2
askbot-72210f4fef0b81decc90738b0fe5ac407455eaa0.zip
merged with the user-groups branch
-rw-r--r--askbot/__init__.py12
-rw-r--r--askbot/conf/forum_data_rules.py11
-rw-r--r--askbot/conf/group_settings.py25
-rw-r--r--askbot/conf/minimum_reputation.py14
-rw-r--r--askbot/conf/moderation.py9
-rw-r--r--askbot/const/__init__.py20
-rw-r--r--askbot/deps/django_authopenid/urls.py7
-rw-r--r--askbot/deps/django_authopenid/views.py82
-rw-r--r--askbot/deps/livesettings/models.py58
-rw-r--r--askbot/deps/livesettings/overrides.py4
-rw-r--r--askbot/doc/source/changelog.rst5
-rw-r--r--askbot/doc/source/contributors.rst1
-rw-r--r--askbot/doc/source/management-commands.rst2
-rw-r--r--askbot/doc/source/settings.rst14
-rw-r--r--askbot/exceptions.py5
-rw-r--r--askbot/forms.py595
-rw-r--r--askbot/mail/__init__.py22
-rw-r--r--askbot/mail/lamson_handlers.py1
-rw-r--r--askbot/management/commands/build_livesettings_cache.py12
-rw-r--r--askbot/management/commands/send_unanswered_question_reminders.py4
-rw-r--r--askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py9
-rw-r--r--askbot/migrations/0126_add_field__auth_user__is_fake.py19
-rw-r--r--askbot/migrations/0127_save_category_tree_as_json.py385
-rw-r--r--askbot/migrations/0128_add_groups_field__to__thread_and_post.py335
-rw-r--r--askbot/migrations/0129_auto__del_field_post_is_private.py322
-rw-r--r--askbot/migrations/0130_auto__del_field_postrevision_revision_type.py321
-rw-r--r--askbot/migrations/0131_auto__add_field_tag_status.py335
-rw-r--r--askbot/migrations/0132_auto__add_draftquestion__add_draftanswer.py356
-rw-r--r--askbot/migrations/0133_apply_global_group_to_posts_and_users.py449
-rw-r--r--askbot/migrations/0134_create_personal_groups_for_all_users.py378
-rw-r--r--askbot/migrations/0135_add__primary_group__field__to__auth_user.py353
-rw-r--r--askbot/models/__init__.py163
-rw-r--r--askbot/models/base.py2
-rw-r--r--askbot/models/post.py366
-rw-r--r--askbot/models/question.py188
-rw-r--r--askbot/models/tag.py39
-rw-r--r--askbot/models/user.py5
-rw-r--r--askbot/setup_templates/settings.py2
-rw-r--r--askbot/setup_templates/settings.py.mustache2
-rw-r--r--askbot/skins/common/media/js/post.js196
-rw-r--r--askbot/skins/common/media/js/user.js160
-rw-r--r--askbot/skins/common/media/js/utils.js21
-rw-r--r--askbot/skins/common/templates/widgets/edit_post.html30
-rw-r--r--askbot/skins/default/media/images/OpenSans-CondBold.ttfbin0 -> 264372 bytes
-rw-r--r--askbot/skins/default/media/images/OpenSans-CondLight.ttfbin0 -> 221108 bytes
-rw-r--r--askbot/skins/default/media/images/OpenSans-CondLightItalic.ttfbin0 -> 210804 bytes
-rw-r--r--askbot/skins/default/media/style/lib_style.less5
-rw-r--r--askbot/skins/default/media/style/style.css159
-rw-r--r--askbot/skins/default/media/style/style.less240
-rw-r--r--askbot/skins/default/templates/ask.html12
-rw-r--r--askbot/skins/default/templates/ask_by_widget.html16
-rw-r--r--askbot/skins/default/templates/ask_widget_complete.html8
-rw-r--r--askbot/skins/default/templates/authopenid/widget_signin.html231
-rw-r--r--askbot/skins/default/templates/django_error.html31
-rw-r--r--askbot/skins/default/templates/email/ask_for_signature.html1
-rw-r--r--askbot/skins/default/templates/macros.html28
-rw-r--r--askbot/skins/default/templates/meta/bottom_scripts.html3
-rw-r--r--askbot/skins/default/templates/meta/fonts.html16
-rw-r--r--askbot/skins/default/templates/meta/html_head_javascript.html1
-rw-r--r--askbot/skins/default/templates/meta/html_head_stylesheets.html3
-rw-r--r--askbot/skins/default/templates/question.html2
-rw-r--r--askbot/skins/default/templates/question/content.html28
-rw-r--r--askbot/skins/default/templates/question/javascript.html77
-rw-r--r--askbot/skins/default/templates/question/new_answer_form.html7
-rw-r--r--askbot/skins/default/templates/question/question_card.html6
-rw-r--r--askbot/skins/default/templates/question/sidebar.html65
-rw-r--r--askbot/skins/default/templates/question_edit.html3
-rw-r--r--askbot/skins/default/templates/reopen.html4
-rw-r--r--askbot/skins/default/templates/revisions.html17
-rw-r--r--askbot/skins/default/templates/user_profile/user.html2
-rw-r--r--askbot/skins/default/templates/user_profile/user_inbox.html11
-rw-r--r--askbot/skins/default/templates/widget_base.html21
-rw-r--r--askbot/skins/default/templates/widgets/ask_button.html5
-rw-r--r--askbot/skins/default/templates/widgets/ask_form.html5
-rw-r--r--askbot/tasks.py133
-rw-r--r--askbot/tests/__init__.py2
-rw-r--r--askbot/tests/__init__.py.orig19
-rw-r--r--askbot/tests/badge_tests.py6
-rw-r--r--askbot/tests/cache_tests.py17
-rw-r--r--askbot/tests/db_api_tests.py177
-rw-r--r--askbot/tests/email_alert_tests.py4
-rw-r--r--askbot/tests/form_tests.py60
-rw-r--r--askbot/tests/management_command_tests.py2
-rw-r--r--askbot/tests/permission_assertion_tests.py6
-rw-r--r--askbot/tests/user_model_tests.py21
-rw-r--r--askbot/tests/utils.py78
-rw-r--r--askbot/tests/widget_tests.py61
-rw-r--r--askbot/urls.py114
-rw-r--r--askbot/utils/console.py22
-rw-r--r--askbot/utils/forms.py2
-rw-r--r--askbot/utils/slug.py71
-rw-r--r--askbot/views/__init__.py1
-rw-r--r--askbot/views/commands.py172
-rw-r--r--askbot/views/meta.py2
-rw-r--r--askbot/views/readers.py38
-rw-r--r--askbot/views/users.py11
-rw-r--r--askbot/views/widgets.py74
-rw-r--r--askbot/views/writers.py60
98 files changed, 6564 insertions, 935 deletions
diff --git a/askbot/__init__.py b/askbot/__init__.py
index c51becac..c79d19f9 100644
--- a/askbot/__init__.py
+++ b/askbot/__init__.py
@@ -5,9 +5,6 @@ Functions in the askbot module perform various
basic actions on behalf of the forum application
"""
import os
-import smtplib
-import sys
-import logging
VERSION = (0, 7, 43)
@@ -44,17 +41,19 @@ try:
from askbot.deployment.assertions import assert_package_compatibility
assert_package_compatibility()
patches.patch_django()
- patches.patch_coffin()#must go after django
+ patches.patch_coffin() # must go after django
except ImportError:
pass
+
def get_install_directory():
"""returns path to directory
- where code of the askbot django application
+ where code of the askbot django application
is installed
"""
return os.path.dirname(__file__)
+
def get_path_to(relative_path):
"""returns absolute path to a file
relative to ``askbot`` directory
@@ -73,10 +72,11 @@ def get_version():
"""
return '.'.join([str(subversion) for subversion in VERSION])
+
def get_database_engine_name():
"""returns name of the database engine,
independently of the version of django
- - for django >=1.2 looks into ``settings.DATABASES['default']``,
+ - for django >=1.2 looks into ``settings.DATABASES['default']``,
(i.e. assumes that askbot uses database named 'default')
, and for django 1.1 and below returns settings.DATABASE_ENGINE
"""
diff --git a/askbot/conf/forum_data_rules.py b/askbot/conf/forum_data_rules.py
index a94a5f6c..46a1fe08 100644
--- a/askbot/conf/forum_data_rules.py
+++ b/askbot/conf/forum_data_rules.py
@@ -137,6 +137,17 @@ settings.register(
settings.register(
livesettings.BooleanValue(
FORUM_DATA_RULES,
+ 'LIMIT_ONE_ANSWER_PER_USER',
+ default = True,
+ description = _(
+ 'Limit one answer per question per user'
+ )
+ )
+)
+
+settings.register(
+ livesettings.BooleanValue(
+ FORUM_DATA_RULES,
'TAGS_ARE_REQUIRED',
description = _('Are tags required?'),
default = False,
diff --git a/askbot/conf/group_settings.py b/askbot/conf/group_settings.py
index b3c0069c..2933b831 100644
--- a/askbot/conf/group_settings.py
+++ b/askbot/conf/group_settings.py
@@ -19,6 +19,31 @@ settings.register(
)
)
+def group_name_update_callback(old_name, new_name):
+ from askbot.models.tag import get_global_group, clean_group_name
+ cleaned_new_name = clean_group_name(new_name.strip())
+
+ if new_name == '':
+ #name cannot be empty
+ return old_name
+
+ group = get_global_group()
+ group.name = cleaned_new_name
+ group.save()
+ return new_name
+
+
+settings.register(
+ livesettings.StringValue(
+ GROUP_SETTINGS,
+ 'GLOBAL_GROUP_NAME',
+ default = _('everyone'),
+ description = _('Global user group name'),
+ help_text = _('All users belong to this group automatically'),
+ update_callback=group_name_update_callback
+ )
+)
+
settings.register(
livesettings.BooleanValue(
GROUP_SETTINGS,
diff --git a/askbot/conf/minimum_reputation.py b/askbot/conf/minimum_reputation.py
index 54ba0f65..2c3a3496 100644
--- a/askbot/conf/minimum_reputation.py
+++ b/askbot/conf/minimum_reputation.py
@@ -181,7 +181,6 @@ settings.register(
)
)
-
settings.register(
livesettings.IntegerValue(
MIN_REP,
@@ -190,3 +189,16 @@ settings.register(
description=_('Post answers and comments by email')
)
)
+
+settings.register(
+ livesettings.IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_TRIGGER_EMAIL',
+ default=15,
+ description=_('Trigger email notifications'),
+ help_text=_(
+ 'Reduces spam as notifications wont\'t be sent '
+ 'to regular users for posts of low karma users'
+ )
+ )
+)
diff --git a/askbot/conf/moderation.py b/askbot/conf/moderation.py
index 9f8e24c7..8e77385f 100644
--- a/askbot/conf/moderation.py
+++ b/askbot/conf/moderation.py
@@ -4,8 +4,16 @@ from askbot.conf.settings_wrapper import settings
from askbot.conf.super_groups import DATA_AND_FORMATTING
from askbot.deps.livesettings import ConfigurationGroup
from askbot.deps.livesettings import BooleanValue
+from django.core.cache import cache
from django.utils.translation import ugettext as _
+def empty_cache_callback(old_value, new_value):
+ """used to clear cache on change of certain values"""
+ if old_value != new_value:
+ #todo: change this to warmup cache
+ cache.clear()
+ return new_value
+
MODERATION = ConfigurationGroup(
'MODERATION',
_('Content moderation'),
@@ -18,5 +26,6 @@ settings.register(
'ENABLE_CONTENT_MODERATION',
default = False,
description = _('Enable content moderation'),
+ update_callback = empty_cache_callback
)
)
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 32ceeaad..dfb6995a 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -176,6 +176,7 @@ TYPE_ACTIVITY_MODERATED_POST_EDIT = 25
TYPE_ACTIVITY_CREATE_REJECT_REASON = 26
TYPE_ACTIVITY_UPDATE_REJECT_REASON = 27
TYPE_ACTIVITY_VALIDATION_EMAIL_SENT = 28
+TYPE_ACTIVITY_POST_SHARED = 29
#TYPE_ACTIVITY_EDIT_QUESTION = 17
#TYPE_ACTIVITY_EDIT_ANSWER = 18
@@ -199,6 +200,7 @@ TYPE_ACTIVITY = (
(TYPE_ACTIVITY_FAVORITE, _('selected favorite')),
(TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')),
(TYPE_ACTIVITY_EMAIL_UPDATE_SENT, _('email update sent to user')),
+ (TYPE_ACTIVITY_POST_SHARED, _('a post was shared')),
(
TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT,
_('reminder about unanswered questions sent'),
@@ -244,6 +246,7 @@ RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS = (
TYPE_ACTIVITY_UPDATE_QUESTION,
TYPE_ACTIVITY_ANSWER,
TYPE_ACTIVITY_ASK_QUESTION,
+ TYPE_ACTIVITY_POST_SHARED
)
@@ -256,6 +259,7 @@ RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY = (
TYPE_ACTIVITY_COMMENT_ANSWER,
TYPE_ACTIVITY_UPDATE_ANSWER,
TYPE_ACTIVITY_UPDATE_QUESTION,
+ TYPE_ACTIVITY_POST_SHARED,
# TYPE_ACTIVITY_PRIZE,
# TYPE_ACTIVITY_MARK_ANSWER,
# TYPE_ACTIVITY_VOTE_UP,
@@ -267,15 +271,15 @@ RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY = (
# TYPE_ACTIVITY_FAVORITE,
)
-
RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES = {
- TYPE_ACTIVITY_COMMENT_QUESTION: 'question_comment',
- TYPE_ACTIVITY_COMMENT_ANSWER: 'answer_comment',
- TYPE_ACTIVITY_UPDATE_ANSWER: 'answer_update',
- TYPE_ACTIVITY_UPDATE_QUESTION: 'question_update',
- TYPE_ACTIVITY_ANSWER: 'new_answer',
- TYPE_ACTIVITY_ASK_QUESTION: 'new_question',
- }
+ TYPE_ACTIVITY_COMMENT_QUESTION: 'question_comment',
+ TYPE_ACTIVITY_COMMENT_ANSWER: 'answer_comment',
+ TYPE_ACTIVITY_UPDATE_ANSWER: 'answer_update',
+ TYPE_ACTIVITY_UPDATE_QUESTION: 'question_update',
+ TYPE_ACTIVITY_ANSWER: 'new_answer',
+ TYPE_ACTIVITY_ASK_QUESTION: 'new_question',
+ TYPE_ACTIVITY_POST_SHARED: 'post_shared'
+}
assert(
set(RESPONSE_ACTIVITY_TYPES_FOR_INSTANT_NOTIFICATIONS) \
diff --git a/askbot/deps/django_authopenid/urls.py b/askbot/deps/django_authopenid/urls.py
index b364aad6..9b97d847 100644
--- a/askbot/deps/django_authopenid/urls.py
+++ b/askbot/deps/django_authopenid/urls.py
@@ -7,13 +7,16 @@ urlpatterns = patterns('askbot.deps.django_authopenid.views',
url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'),
# manage account registration
url(r'^%s$' % _('signin/'), 'signin', name='user_signin'),
+ url(r'^%s$' % _('widget/signin/'), 'signin',
+ {'template_name': 'authopenid/widget_signin.html'},
+ name='widget_signin'),
url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
#this view is "complete-openid" signin
- url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
+ url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
name='user_complete_signin'),
url(
r'^%s%s$' % (_('signin/'), _('complete-oauth/')),
- 'complete_oauth_signin',
+ 'complete_oauth_signin',
name='user_complete_oauth_signin'
),
url(r'^%s$' % _('register/'), 'register', name='user_register'),
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index e10e6626..2b2c586d 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2007, 2008, BenoƮt Chesneau
# Copyright (c) 2007 Simon Willison, original work on django-openid
-#
+#
# All rights reserved.
-#
+#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
-#
+#
# * Redistributions of source code must retain the above copyright
# * notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
@@ -17,7 +17,7 @@
# * of its contributors may be used to endorse or promote products
# * derived from this software without specific prior written
# * permission.
-#
+#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
@@ -136,7 +136,7 @@ def ask_openid(
):
""" basic function to ask openid and return response """
on_failure = on_failure or signin_failure
-
+
trust_root = getattr(
django_settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
)
@@ -166,9 +166,9 @@ def complete(request, on_success=None, on_failure=None, return_to=None):
""" complete openid signin """
assert(on_success is not None)
assert(on_failure is not None)
-
+
logging.debug('in askbot.deps.django_authopenid.complete')
-
+
consumer = Consumer(request.session, util.DjangoOpenIDStore())
# make sure params are encoded in utf8
params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
@@ -178,7 +178,7 @@ def complete(request, on_success=None, on_failure=None, return_to=None):
logging.debug(u'returned openid parameters were: %s' % unicode(params))
except Exception, e:
logging.critical(u'fix logging statement above ' + unicode(e))
-
+
if openid_response.status == SUCCESS:
logging.debug('openid response status is SUCCESS')
return on_success(
@@ -271,13 +271,13 @@ def complete_oauth_signin(request):
#@not_authenticated
@csrf.csrf_protect
-def signin(request):
+def signin(request, template_name='authopenid/signin.html'):
"""
- signin page. It manages the legacy authentification (user/password)
+ signin page. It manages the legacy authentification (user/password)
and openid authentification
-
+
url: /signin/
-
+
template : authopenid/signin.htm
"""
logging.debug('in signin view')
@@ -407,11 +407,11 @@ def signin(request):
sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
redirect_to = "%s%s?%s" % (
get_url_host(request),
- reverse('user_complete_signin'),
+ reverse('user_complete_signin'),
urllib.urlencode({'next':next_url})
)
return ask_openid(
- request,
+ request,
login_form.cleaned_data['openid_url'],
redirect_to,
on_failure=signin_failure,
@@ -482,7 +482,7 @@ def signin(request):
user = authenticate(
method = 'wordpress_site',
wordpress_url = wp.url,
- wp_user_id = wp_user.user_id
+ wp_user_id = wp_user.user_id
)
return finalize_generic_signin(
request = request,
@@ -511,8 +511,9 @@ def signin(request):
return show_signin_view(
request,
login_form = login_form,
- view_subtype = view_subtype
- )
+ view_subtype = view_subtype,
+ template_name=template_name
+ )
@csrf.csrf_protect
def show_signin_view(
@@ -521,7 +522,8 @@ def show_signin_view(
account_recovery_form = None,
account_recovery_message = None,
sticky = False,
- view_subtype = 'default'
+ view_subtype = 'default',
+ template_name='authopenid/signin.html'
):
"""url-less utility function that populates
context of template 'authopenid/signin.html'
@@ -529,12 +531,12 @@ def show_signin_view(
"""
allowed_subtypes = (
- 'default', 'add_openid',
+ 'default', 'add_openid',
'email_sent', 'change_openid',
'bad_key'
)
- assert(view_subtype in allowed_subtypes)
+ assert(view_subtype in allowed_subtypes)
if sticky:
next_url = reverse('user_signin')
@@ -648,7 +650,7 @@ def show_signin_view(
data['existing_login_methods'] = existing_login_methods
active_provider_names = [
item.provider_name for item in existing_login_methods
- ]
+ ]
util.set_login_provider_tooltips(
major_login_providers,
@@ -662,7 +664,7 @@ def show_signin_view(
data['major_login_providers'] = major_login_providers.values()
data['minor_login_providers'] = minor_login_providers.values()
- return render_into_skin('authopenid/signin.html', data, request)
+ return render_into_skin(template_name, data, request)
@login_required
def delete_login_method(request):
@@ -694,8 +696,8 @@ def complete_signin(request):
""" in case of complete signin with openid """
logging.debug('')#blank log just for the trace
return complete(
- request,
- on_success = signin_success,
+ request,
+ on_success = signin_success,
on_failure = signin_failure,
return_to = get_url_host(request) + reverse('user_complete_signin')
)
@@ -737,7 +739,7 @@ def signin_success(request, identity_url, openid_response):
)
def finalize_generic_signin(
- request = None,
+ request = None,
user = None,
login_provider_name = None,
user_identifier = None,
@@ -950,7 +952,7 @@ def register(request, login_provider_name=None, user_identifier=None):
#check if we need to post a question that was added anonymously
#this needs to be a function call becase this is also done
#if user just logged in and did not need to create the new account
-
+
if user != None:
if askbot_settings.EMAIL_VALIDATION == True:
logging.debug('sending email validation')
@@ -964,7 +966,7 @@ def register(request, login_provider_name=None, user_identifier=None):
else:
logging.debug('have really strange error')
raise Exception('openid login failed')#should not ever get here
-
+
providers = {
'yahoo':'<font color="purple">Yahoo!</font>',
'flickr':'<font color="#0063dc">flick</font><font color="#ff0084">r</font>&trade;',
@@ -977,7 +979,7 @@ def register(request, login_provider_name=None, user_identifier=None):
logging.error('openid provider named "%s" has no pretty customized logo' % login_provider_name)
else:
provider_logo = providers[login_provider_name]
-
+
logging.debug('printing authopenid/complete.html output')
data = {
'openid_register_form': register_form,
@@ -1006,7 +1008,7 @@ def signup_with_password(request):
"""Create a password-protected account
template: authopenid/signup_with_password.html
"""
-
+
logging.debug(get_request_info(request))
next = get_next_url(request)
login_form = forms.LoginForm(initial = {'next': next})
@@ -1022,7 +1024,7 @@ def signup_with_password(request):
if request.method == 'POST':
form = RegisterForm(request.POST)
email_feeds_form = askbot_forms.SimpleEmailSubscribeForm(request.POST)
-
+
#validation outside if to remember form values
logging.debug('validating classic register form')
form1_is_valid = form.is_valid()
@@ -1042,7 +1044,7 @@ def signup_with_password(request):
password = form.cleaned_data['password1']
email = form.cleaned_data['email']
provider_name = form.cleaned_data['login_provider']
-
+
new_user = User.objects.create_user(username, email, password)
user_registered.send(None, user = new_user)
@@ -1061,13 +1063,13 @@ def signup_with_password(request):
logging.debug('new user logged in')
email_feeds_form.save(user)
logging.debug('email feeds form saved')
-
+
# send email
#subject = _("Welcome email subject line")
#message_template = get_emplate(
# 'authopenid/confirm_email.txt'
#)
- #message_context = Context({
+ #message_context = Context({
# 'signup_url': askbot_settings.APP_URL + reverse('user_signin'),
# 'username': username,
# 'password': password,
@@ -1096,7 +1098,7 @@ def signup_with_password(request):
minor_login_providers = util.get_enabled_minor_login_providers()
context_data = {
- 'form': form,
+ 'form': form,
'page_class': 'openid-signin',
'email_feeds_form': email_feeds_form,
'major_login_providers': major_login_providers.values(),
@@ -1140,7 +1142,7 @@ XRDF_TEMPLATE = """<?xml version='1.0' encoding='UTF-8'?>
</Service>
</XRD>
</xrds:XRDS>"""
-
+
def xrdf(request):
url_host = get_url_host(request)
return_to = "%s%s" % (url_host, reverse('user_complete_signin'))
@@ -1191,7 +1193,7 @@ def _send_email_key(user):
def send_new_email_key(user,nomessage=False):
import random
random.seed()
- user.email_key = '%032x' % random.getrandbits(128)
+ user.email_key = '%032x' % random.getrandbits(128)
user.save()
_send_email_key(user)
if nomessage==False:
@@ -1207,14 +1209,14 @@ def send_email_key(request):
email sending is called internally
raises 404 if email validation is off
- if current email is valid shows 'key_not_sent' view of
+ if current email is valid shows 'key_not_sent' view of
authopenid/changeemail.html template
"""
if askbot_settings.EMAIL_VALIDATION == True:
if request.user.email_isvalid:
data = {
- 'email': request.user.email,
- 'action_type': 'key_not_sent',
+ 'email': request.user.email,
+ 'action_type': 'key_not_sent',
'change_link': reverse('user_changeemail')
}
return render_into_skin(
@@ -1278,7 +1280,7 @@ def account_recover(request, key = None):
)
else:
return show_signin_view(request, view_subtype = 'bad_key')
-
+
#internal server view used as return value by other views
def validation_email_sent(request):
diff --git a/askbot/deps/livesettings/models.py b/askbot/deps/livesettings/models.py
index 1a57dfc5..71db8acf 100644
--- a/askbot/deps/livesettings/models.py
+++ b/askbot/deps/livesettings/models.py
@@ -25,14 +25,20 @@ def _safe_get_siteid(site):
def find_setting(group, key, site=None):
"""Get a setting or longsetting by group and key, cache and return it."""
-
+
siteid = _safe_get_siteid(site)
setting = None
-
+
use_db, overrides = get_overrides(siteid)
ck = cache_key('Setting', siteid, group, key)
-
- if use_db:
+
+ grp = overrides.get(group, None)
+
+ if grp and key in grp:
+ val = grp[key]
+ setting = ImmutableSetting(key=key, group=group, value=val)
+ log.debug('Returning overridden: %s', setting)
+ elif use_db:
try:
setting = cache_get(ck)
@@ -45,10 +51,10 @@ def find_setting(group, key, site=None):
# maybe it is a "long setting"
try:
setting = LongSetting.objects.get(site__id__exact=siteid, key__exact=key, group__exact=group)
-
+
except LongSetting.DoesNotExist:
pass
-
+
cache_set(ck, value=setting)
else:
@@ -57,13 +63,13 @@ def find_setting(group, key, site=None):
val = grp[key]
setting = ImmutableSetting(key=key, group=group, value=val)
log.debug('Returning overridden: %s', setting)
-
+
if not setting:
raise SettingNotSet(key, cachekey=ck)
return setting
-class SettingNotSet(Exception):
+class SettingNotSet(Exception):
def __init__(self, k, cachekey=None):
self.key = k
self.cachekey = cachekey
@@ -77,22 +83,22 @@ class SettingManager(models.Manager):
class ImmutableSetting(object):
-
+
def __init__(self, group="", key="", value="", site=1):
self.site = site
self.group = group
self.key = key
self.value = value
-
+
def cache_key(self, *args, **kwargs):
return cache_key('OverrideSetting', self.site, self.group, self.key)
-
+
def delete(self):
pass
-
+
def save(self, *args, **kwargs):
pass
-
+
def __repr__(self):
return "ImmutableSetting: %s.%s=%s" % (self.group, self.key, self.value)
@@ -120,11 +126,18 @@ class Setting(models.Model, CachedObjectMixin):
site = self.site
except Site.DoesNotExist:
self.site = Site.objects.get_current()
-
+
super(Setting, self).save(force_insert=force_insert, force_update=force_update)
-
+
self.cache_set()
-
+
+ def cache_set(self, *args, **kwargs):
+ val = kwargs.pop('value', self)
+ key = self.cache_key(*args, **kwargs)
+ #TODO: fix this with Django's > 1.3 CACHE dict setting support
+ length = getattr(settings, 'LIVESETTINGS_CACHE_TIMEOUT', settings.CACHE_TIMEOUT)
+ cache_set(key, value=val, length=length)
+
class Meta:
unique_together = ('site', 'group', 'key')
@@ -149,7 +162,7 @@ class LongSetting(models.Model, CachedObjectMixin):
def cache_key(self, *args, **kwargs):
# note same cache pattern as Setting. This is so we can look up in one check.
- # they can't overlap anyway, so this is moderately safe. At the worst, the
+ # they can't overlap anyway, so this is moderately safe. At the worst, the
# Setting will override a LongSetting.
return cache_key('Setting', self.site, self.group, self.key)
@@ -164,7 +177,14 @@ class LongSetting(models.Model, CachedObjectMixin):
self.site = Site.objects.get_current()
super(LongSetting, self).save(force_insert=force_insert, force_update=force_update)
self.cache_set()
-
+
+ def cache_set(self, *args, **kwargs):
+ val = kwargs.pop('value', self)
+ key = self.cache_key(*args, **kwargs)
+ #TODO: fix this with Django's > 1.3 CACHE dict setting support
+ length = getattr(settings, 'LIVESETTINGS_CACHE_TIMEOUT', settings.CACHE_TIMEOUT)
+ cache_set(key, value=val, length=length)
+
class Meta:
unique_together = ('site', 'group', 'key')
-
+
diff --git a/askbot/deps/livesettings/overrides.py b/askbot/deps/livesettings/overrides.py
index f3dc3355..c2fc09df 100644
--- a/askbot/deps/livesettings/overrides.py
+++ b/askbot/deps/livesettings/overrides.py
@@ -35,7 +35,7 @@ def get_overrides(siteid=-1):
}
}
- In the settings dict above, the "val" entries must exactly match the format
+ In the settings dict above, the "val" entries must exactly match the format
stored in the database for a setting. Do not use a literal True or an integer,
it needs to be the string representation of them.
@@ -45,7 +45,7 @@ def get_overrides(siteid=-1):
if hasattr(djangosettings, 'LIVESETTINGS_OPTIONS'):
if siteid == -1:
siteid = _safe_get_siteid(None)
-
+
opts = djangosettings.LIVESETTINGS_OPTIONS
if opts.has_key(siteid):
opts = opts[siteid]
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index 3d21a907..b312d2f7 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,11 +3,14 @@ Changes in Askbot
Development version
-------------------
-* Updated LDAP configuration: allow protocol change, master login and
adding "extra options" to the ldap session (Evgeny)
* Tag moderation (Evgeny)
* Editable optional three level category selector for the tags (Evgeny)
* Tag editor adding tags as they are typed (Evgeny)
+* Added optional support for unicode slugs (Evgeny)
+* Optionally allow limiting one answer per question per person (Evgeny)
+* Added management command `build_livesettings_cache` (Adolfo)
+* Administrators can post under fictional user accounts without logging out (jtrain, Evgeny)
* Welcome email for the case when replying by email is enabled (Evgeny)
* Detection of email signature based on the response to the welcome email (Evgeny)
* Hide "website" and "about" section of the blocked user profiles
diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst
index 69348b84..ce8656ea 100644
--- a/askbot/doc/source/contributors.rst
+++ b/askbot/doc/source/contributors.rst
@@ -41,6 +41,7 @@ Programming and documentation
* Silvio Heuberger
* `Alexandros <https://github.com/alexandros-z>`_
* `Paul Backhouse <https://github.com/powlo>`_
+* `jtrain <https://github.com/jtrain>`_
Translations
------------
diff --git a/askbot/doc/source/management-commands.rst b/askbot/doc/source/management-commands.rst
index b96251dc..2755bcf5 100644
--- a/askbot/doc/source/management-commands.rst
+++ b/askbot/doc/source/management-commands.rst
@@ -80,6 +80,8 @@ The bulk of the management commands fall into this group and will probably be th
+---------------------------------+-------------------------------------------------------------+
| `build_thread_summary_cache` | Rebuilds cache for the question summary snippet. |
+---------------------------------+-------------------------------------------------------------+
+| `build_livesettings_cache` | Rebuilds cache for the live settings. |
++---------------------------------+-------------------------------------------------------------+
| `delete_contextless_...` | `delete_contextless_badge_award_activities` |
| | Deletes Activity objects of type badge award where the |
| | related context object is lost. |
diff --git a/askbot/doc/source/settings.rst b/askbot/doc/source/settings.rst
new file mode 100644
index 00000000..d07e697b
--- /dev/null
+++ b/askbot/doc/source/settings.rst
@@ -0,0 +1,14 @@
+=================================
+Settings for ``settings.py`` file
+=================================
+
+* ``ALLOW_UNICODE_SLUGS`` - if ``True``, slugs will use unicode, default - ``False``
+
+There are more settings that are not documented yet,
+but most are described in the ``settings.py`` template:
+
+ askbot/setup_templates/settings.py.mustache
+
+TODO: describe all of them here.
+
+
diff --git a/askbot/exceptions.py b/askbot/exceptions.py
index d2d5ddf0..12802e7e 100644
--- a/askbot/exceptions.py
+++ b/askbot/exceptions.py
@@ -19,6 +19,11 @@ class InsufficientReputation(exceptions.PermissionDenied):
"""
pass
+class AnswerAlreadyGiven(exceptions.PermissionDenied):
+ """Raised when user attempts to post a second answer
+ to the same question"""
+ pass
+
class DuplicateCommand(exceptions.PermissionDenied):
"""exception class to indicate that something
that can happen only once was attempted for the second time
diff --git a/askbot/forms.py b/askbot/forms.py
index 11f001e6..7030643a 100644
--- a/askbot/forms.py
+++ b/askbot/forms.py
@@ -1,12 +1,14 @@
+"""Forms, custom form fields and related utility functions
+used in AskBot"""
import re
from django import forms
from askbot import const
from askbot.const import message_keys
+from django.forms.util import ErrorList
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy, string_concat
from django.utils.text import get_text_list
from django.contrib.auth.models import User
-from django.contrib.contenttypes.models import ContentType
from django_countries import countries
from askbot.utils.forms import NextUrlField, UserNameField
from askbot.mail import extract_first_email_address
@@ -16,6 +18,7 @@ from askbot.conf import get_tag_display_filter_strategy_choices
from tinymce.widgets import TinyMCE
import logging
+
def cleanup_dict(dictionary, key, empty_value):
"""deletes key from dictionary if it exists
and the corresponding value equals the empty_value
@@ -23,13 +26,27 @@ def cleanup_dict(dictionary, key, empty_value):
if key in dictionary and dictionary[key] == empty_value:
del dictionary[key]
+
def format_form_errors(form):
+ """Formats form errors in HTML
+ if there is only one error - returns a plain string
+ if more than one, returns an unordered list of errors
+ in HTML format.
+ If there are no errors, returns empty string
+ """
if form.errors:
errors = form.errors.values()
if len(errors) == 1:
return errors[0]
else:
- return 'hahahahah'
+ result = '<ul>'
+ for error in errors:
+ result += '<li>%s</li>' % error
+ result += '</ul>'
+ return result
+ else:
+ return ''
+
def clean_marked_tagnames(tagnames):
"""return two strings - one containing tagnames
@@ -38,7 +55,7 @@ def clean_marked_tagnames(tagnames):
wildcard tags are those that have an asterisk at the end
the function does not verify that the tag names are valid
"""
- if askbot_settings.USE_WILDCARD_TAGS == False:
+ if askbot_settings.USE_WILDCARD_TAGS is False:
return tagnames, list()
pure_tags = list()
@@ -56,7 +73,8 @@ def clean_marked_tagnames(tagnames):
return pure_tags, wildcards
-def filter_choices(remove_choices = None, from_choices = None):
+
+def filter_choices(remove_choices=None, from_choices=None):
"""a utility function that will remove choice tuples
usable for the forms.ChoicesField from
``from_choices``, the removed ones will be those given
@@ -76,12 +94,47 @@ def filter_choices(remove_choices = None, from_choices = None):
if choice == choice_to_test[0]:
remove = True
break
- if remove == False:
- filtered_choices += ( choice_to_test, )
+ if remove is False:
+ filtered_choices += (choice_to_test, )
return filtered_choices
-COUNTRY_CHOICES = (('unknown',_('select country')),) + countries.COUNTRIES
+
+def need_mandatory_tags():
+ """true, if list of mandatory tags is not empty"""
+ from askbot import models
+ return (
+ askbot_settings.TAGS_ARE_REQUIRED
+ and len(models.tag.get_mandatory_tags()) > 0
+ )
+
+
+def mandatory_tag_missing_in_list(tag_strings):
+ """true, if mandatory tag is not present in the list
+ of ``tag_strings``"""
+ from askbot import models
+ mandatory_tags = models.tag.get_mandatory_tags()
+ for mandatory_tag in mandatory_tags:
+ for tag_string in tag_strings:
+ if tag_strings_match(tag_string, mandatory_tag):
+ return False
+ return True
+
+
+def tag_strings_match(tag_string, mandatory_tag):
+ """true if tag string matches the mandatory tag,
+ the comparison is not symmetric if tag_string ends with a
+ wildcard (asterisk)
+ """
+ if mandatory_tag.endswith('*'):
+ return tag_string.startswith(mandatory_tag[:-1])
+ else:
+ return tag_string == mandatory_tag
+
+
+
+COUNTRY_CHOICES = (('unknown', _('select country')),) + countries.COUNTRIES
+
class CountryField(forms.ChoiceField):
"""this is better placed into the django_coutries app"""
@@ -103,10 +156,13 @@ class CountryField(forms.ChoiceField):
return None
return value
+
class CountedWordsField(forms.CharField):
-
+ """a field where a number of words is expected
+ to be in a certain range"""
+
def __init__(
- self, min_words = 0, max_words = 9999, field_name = None,
+ self, min_words=0, max_words=9999, field_name=None,
*args, **kwargs
):
self.min_words = min_words
@@ -147,28 +203,38 @@ class CountedWordsField(forms.CharField):
class DomainNameField(forms.CharField):
+ """Field for Internet Domain Names
+ todo: maybe there is a standard field for this?
+ """
def clean(self, value):
#find a better regex, taking into account tlds
domain_re = re.compile(r'[a-zA-Z\d]+(\.[a-zA-Z\d]+)+')
if domain_re.match(value):
return value
else:
- raise forms.ValidationError('%s is not a valid domain name' % value)
+ raise forms.ValidationError(
+ '%s is not a valid domain name' % value
+ )
class TitleField(forms.CharField):
+ """Fild receiving question title"""
def __init__(self, *args, **kwargs):
super(TitleField, self).__init__(*args, **kwargs)
- self.required = True
+ self.required = kwargs.get('required', True)
self.widget = forms.TextInput(
- attrs={'size' : 70, 'autocomplete' : 'off'}
- )
+ attrs={'size': 70, 'autocomplete': 'off'}
+ )
self.max_length = 255
- self.label = _('title')
- self.help_text = _('Please enter a descriptive title for your question')
+ self.label = _('title')
+ self.help_text = _(
+ 'please enter a descriptive title for your question'
+ )
self.initial = ''
def clean(self, value):
+ """cleans the field for minimum and maximum length
+ also is supposed to work for unicode non-ascii characters"""
if value is None:
value = ''
if len(value) < askbot_settings.MIN_TITLE_LENGTH:
@@ -195,15 +261,16 @@ class TitleField(forms.CharField):
) % self.max_length
)
- return value.strip() # TODO: test me
+ return value.strip() # TODO: test me
+
class EditorField(forms.CharField):
- """EditorField is subclassed by the
+ """EditorField is subclassed by the
:class:`QuestionEditorField` and :class:`AnswerEditorField`
"""
length_error_template_singular = 'post content must be > %d character',
length_error_template_plural = 'post content must be > %d characters',
- min_length = 10#sentinel default value
+ min_length = 10 # sentinel default value
def __init__(self, *args, **kwargs):
super(EditorField, self).__init__(*args, **kwargs)
@@ -225,24 +292,33 @@ class EditorField(forms.CharField):
self.length_error_template_singular,
self.length_error_template_plural,
self.min_length
- ) % self.min_length
+ ) % self.min_length
raise forms.ValidationError(msg)
return value
+
class QuestionEditorField(EditorField):
+ """Editor field for the questions"""
+
def __init__(self, *args, **kwargs):
super(QuestionEditorField, self).__init__(*args, **kwargs)
- self.length_error_template_singular = 'question body must be > %d character'
- self.length_error_template_plural = 'question body must be > %d characters'
+ self.length_error_template_singular = \
+ 'question body must be > %d character'
+ self.length_error_template_plural = \
+ 'question body must be > %d characters'
self.min_length = askbot_settings.MIN_QUESTION_BODY_LENGTH
+
class AnswerEditorField(EditorField):
+ """Editor field for answers"""
+
def __init__(self, *args, **kwargs):
super(AnswerEditorField, self).__init__(*args, **kwargs)
self.length_error_template_singular = 'answer must be > %d character'
self.length_error_template_plural = 'answer must be > %d characters'
self.min_length = askbot_settings.MIN_ANSWER_BODY_LENGTH
+
def clean_tag(tag_name):
"""a function that cleans a single tag name"""
tag_length = len(tag_name)
@@ -254,7 +330,7 @@ def clean_tag(tag_name):
'each tag must be shorter than %(max_chars)d character',
'each tag must be shorter than %(max_chars)d characters',
tag_length
- ) % {'max_chars':tag_length}
+ ) % {'max_chars': tag_length}
raise forms.ValidationError(msg)
#todo - this needs to come from settings
@@ -270,19 +346,21 @@ def clean_tag(tag_name):
else:
try:
from askbot import models
- stored_tag = models.Tag.objects.get(name__iexact = tag_name)
+ stored_tag = models.Tag.objects.get(name__iexact=tag_name)
return stored_tag.name
except models.Tag.DoesNotExist:
return tag_name
class TagNamesField(forms.CharField):
+ """field that receives AskBot tag names"""
+
def __init__(self, *args, **kwargs):
super(TagNamesField, self).__init__(*args, **kwargs)
self.required = askbot_settings.TAGS_ARE_REQUIRED
self.widget = forms.TextInput(
- attrs={'size' : 50, 'autocomplete' : 'off'}
- )
+ attrs={'size': 50, 'autocomplete': 'off'}
+ )
self.max_length = 255
self.label = _('tags')
self.help_text = ungettext_lazy(
@@ -294,41 +372,18 @@ class TagNamesField(forms.CharField):
) % {'max_tags': askbot_settings.MAX_TAGS_PER_POST}
self.initial = ''
- def need_mandatory_tags(self):
- """true, if list of mandatory tags is not empty"""
- from askbot import models
- num_mandatory_tags = len(models.tag.get_mandatory_tags())
- return askbot_settings.TAGS_ARE_REQUIRED and num_mandatory_tags > 0
-
- def tag_string_matches(self, tag_string, mandatory_tag):
- """true if tag string matches the mandatory tag"""
- if mandatory_tag.endswith('*'):
- return tag_string.startswith(mandatory_tag[:-1])
- else:
- return tag_string == mandatory_tag
-
- def mandatory_tag_missing(self, tag_strings):
- """true, if mandatory tag is not present in the list
- of ``tag_strings``"""
- from askbot import models
- mandatory_tags = models.tag.get_mandatory_tags()
- for mandatory_tag in mandatory_tags:
- for tag_string in tag_strings:
- if self.tag_string_matches(tag_string, mandatory_tag):
- return False
- return True
-
def clean(self, value):
from askbot import models
value = super(TagNamesField, self).clean(value)
data = value.strip()
if len(data) < 1:
if askbot_settings.TAGS_ARE_REQUIRED:
- raise forms.ValidationError(
- _(message_keys.TAGS_ARE_REQUIRED_MESSAGE)
- )
+ raise forms.ValidationError(
+ _(message_keys.TAGS_ARE_REQUIRED_MESSAGE)
+ )
else:
- return ''#don't test for required characters when tags is ''
+ #don't test for required characters when tags is ''
+ return ''
split_re = re.compile(const.TAG_SPLIT_REGEX)
tag_strings = split_re.split(data)
entered_tags = []
@@ -338,11 +393,11 @@ class TagNamesField(forms.CharField):
msg = ungettext_lazy(
'please use %(tag_count)d tag or less',
'please use %(tag_count)d tags or less',
- tag_count) % {'tag_count':max_tags}
+ tag_count) % {'tag_count': max_tags}
raise forms.ValidationError(msg)
- if self.need_mandatory_tags():
- if self.mandatory_tag_missing(tag_strings):
+ if need_mandatory_tags():
+ if mandatory_tag_missing_in_list(tag_strings):
msg = _(
'At least one of the following tags is required : %(tags)s'
) % {'tags': get_text_list(models.tag.get_mandatory_tags())}
@@ -356,30 +411,54 @@ class TagNamesField(forms.CharField):
return u' '.join(cleaned_entered_tags)
+
class WikiField(forms.BooleanField):
+ """Rendered as checkbox turning post into
+ "community wiki"
+ """
+
def __init__(self, *args, **kwargs):
super(WikiField, self).__init__(*args, **kwargs)
self.required = False
self.initial = False
- self.label = _('community wiki (karma is not awarded & many others can edit wiki post)')
- self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
+ self.label = _(
+ 'community wiki (karma is not awarded & '
+ 'many others can edit wiki post)'
+ )
+ self.help_text = _(
+ 'if you choose community wiki option, the question '
+ 'and answer do not generate points and name of '
+ 'author will not be shown'
+ )
+
def clean(self, value):
return value and askbot_settings.WIKI_ON
+
class EmailNotifyField(forms.BooleanField):
+ """Rendered as checkbox which turns on
+ email notifications on the post"""
def __init__(self, *args, **kwargs):
super(EmailNotifyField, self).__init__(*args, **kwargs)
self.required = False
self.widget.attrs['class'] = 'nomargin'
+
class SummaryField(forms.CharField):
+
def __init__(self, *args, **kwargs):
super(SummaryField, self).__init__(*args, **kwargs)
self.required = False
- self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
+ self.widget = forms.TextInput(
+ attrs={'size': 50, 'autocomplete': 'off'}
+ )
self.max_length = 300
- self.label = _('update summary:')
- self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
+ self.label = _('update summary:')
+ self.help_text = _(
+ 'enter a brief summary of your revision (e.g. '
+ 'fixed spelling, grammar, improved style, this '
+ 'field is optional)'
+ )
class DumpUploadForm(forms.Form):
@@ -389,6 +468,7 @@ class DumpUploadForm(forms.Form):
"""
dump_file = forms.FileField()
+
class ShowQuestionForm(forms.Form):
"""Cleans data necessary to access answers and comments
by the respective comment or answer id - necessary
@@ -396,10 +476,10 @@ class ShowQuestionForm(forms.Form):
on the page other than the first page of answers to a question.
Same for the answers that are shown on the later pages.
"""
- answer = forms.IntegerField(required = False)
- comment = forms.IntegerField(required = False)
- page = forms.IntegerField(required = False)
- sort = forms.CharField(required = False)
+ answer = forms.IntegerField(required=False)
+ comment = forms.IntegerField(required=False)
+ page = forms.IntegerField(required=False)
+ sort = forms.CharField(required=False)
def __init__(self, data, default_sort_method):
super(ShowQuestionForm, self).__init__(data)
@@ -443,6 +523,7 @@ class ShowQuestionForm(forms.Form):
self.cleaned_data = out_data
return out_data
+
class ChangeUserReputationForm(forms.Form):
"""Form that allows moderators and site administrators
to adjust reputation of users.
@@ -452,13 +533,15 @@ class ChangeUserReputationForm(forms.Form):
"""
user_reputation_delta = forms.IntegerField(
- min_value = 1,
- label = _('Enter number of points to add or subtract')
- )
- comment = forms.CharField(max_length = 128)
+ min_value=1,
+ label=_(
+ 'Enter number of points to add or subtract'
+ )
+ )
+ comment = forms.CharField(max_length=128)
def clean_comment(self):
- if 'comment' in self.cleaned_data:
+ if 'comment' in self.cleaned_data:
comment = self.cleaned_data['comment'].strip()
if comment == '':
del self.cleaned_data['comment']
@@ -492,9 +575,7 @@ class ChangeUserStatusForm(forms.Form):
"moderation" tab
"""
- user_status = forms.ChoiceField(
- label = _('Change status to'),
- )
+ user_status = forms.ChoiceField(label=_('Change status to'))
def __init__(self, *arg, **kwarg):
@@ -515,12 +596,12 @@ class ChangeUserStatusForm(forms.Form):
#remove current status of the "subject" user from choices
user_status_choices = filter_choices(
- remove_choices = [subject.status, ],
- from_choices = user_status_choices
+ remove_choices=[subject.status, ],
+ from_choices=user_status_choices
)
#add prompt option
- user_status_choices = ( ('select', _('which one?')), ) \
+ user_status_choices = (('select', _('which one?')), ) \
+ user_status_choices
self.fields['user_status'].choices = user_status_choices
@@ -568,41 +649,39 @@ class ChangeUserStatusForm(forms.Form):
msg = _(
'If you wish to change %(username)s\'s status, '
'please make a meaningful selection.'
- ) % {'username': self.subject.username }
+ ) % {'username': self.subject.username}
raise forms.ValidationError(msg)
return self.cleaned_data
+
class SendMessageForm(forms.Form):
subject_line = forms.CharField(
- label = _('Subject line'),
- max_length = 64,
- widget = forms.TextInput(
- attrs = {'size':64},
- )
- )
+ label=_('Subject line'),
+ max_length=64,
+ widget=forms.TextInput(attrs={'size': 64}, )
+ )
body_text = forms.CharField(
- label = _('Message text'),
- max_length = 1600,
- widget = forms.Textarea(
- attrs = {'cols':64}
- )
+ label=_('Message text'),
+ max_length=1600,
+ widget=forms.Textarea(attrs={'cols': 64})
)
class NotARobotForm(forms.Form):
recaptcha = RecaptchaField(
- private_key = askbot_settings.RECAPTCHA_SECRET,
- public_key = askbot_settings.RECAPTCHA_KEY
+ private_key=askbot_settings.RECAPTCHA_SECRET,
+ public_key=askbot_settings.RECAPTCHA_KEY
)
+
class FeedbackForm(forms.Form):
name = forms.CharField(label=_('Your name (optional):'), required=False)
email = forms.EmailField(label=_('Email:'), required=False)
message = forms.CharField(
label=_('Your message:'),
max_length=800,
- widget=forms.Textarea(attrs={'cols':60})
+ widget=forms.Textarea(attrs={'cols': 60})
)
no_email = forms.BooleanField(
label=_("I don't want to give my email or receive a response:"),
@@ -619,20 +698,22 @@ class FeedbackForm(forms.Form):
def _add_recaptcha_field(self):
self.fields['recaptcha'] = RecaptchaField(
- private_key = askbot_settings.RECAPTCHA_SECRET,
- public_key = askbot_settings.RECAPTCHA_KEY
- )
+ private_key=askbot_settings.RECAPTCHA_SECRET,
+ public_key=askbot_settings.RECAPTCHA_KEY
+ )
def clean(self):
super(FeedbackForm, self).clean()
if not self.is_auth:
- if not self.cleaned_data['no_email'] and not self.cleaned_data['email']:
+ if not self.cleaned_data['no_email'] \
+ and not self.cleaned_data['email']:
msg = _('Please mark "I dont want to give my mail" field.')
self._errors['email'] = self.error_class([msg])
return self.cleaned_data
-class FormWithHideableFields(forms.Form):
+
+class FormWithHideableFields(object):
"""allows to swap a field widget to HiddenInput() and back"""
def hide_field(self, name):
@@ -653,7 +734,8 @@ class FormWithHideableFields(forms.Form):
if name in self.__hidden_fields:
self.fields[name] = self.__hidden_fields.pop(name)
-class PostPrivatelyForm(FormWithHideableFields):
+
+class PostPrivatelyForm(forms.Form, FormWithHideableFields):
"""has a single field `post_privately` with
two related methods"""
@@ -681,7 +763,93 @@ class PostPrivatelyForm(FormWithHideableFields):
return self.cleaned_data['post_privately']
-class AskForm(PostPrivatelyForm):
+class DraftQuestionForm(forms.Form):
+ """No real validation required for this form"""
+ title = forms.CharField(required=False)
+ text = forms.CharField(required=False)
+ tagnames = forms.CharField(required=False)
+
+
+class DraftAnswerForm(forms.Form):
+ """Only thread_id is required"""
+ thread_id = forms.IntegerField()
+ text = forms.CharField(required=False)
+
+
+class PostAsSomeoneForm(forms.Form):
+ post_author_username = forms.CharField(
+ initial=_('User name:'),
+ help_text=_(
+ 'Enter name to post on behalf of someone else. '
+ 'Can create new accounts.'
+ ),
+ required=False,
+ widget=forms.TextInput(attrs={'class': 'tipped-input'})
+ )
+ post_author_email = forms.CharField(
+ initial=_('Email address:'),
+ required=False,
+ widget=forms.TextInput(attrs={'class': 'tipped-input'})
+ )
+
+ def get_post_user(self, user):
+ """returns user on whose behalf the post or a revision
+ is being made
+ """
+ username = self.cleaned_data['post_author_username']
+ email= self.cleaned_data['post_author_email']
+ if user.is_administrator() and username and email:
+ post_user = user.get_or_create_fake_user(username, email)
+ else:
+ post_user = user
+ return post_user
+
+ def clean_post_author_username(self):
+ """if value is the same as initial, it is reset to
+ empty string
+ todo: maybe better to have field where initial value is invalid,
+ then we would not have to have two almost identical clean functions?
+ """
+ username = self.cleaned_data.get('post_author_username', '')
+ initial_username = unicode(self.fields['post_author_username'].initial)
+ if username == initial_username:
+ self.cleaned_data['post_author_username'] = ''
+ return self.cleaned_data['post_author_username']
+
+ def clean_post_author_email(self):
+ """if value is the same as initial, it is reset to
+ empty string"""
+ email = self.cleaned_data.get('post_author_email', '')
+ initial_email = unicode(self.fields['post_author_email'].initial)
+ if email == initial_email:
+ email = ''
+ if email != '':
+ email = forms.EmailField().clean(email)
+ self.cleaned_data['post_author_email'] = email
+ return email
+
+ def clean(self):
+ """requires email address if user name is given"""
+ username = self.cleaned_data.get('post_author_username', '')
+ email = self.cleaned_data.get('post_author_email', '')
+ if username == '' and email:
+ username_errors = self._errors.get(
+ 'post_author_username',
+ ErrorList()
+ )
+ username_errors.append(_('User name is required with the email'))
+ self._errors['post_author_username'] = username_errors
+ raise forms.ValidationError('missing user name')
+ elif email == '' and username:
+ email_errors = self._errors.get('post_author_email', ErrorList())
+ email_errors.append(_('Email is required if user name is added'))
+ self._errors['post_author_email'] = email_errors
+ raise forms.ValidationError('missing email')
+
+ return self.cleaned_data
+
+
+class AskForm(PostAsSomeoneForm, PostPrivatelyForm):
"""the form used to askbot questions
field ask_anonymously is shown to the user if the
if ALLOW_ASK_ANONYMOUSLY live setting is True
@@ -692,31 +860,32 @@ class AskForm(PostPrivatelyForm):
title = TitleField()
tags = TagNamesField()
wiki = WikiField()
+ group_id = forms.IntegerField(required = False, widget = forms.HiddenInput)
ask_anonymously = forms.BooleanField(
- label = _('ask anonymously'),
- help_text = _(
+ label=_('ask anonymously'),
+ help_text=_(
'Check if you do not want to reveal your name '
'when asking this question'
),
- required = False,
+ required=False,
+ )
+ openid = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 40, 'class': 'openid-input'})
)
-
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
def __init__(self, *args, **kwargs):
super(AskForm, self).__init__(*args, **kwargs)
#it's important that this field is set up dynamically
self.fields['text'] = QuestionEditorField()
#hide ask_anonymously field
- if askbot_settings.ALLOW_ASK_ANONYMOUSLY == False:
+ if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False:
self.hide_field('ask_anonymously')
def clean_ask_anonymously(self):
"""returns false if anonymous asking is not allowed
"""
- if askbot_settings.ALLOW_ASK_ANONYMOUSLY == False:
+ if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False:
self.cleaned_data['ask_anonymously'] = False
return self.cleaned_data['ask_anonymously']
@@ -726,6 +895,25 @@ ASK_BY_EMAIL_SUBJECT_HELP = _(
'[tag1, tag2, tag3,...] question title'
)
+class AskWidgetForm(forms.Form, FormWithHideableFields):
+ '''Simple form with just the title to ask a question'''
+
+ title = TitleField()
+ ask_anonymously = forms.BooleanField(
+ label=_('ask anonymously'),
+ help_text=_(
+ 'Check if you do not want to reveal your name '
+ 'when asking this question'
+ ),
+ required=False,
+ )
+
+ def __init__(self, *args, **kwargs):
+ super(AskWidgetForm, self).__init__(*args, **kwargs)
+ #hide ask_anonymously field
+ if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False:
+ self.hide_field('ask_anonymously')
+
class AskByEmailForm(forms.Form):
""":class:`~askbot.forms.AskByEmailForm`
validates question data, where question was posted
@@ -744,12 +932,13 @@ class AskByEmailForm(forms.Form):
* ``email`` - email address
* ``title`` - question title
* ``tagnames`` - tag names all in one string
- * ``body_text`` - body of question text - a pass-through, no extra validation
+ * ``body_text`` - body of question text -
+ a pass-through, no extra validation
"""
- sender = forms.CharField(max_length = 255)
+ sender = forms.CharField(max_length=255)
subject = forms.CharField(
- max_length = 255,
- error_messages = {
+ max_length=255,
+ error_messages={
'required': ASK_BY_EMAIL_SUBJECT_HELP
}
)
@@ -781,17 +970,19 @@ class AskByEmailForm(forms.Form):
match = subject_re.match(raw_subject)
if match:
#make raw tags comma-separated
- if match.group(1) is None:#no tags
+ if match.group(1) is None: # no tags
self.cleaned_data['tagnames'] = ''
else:
- tagnames = match.group(1).replace(';',',')
+ tagnames = match.group(1).replace(';', ',')
#pre-process tags
tag_list = [tag.strip() for tag in tagnames.split(',')]
tag_list = [re.sub(r'\s+', ' ', tag) for tag in tag_list]
+
if askbot_settings.REPLACE_SPACE_WITH_DASH_IN_EMAILED_TAGS:
tag_list = [tag.replace(' ', '-') for tag in tag_list]
- tagnames = ' '.join(tag_list)#todo: use tag separator char here
+ #todo: use tag separator char here
+ tagnames = ' '.join(tag_list)
#clean tags - may raise ValidationError
self.cleaned_data['tagnames'] = TagNamesField().clean(tagnames)
@@ -803,23 +994,29 @@ class AskByEmailForm(forms.Form):
raise forms.ValidationError(ASK_BY_EMAIL_SUBJECT_HELP)
return self.cleaned_data['subject']
-class AnswerForm(PostPrivatelyForm):
- wiki = WikiField()
- openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
- user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
- email_notify = EmailNotifyField(initial = False)
+
+class AnswerForm(PostAsSomeoneForm, PostPrivatelyForm):
+ text = AnswerEditorField()
+ wiki = WikiField()
+ openid = forms.CharField(
+ required=False, max_length=255,
+ widget=forms.TextInput(attrs={'size': 40, 'class': 'openid-input'})
+ )
+ email_notify = EmailNotifyField(initial=False)
+
def __init__(self, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
- self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates'
- #it is important to add this field dynamically
self.fields['text'] = AnswerEditorField()
+ self.fields['email_notify'].widget.attrs['id'] = \
+ 'question-subscribe-updates'
+
class VoteForm(forms.Form):
"""form used in ajax vote view (only comment_upvote so far)
"""
post_id = forms.IntegerField()
- cancel_vote = forms.CharField()#char because it is 'true' or 'false' as string
+ # char because it is 'true' or 'false' as string
+ cancel_vote = forms.CharField()
def clean_cancel_vote(self):
val = self.cleaned_data['cancel_vote']
@@ -829,7 +1026,9 @@ class VoteForm(forms.Form):
result = False
else:
del self.cleaned_data['cancel_vote']
- raise forms.ValidationError('either "true" or "false" strings expected')
+ raise forms.ValidationError(
+ 'either "true" or "false" strings expected'
+ )
self.cleaned_data['cancel_vote'] = result
return self.cleaned_data['cancel_vote']
@@ -837,42 +1036,55 @@ class VoteForm(forms.Form):
class CloseForm(forms.Form):
reason = forms.ChoiceField(choices=const.CLOSE_REASONS)
+
class RetagQuestionForm(forms.Form):
tags = TagNamesField()
- # initialize the default values
+
def __init__(self, question, *args, **kwargs):
+ """initialize the default values"""
super(RetagQuestionForm, self).__init__(*args, **kwargs)
self.fields['tags'].initial = question.thread.tagnames
+
class RevisionForm(forms.Form):
"""
Lists revisions of a Question or Answer
"""
- revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'}))
+ revision = forms.ChoiceField(
+ widget=forms.Select(
+ attrs={'style': 'width:520px'}
+ )
+ )
def __init__(self, post, latest_revision, *args, **kwargs):
super(RevisionForm, self).__init__(*args, **kwargs)
revisions = post.revisions.values_list(
- 'revision', 'author__username', 'revised_at', 'summary')
+ 'revision', 'author__username', 'revised_at', 'summary'
+ )
date_format = '%c'
- self.fields['revision'].choices = [
- (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3]))
- for r in revisions]
+ rev_choices = list()
+ for r in revisions:
+ rev_details = u'%s - %s (%s) %s' % (
+ r[0], r[1], r[2].strftime(date_format), r[3]
+ )
+ rev_choices.append((r[0], rev_details))
+
+ self.fields['revision'].choices = rev_choices
self.fields['revision'].initial = latest_revision.revision
-class EditQuestionForm(PostPrivatelyForm):
+class EditQuestionForm(PostAsSomeoneForm, PostPrivatelyForm):
title = TitleField()
tags = TagNamesField()
summary = SummaryField()
wiki = WikiField()
reveal_identity = forms.BooleanField(
- help_text = _(
+ help_text=_(
'You have asked this question anonymously, '
'if you decide to reveal your identity, please check '
'this box.'
),
- label = _('reveal identity'),
- required = False,
+ label=_('reveal identity'),
+ required=False,
)
#todo: this is odd that this form takes question as an argument
@@ -904,8 +1116,8 @@ class EditQuestionForm(PostPrivatelyForm):
def can_stay_anonymous(self):
"""determines if the user cat keep editing the question
anonymously"""
- return (askbot_settings.ALLOW_ASK_ANONYMOUSLY \
- and self.question.is_anonymous \
+ return (askbot_settings.ALLOW_ASK_ANONYMOUSLY
+ and self.question.is_anonymous
and self.user.is_owner_of(self.question)
)
@@ -931,7 +1143,7 @@ class EditQuestionForm(PostPrivatelyForm):
"""
value = self.cleaned_data['reveal_identity']
if self.question.is_anonymous:
- if value == True:
+ if value is True:
if self.user.is_owner_of(self.question):
#regardless of the ALLOW_ASK_ANONYMOUSLY
return True
@@ -949,7 +1161,7 @@ class EditQuestionForm(PostPrivatelyForm):
else:
can_ask_anon = askbot_settings.ALLOW_ASK_ANONYMOUSLY
is_owner = self.user.is_owner_of(self.question)
- if can_ask_anon == False and is_owner:
+ if can_ask_anon is False and is_owner:
self.show_field('reveal_identity')
raise forms.ValidationError(
_(
@@ -971,14 +1183,15 @@ class EditQuestionForm(PostPrivatelyForm):
field edit_anonymously. It relies on correct cleaning
if the "reveal_identity" field
"""
+ super(EditQuestionForm, self).clean()
reveal_identity = self.cleaned_data.get('reveal_identity', False)
stay_anonymous = False
- if reveal_identity == False and self.can_stay_anonymous():
+ if reveal_identity is False and self.can_stay_anonymous():
stay_anonymous = True
self.cleaned_data['stay_anonymous'] = stay_anonymous
return self.cleaned_data
-class EditAnswerForm(PostPrivatelyForm):
+class EditAnswerForm(PostAsSomeoneForm, PostPrivatelyForm):
summary = SummaryField()
wiki = WikiField()
@@ -1002,39 +1215,40 @@ class EditAnswerForm(PostPrivatelyForm):
class EditTagWikiForm(forms.Form):
- text = forms.CharField(required = False)
+ text = forms.CharField(required=False)
tag_id = forms.IntegerField()
+
class EditUserForm(forms.Form):
email = forms.EmailField(
label=u'Email',
required=True,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
realname = forms.CharField(
label=_('Real name'),
required=False,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
website = forms.URLField(
label=_('Website'),
required=False,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
city = forms.CharField(
label=_('City'),
required=False,
max_length=255,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
- country = CountryField(required = False)
+ country = CountryField(required=False)
show_country = forms.BooleanField(
label=_('Show country'),
@@ -1048,15 +1262,18 @@ class EditUserForm(forms.Form):
birthday = forms.DateField(
label=_('Date of birth'),
- help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'),
+ help_text=_(
+ 'will not be shown, used to calculate '
+ 'age, format: YYYY-MM-DD'
+ ),
required=False,
- widget=forms.TextInput(attrs={'size' : 35})
+ widget=forms.TextInput(attrs={'size': 35})
)
about = forms.CharField(
label=_('Profile'),
required=False,
- widget=forms.Textarea(attrs={'cols' : 60})
+ widget=forms.Textarea(attrs={'cols': 60})
)
def __init__(self, user, *args, **kwargs):
@@ -1070,7 +1287,7 @@ class EditUserForm(forms.Form):
self.fields['realname'].initial = user.real_name
self.fields['website'].initial = user.website
self.fields['city'].initial = user.location
- if user.country == None:
+ if user.country is None:
country = 'unknown'
else:
country = user.country
@@ -1088,17 +1305,24 @@ class EditUserForm(forms.Form):
"""For security reason one unique email in database"""
if self.user.email != self.cleaned_data['email']:
#todo dry it, there is a similar thing in openidauth
- if askbot_settings.EMAIL_UNIQUE == True:
+ if askbot_settings.EMAIL_UNIQUE is True:
if 'email' in self.cleaned_data:
try:
- User.objects.get(email = self.cleaned_data['email'])
+ User.objects.get(email=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
except User.MultipleObjectsReturned:
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
- raise forms.ValidationError(_('this email has already been registered, please use another one'))
+ raise forms.ValidationError(_(
+ 'this email has already been registered, '
+ 'please use another one')
+ )
+ raise forms.ValidationError(_(
+ 'this email has already been registered, '
+ 'please use another one')
+ )
return self.cleaned_data['email']
+
class TagFilterSelectionForm(forms.ModelForm):
email_tag_filter_strategy = forms.ChoiceField(
initial = const.EXCLUDE_IGNORED,
@@ -1126,31 +1350,31 @@ class TagFilterSelectionForm(forms.ModelForm):
class EmailFeedSettingField(forms.ChoiceField):
def __init__(self, *arg, **kwarg):
kwarg['choices'] = const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES
- #kwarg['initial'] = askbot_settings.DEFAULT_NOTIFICATION_DELIVERY_SCHEDULE
kwarg['widget'] = forms.RadioSelect
super(EmailFeedSettingField, self).__init__(*arg, **kwarg)
+
class EditUserEmailFeedsForm(forms.Form):
FORM_TO_MODEL_MAP = {
- 'all_questions':'q_all',
- 'asked_by_me':'q_ask',
- 'answered_by_me':'q_ans',
- 'individually_selected':'q_sel',
- 'mentions_and_comments':'m_and_c',
+ 'all_questions': 'q_all',
+ 'asked_by_me': 'q_ask',
+ 'answered_by_me': 'q_ans',
+ 'individually_selected': 'q_sel',
+ 'mentions_and_comments': 'm_and_c',
}
NO_EMAIL_INITIAL = {
- 'all_questions':'n',
- 'asked_by_me':'n',
- 'answered_by_me':'n',
- 'individually_selected':'n',
- 'mentions_and_comments':'n',
+ 'all_questions': 'n',
+ 'asked_by_me': 'n',
+ 'answered_by_me': 'n',
+ 'individually_selected': 'n',
+ 'mentions_and_comments': 'n',
}
INSTANT_EMAIL_INITIAL = {
- 'all_questions':'i',
- 'asked_by_me':'i',
- 'answered_by_me':'i',
- 'individually_selected':'i',
- 'mentions_and_comments':'i',
+ 'all_questions': 'i',
+ 'asked_by_me': 'i',
+ 'answered_by_me': 'i',
+ 'individually_selected': 'i',
+ 'mentions_and_comments': 'i',
}
asked_by_me = EmailFeedSettingField(
@@ -1173,7 +1397,7 @@ class EditUserEmailFeedsForm(forms.Form):
def set_initial_values(self, user=None):
from askbot import models
KEY_MAP = dict([(v, k) for k, v in self.FORM_TO_MODEL_MAP.iteritems()])
- if user != None:
+ if user is not None:
settings = models.EmailFeedSetting.objects.filter(subscriber=user)
initial_values = {}
for setting in settings:
@@ -1203,7 +1427,7 @@ class EditUserEmailFeedsForm(forms.Form):
"""
return self.FORM_TO_MODEL_MAP.values()
- def set_frequency(self, frequency = 'n'):
+ def set_frequency(self, frequency='n'):
data = {
'all_questions': frequency,
'asked_by_me': frequency,
@@ -1215,9 +1439,9 @@ class EditUserEmailFeedsForm(forms.Form):
self.cleaned_data = data
self.initial = data
- def save(self,user,save_unbound=False):
- """
- with save_unbound==True will bypass form validation and save initial values
+ def save(self, user, save_unbound=False):
+ """with save_unbound==True will bypass form
+ validation and save initial values
"""
from askbot import models
changed = False
@@ -1245,23 +1469,25 @@ class EditUserEmailFeedsForm(forms.Form):
user.followed_threads.clear()
return changed
+
class SubscribeForEmailUpdatesField(forms.ChoiceField):
"""a simple yes or no field to subscribe for email or not"""
def __init__(self, **kwargs):
kwargs['widget'] = forms.widgets.RadioSelect
kwargs['error_messages'] = {
- 'required':_('please choose one of the options above')
+ 'required': _('please choose one of the options above')
}
kwargs['choices'] = (
- ('y',_('okay, let\'s try!')),
+ ('y', _('okay, let\'s try!')),
(
'n',
- _('no %(sitename)s email please, thanks') \
+ _('no %(sitename)s email please, thanks')
% {'sitename': askbot_settings.APP_SHORT_NAME}
)
)
super(SubscribeForEmailUpdatesField, self).__init__(**kwargs)
+
class SimpleEmailSubscribeForm(forms.Form):
subscribe = SubscribeForEmailUpdatesField()
@@ -1277,11 +1503,13 @@ class SimpleEmailSubscribeForm(forms.Form):
email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL)
email_settings_form.save(user, save_unbound=True)
+
class GroupLogoURLForm(forms.Form):
"""form for saving group logo url"""
group_id = forms.IntegerField()
image_url = forms.CharField()
+
class EditGroupMembershipForm(forms.Form):
"""a form for adding or removing users
to and from user groups"""
@@ -1297,13 +1525,14 @@ class EditGroupMembershipForm(forms.Form):
raise forms.ValidationError('invalid action')
return action
+
class EditRejectReasonForm(forms.Form):
- reason_id = forms.IntegerField(required = False)
+ reason_id = forms.IntegerField(required=False)
title = CountedWordsField(
- min_words = 1, max_words = 4, field_name = _('Title')
+ min_words=1, max_words=4, field_name=_('Title')
)
details = CountedWordsField(
- min_words = 6, field_name = _('Description')
+ min_words=6, field_name=_('Description')
)
class ModerateTagForm(forms.Form):
@@ -1315,3 +1544,7 @@ class ModerateTagForm(forms.Form):
action = self.cleaned_data['action']
assert(action in ('accept', 'reject'))
return action
+
+class ShareQuestionForm(forms.Form):
+ thread_id = forms.IntegerField()
+ recipient_name = forms.CharField()
diff --git a/askbot/mail/__init__.py b/askbot/mail/__init__.py
index 1c32f14d..e653a0e3 100644
--- a/askbot/mail/__init__.py
+++ b/askbot/mail/__init__.py
@@ -344,14 +344,21 @@ def process_emailed_question(
form = AskByEmailForm(data)
if form.is_valid():
email_address = form.cleaned_data['email']
- user = User.objects.get(
- email__iexact = email_address
- )
+ user = User.objects.get(email__iexact = email_address)
- if user.can_post_by_email() == False:
+ if user.can_post_by_email() is False:
raise PermissionDenied(messages.insufficient_reputation(user))
- if user.email_isvalid == False:
+ body_text = form.cleaned_data['body_text']
+ stripped_body_text = user.strip_email_signature(body_text)
+ signature_not_detected = (
+ stripped_body_text == body_text and user.email_signature
+ )
+
+ #ask for signature response if user's email has not been
+ #validated yet or if email signature could not be found
+ if user.email_isvalid is False or signature_not_detected:
+
reply_to = ReplyAddress.objects.create_new(
user = user,
reply_action = 'validate_email'
@@ -361,16 +368,11 @@ def process_emailed_question(
tagnames = form.cleaned_data['tagnames']
title = form.cleaned_data['title']
- body_text = form.cleaned_data['body_text']
#defect - here we might get "too many tags" issue
if tags:
tagnames += ' ' + ' '.join(tags)
- stripped_body_text = user.strip_email_signature(body_text)
- if stripped_body_text == body_text and user.email_signature:
- #todo: send an email asking to update the signature
- raise ValueError('email signature changed')
user.post_question(
title = title,
diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py
index f7053414..8de3bd71 100644
--- a/askbot/mail/lamson_handlers.py
+++ b/askbot/mail/lamson_handlers.py
@@ -195,6 +195,7 @@ def ASK(message, host = None, addr = None):
from_address, subject, body_text, stored_files
)
else:
+ #this is the Ask the group branch
if askbot_settings.GROUP_EMAIL_ADDRESSES_ENABLED == False:
return
try:
diff --git a/askbot/management/commands/build_livesettings_cache.py b/askbot/management/commands/build_livesettings_cache.py
new file mode 100644
index 00000000..1898fc6a
--- /dev/null
+++ b/askbot/management/commands/build_livesettings_cache.py
@@ -0,0 +1,12 @@
+from django.core.management.base import NoArgsCommand
+
+class Command(NoArgsCommand):
+ '''Loads livesettings values to cache helping speed up
+ initial load time for the users'''
+
+ def handle_noargs(self, **options):
+ from askbot.conf import settings
+ #Just loads all the settings that way they will be in the cache
+ for key, value in settings._ConfigSettings__instance.items():
+ empty1 = getattr(settings, key)
+ print 'cache pre-loaded'
diff --git a/askbot/management/commands/send_unanswered_question_reminders.py b/askbot/management/commands/send_unanswered_question_reminders.py
index 82b6ecd8..39402b25 100644
--- a/askbot/management/commands/send_unanswered_question_reminders.py
+++ b/askbot/management/commands/send_unanswered_question_reminders.py
@@ -43,6 +43,10 @@ class Command(NoArgsCommand):
user_questions = questions.exclude(author = user)
user_questions = user.get_tag_filtered_questions(user_questions)
+ if askbot_settings.GROUPS_ENABLED:
+ user_groups = user.get_groups()
+ user_questions = user_questions.filter(groups__in = user_groups)
+
final_question_list = user_questions.get_questions_needing_reminder(
user = user,
activity_type = const.TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT,
diff --git a/askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py b/askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py
index 8ad83488..b5a1e0c9 100644
--- a/askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py
+++ b/askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py
@@ -9,6 +9,7 @@ class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Post.is_private'
+ db.start_transaction()
db.add_column('askbot_post', 'is_private',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
@@ -20,7 +21,15 @@ class Migration(SchemaMigration):
# Changing field 'ReplyAddress.post'
db.alter_column('askbot_replyaddress', 'post_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['askbot.Post']))
+ db.commit_transaction()
+ try:
+ db.start_transaction()
+ # Adding field 'User.interesting_tags'
+ db.add_column(u'auth_user', 'email_signature', self.gf('django.db.models.fields.TextField')(blank=True, default = ''), keep_default=False)
+ db.commit_transaction()
+ except:
+ db.rollback_transaction()
def backwards(self, orm):
db.delete_column('askbot_post', 'is_private')
diff --git a/askbot/migrations/0126_add_field__auth_user__is_fake.py b/askbot/migrations/0126_add_field__auth_user__is_fake.py
new file mode 100644
index 00000000..e0928ed7
--- /dev/null
+++ b/askbot/migrations/0126_add_field__auth_user__is_fake.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from south.db import db
+from south.v2 import SchemaMigration
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ try:
+ # Adding field 'User.is_fake'
+ db.add_column(
+ u'auth_user', 'is_fake',
+ self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
+ except:
+ pass
+
+ def backwards(self, orm):
+ db.delete_column('auth_user', 'is_fake')
+
+ complete_apps = ['askbot']
diff --git a/askbot/migrations/0127_save_category_tree_as_json.py b/askbot/migrations/0127_save_category_tree_as_json.py
new file mode 100644
index 00000000..b13cd2fe
--- /dev/null
+++ b/askbot/migrations/0127_save_category_tree_as_json.py
@@ -0,0 +1,385 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.utils import simplejson
+from django.db import models
+from askbot.conf import settings as askbot_settings
+
+def get_subtree(tree, path):
+ """#this might be simpler, but not tested
+ clevel = tree
+ for step in path:
+ try:
+ level = clevel[step]
+ except IndexError:
+ return False
+ return clevel
+ """
+ if len(path) == 1:
+ assert(path[0] == 0)
+ return tree
+ else:
+ import copy
+ parent_path = copy.copy(path)
+ leaf_index = parent_path.pop()
+ branch_index = parent_path[-1]
+ parent_tree = get_subtree(tree, parent_path)
+ return parent_tree[branch_index][1]
+
+def parse_tree(text):
+ """parse tree represented as indented text
+ one item per line, with two spaces per level of indentation
+ """
+ lines = text.split('\n')
+ import re
+ in_re = re.compile(r'^([ ]*)')
+
+ tree = [['dummy', []]]
+ subtree_path = [0]
+ clevel = 0
+
+ for line in lines:
+ if line.strip() == '':
+ continue
+ match = in_re.match(line)
+ level = len(match.group(1))/2 + 1
+
+ if level > clevel:
+ subtree_path.append(0)#
+ elif level < clevel:
+ subtree_path = subtree_path[:level+1]
+ leaf_index = subtree_path.pop()
+ subtree_path.append(leaf_index + 1)
+ else:
+ leaf_index = subtree_path.pop()
+ subtree_path.append(leaf_index + 1)
+
+ clevel = level
+ try:
+ subtree = get_subtree(tree, subtree_path)
+ except:
+ return tree
+ subtree.append([line.strip(), []])
+
+ return tree
+
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ """reads category tree saved as string,
+ translates it to json and saves back"""
+ old_data = askbot_settings.CATEGORY_TREE
+ json_data = parse_tree(old_data)
+ json_string = simplejson.dumps(json_data)
+ askbot_settings.update('CATEGORY_TREE', json_string)
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+ pass
+
+
+ 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.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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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']"}),
+ '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'}),
+ 'is_private': ('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', [], {}),
+ 'revision_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ '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.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.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'}),
+ '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']"}),
+ '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.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.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_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'}),
+ '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': '30'}),
+ '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']
+ symmetrical = True
diff --git a/askbot/migrations/0128_add_groups_field__to__thread_and_post.py b/askbot/migrations/0128_add_groups_field__to__thread_and_post.py
new file mode 100644
index 00000000..ab8bf919
--- /dev/null
+++ b/askbot/migrations/0128_add_groups_field__to__thread_and_post.py
@@ -0,0 +1,335 @@
+# -*- 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 M2M table for field groups on 'Thread'
+ db.create_table('askbot_thread_groups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('thread', models.ForeignKey(orm['askbot.thread'], null=False)),
+ ('tag', models.ForeignKey(orm['askbot.tag'], null=False))
+ ))
+ db.create_unique('askbot_thread_groups', ['thread_id', 'tag_id'])
+ # Adding M2M table for field groups on 'Post'
+ db.create_table('askbot_post_groups', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('post', models.ForeignKey(orm['askbot.post'], null=False)),
+ ('tag', models.ForeignKey(orm['askbot.tag'], null=False))
+ ))
+ db.create_unique('askbot_post_groups', ['post_id', 'tag_id'])
+
+ def backwards(self, orm):
+
+ # Removing M2M tables for field groups on 'Thread' and 'Post'
+ db.delete_table('askbot_thread_groups')
+ db.delete_table('askbot_post_groups')
+
+ 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.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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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']"}),
+ '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'}),
+ 'is_private': ('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', [], {}),
+ 'revision_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ '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.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.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'}),
+ '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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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_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': '30'}),
+ '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']
diff --git a/askbot/migrations/0129_auto__del_field_post_is_private.py b/askbot/migrations/0129_auto__del_field_post_is_private.py
new file mode 100644
index 00000000..68637bdc
--- /dev/null
+++ b/askbot/migrations/0129_auto__del_field_post_is_private.py
@@ -0,0 +1,322 @@
+# -*- 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):
+ # Deleting field 'Post.is_private'
+ db.delete_column('askbot_post', 'is_private')
+
+ def backwards(self, orm):
+ # Adding field 'Post.is_private'
+ db.add_column('askbot_post', 'is_private',
+ self.gf('django.db.models.fields.BooleanField')(default=False),
+ keep_default=False)
+
+ 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.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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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', 'to': "orm['askbot.Tag']"}),
+ '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', [], {}),
+ 'revision_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ '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.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.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'}),
+ '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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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_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'}),
+ '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': '30'}),
+ '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']
diff --git a/askbot/migrations/0130_auto__del_field_postrevision_revision_type.py b/askbot/migrations/0130_auto__del_field_postrevision_revision_type.py
new file mode 100644
index 00000000..4143ffa8
--- /dev/null
+++ b/askbot/migrations/0130_auto__del_field_postrevision_revision_type.py
@@ -0,0 +1,321 @@
+# -*- 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):
+ # Deleting field 'PostRevision.revision_type'
+ db.delete_column('askbot_postrevision', 'revision_type')
+
+ def backwards(self, orm):
+ # Adding field 'PostRevision.revision_type'
+ db.add_column('askbot_postrevision', 'revision_type',
+ self.gf('django.db.models.fields.SmallIntegerField')(default=1),
+ keep_default=False)
+
+ 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.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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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'}),
+ '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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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_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'}),
+ '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': '30'}),
+ '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']
diff --git a/askbot/migrations/0131_auto__add_field_tag_status.py b/askbot/migrations/0131_auto__add_field_tag_status.py
new file mode 100644
index 00000000..f5557546
--- /dev/null
+++ b/askbot/migrations/0131_auto__add_field_tag_status.py
@@ -0,0 +1,335 @@
+# -*- 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 'Tag.status'
+ db.add_column(u'tag', 'status',
+ self.gf('django.db.models.fields.SmallIntegerField')(default=1),
+ keep_default=False)
+
+ # Adding M2M table for field suggested_by on 'Tag'
+ db.create_table(u'tag_suggested_by', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('tag', models.ForeignKey(orm['askbot.tag'], null=False)),
+ ('user', models.ForeignKey(orm['auth.user'], null=False))
+ ))
+ db.create_unique(u'tag_suggested_by', ['tag_id', 'user_id'])
+
+ def backwards(self, orm):
+ # Deleting field 'Tag.status'
+ db.delete_column(u'tag', 'status')
+
+ # Removing M2M table for field suggested_by on 'Tag'
+ db.delete_table('tag_suggested_by')
+
+ 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.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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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_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': '30'}),
+ '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']
diff --git a/askbot/migrations/0132_auto__add_draftquestion__add_draftanswer.py b/askbot/migrations/0132_auto__add_draftquestion__add_draftanswer.py
new file mode 100644
index 00000000..bceca754
--- /dev/null
+++ b/askbot/migrations/0132_auto__add_draftquestion__add_draftanswer.py
@@ -0,0 +1,356 @@
+# -*- 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 model 'DraftQuestion'
+ db.create_table('askbot_draftquestion', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=300, null=True)),
+ ('text', self.gf('django.db.models.fields.TextField')(null=True)),
+ ('tagnames', self.gf('django.db.models.fields.CharField')(max_length=125, null=True)),
+ ))
+ db.send_create_signal('askbot', ['DraftQuestion'])
+
+ # Adding model 'DraftAnswer'
+ db.create_table('askbot_draftanswer', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('thread', self.gf('django.db.models.fields.related.ForeignKey')(related_name='draft_answers', to=orm['askbot.Thread'])),
+ ('author', self.gf('django.db.models.fields.related.ForeignKey')(related_name='draft_answers', to=orm['auth.User'])),
+ ('text', self.gf('django.db.models.fields.TextField')(null=True)),
+ ))
+ db.send_create_signal('askbot', ['DraftAnswer'])
+
+ def backwards(self, orm):
+ # Deleting model 'DraftQuestion'
+ db.delete_table('askbot_draftquestion')
+
+ # Deleting model 'DraftAnswer'
+ db.delete_table('askbot_draftanswer')
+
+ 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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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_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': '30'}),
+ '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/migrations/0133_apply_global_group_to_posts_and_users.py b/askbot/migrations/0133_apply_global_group_to_posts_and_users.py
new file mode 100644
index 00000000..733c4c73
--- /dev/null
+++ b/askbot/migrations/0133_apply_global_group_to_posts_and_users.py
@@ -0,0 +1,449 @@
+# -*- coding: utf-8 -*-
+import datetime
+import re
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+from django.core.management.base import CommandError
+from askbot.conf import settings as askbot_settings
+from askbot.utils.console import ProgressBar
+
+def clean_group_name(name):
+ """group names allow spaces,
+ tag names do not, so we use this method
+ to replace spaces with dashes"""
+ return re.sub('\s+', '-', name.strip())
+
+def get_superuser(orm):
+ """returns superuser, assumes that user
+ with the lowest id with the superuser status
+ otherwise - if there are no users at all -
+ will return None
+ and finally if there are users, but no super -
+ will fail and ask create a superuser
+ """
+ try:
+ return orm['auth.User'].objects.filter(
+ is_superuser=True
+ ).order_by('id')[0]
+ except IndexError:
+ if orm['auth.User'].objects.count() > 0:
+ raise CommandError('Please create a superuser to continue')
+ else:
+ return None
+
+def create_group(orm, name=None, created_by=None):
+ """creates a group with a given name and the founder"""
+ group = orm['askbot.Tag']()
+ group.name = name
+ group.created_by = created_by
+ group.save()
+
+ group_profile = orm['askbot.GroupProfile']()
+ group_profile.group_tag = group
+ group_profile.save()
+
+ return group
+
+def get_global_group(orm):
+ """returns the "global user group" if exists
+ otherwise will attempt to create the group -
+ if a superuser exists (which may fail)
+ and if superuser does not exist - then
+ return None
+ """
+ group_name = clean_group_name(askbot_settings.GLOBAL_GROUP_NAME)
+ model = orm['askbot.Tag']
+ try:
+ return model.objects.get(name=group_name)
+ except model.DoesNotExist:
+ superuser = get_superuser(orm)
+ if superuser:
+ return create_group(
+ orm,
+ name=group_name,
+ created_by=superuser
+ )
+ else:
+ return None
+
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ "Write your forwards methods here."
+ group = get_global_group(orm)
+
+ items = orm['askbot.Thread'].objects.all()
+ message = 'Moving group-less threads to the global group'
+ done_count = 0
+ for thread in ProgressBar(items.iterator(), items.count(), message):
+ if thread.groups.count() == 0:
+ thread.groups.add(group)
+ done_count += 1
+
+ print 'Added global group to %d threads.\n' % done_count
+
+ post_types = ('question', 'answer')
+ posts = orm['askbot.Post'].objects.filter(post_type__in=post_types)
+ message = 'Move groupless questions and answers to the global group'
+ done_count = 0
+ for post in ProgressBar(posts.iterator(), posts.count(), message):
+ if post.groups.count() == 0:
+ post.groups.add(group)
+ done_count += 1
+
+ print 'Added global group to %d posts.\n' % done_count
+
+ comments = orm['askbot.Post'].objects.filter(post_type='comment')
+ message = 'Copying group information from answers ' +\
+ 'and questions to comments'
+ done_count = 0
+ for comment in ProgressBar(comments.iterator(), comments.count(), message):
+ if comment.groups.count() == 0:
+ parent_post_groups = comment.parent.groups.all()
+ comment.groups.add(*parent_post_groups)
+ done_count += 1
+
+ print 'Added global group to %d comments.\n' % done_count
+
+ users = orm['auth.User'].objects.all()
+ message = 'Adding all users to the global group'
+ done_count = 0
+ for user in ProgressBar(users.iterator(), users.count(), message):
+ cls = orm['askbot.GroupMembership']
+ if not cls.objects.filter(user=user, group=group).exists():
+ membership = cls(user=user, group=group)
+ membership.save()
+ done_count += 1
+
+ print 'Added global group to %d users.' % done_count
+
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+ pass
+
+
+ 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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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': '30'}),
+ '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']
+ symmetrical = True
diff --git a/askbot/migrations/0134_create_personal_groups_for_all_users.py b/askbot/migrations/0134_create_personal_groups_for_all_users.py
new file mode 100644
index 00000000..e1c45ec1
--- /dev/null
+++ b/askbot/migrations/0134_create_personal_groups_for_all_users.py
@@ -0,0 +1,378 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+from askbot.utils.console import ProgressBar
+
+def format_group_name(user):
+ """returns name of the personal group
+ given the user object
+ """
+ return '_internal_%s_%d' % (user.username, user.id)
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ "Write your forwards methods here."
+ message = 'Creating personal group for the users'
+ users = orm['auth.User'].objects.all()
+ for user in ProgressBar(users.iterator(), users.count(), message):
+ group_name = format_group_name(user)
+ group_tag = orm['askbot.Tag'](name=group_name, created_by=user)
+ group_tag.save()
+ group_profile = orm['askbot.GroupProfile'](
+ group_tag=group_tag, is_open=False
+ )
+ group_profile.save()
+ membership = orm['askbot.GroupMembership'](group=group_tag, user=user)
+ membership.save()
+
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+ for user in orm['auth.User'].objects.iterator():
+ group_name = format_group_name(user)
+
+ memberships = orm['askbot.GroupMembership'].objects.filter(
+ user=user,
+ group__name=group_name
+ )
+ memberships.delete()
+
+ group_profiles = orm['askbot.GroupProfile'].objects.filter(
+ group_tag__name=group_name
+ )
+ group_profiles.delete()
+
+ orm['askbot.Tag'].objects.filter(name=group_name).delete()
+
+
+ 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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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.Tag']"}),
+ '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', 'tag'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']"})
+ },
+ '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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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': '30'}),
+ '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']
+ symmetrical = True
diff --git a/askbot/migrations/0135_add__primary_group__field__to__auth_user.py b/askbot/migrations/0135_add__primary_group__field__to__auth_user.py
new file mode 100644
index 00000000..a3f51ecb
--- /dev/null
+++ b/askbot/migrations/0135_add__primary_group__field__to__auth_user.py
@@ -0,0 +1,353 @@
+# -*- coding: utf-8 -*-
+from south.db import db
+from south.v2 import SchemaMigration
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ try:
+ # Adding field 'User.primary_group'
+ db.add_column(
+ u'auth_user',
+ 'primary_group',
+ self.gf('django.db.models.fields.ForegnKey')(
+ null=True,
+ blank=True,
+ default=None,
+ to=['orm.Tag']
+ )
+ )
+ except:
+ pass
+
+ def backwards(self, orm):
+ db.delete_column('auth_user', 'primary_group')
+
+ 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.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.groupmembership': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.groupprofile': {
+ 'Meta': {'object_name': 'GroupProfile'},
+ 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ '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.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.Tag']"}),
+ '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', 'tag'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']"})
+ },
+ '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.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', 'to': "orm['askbot.Tag']"}),
+ '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.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.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': '30'}),
+ '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']
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index 99036f45..27336576 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -28,12 +28,19 @@ from askbot.models.question import Thread
from askbot.skins import utils as skin_utils
from askbot.mail import messages
from askbot.models.question import QuestionView, AnonymousQuestion
+from askbot.models.question import DraftQuestion
from askbot.models.question import FavoriteQuestion
from askbot.models.tag import Tag, MarkedTag
-from askbot.models.tag import get_group_names, get_groups
+from askbot.models.tag import get_global_group
+from askbot.models.tag import get_group_names
+from askbot.models.tag import get_groups
+from askbot.models.tag import format_personal_group_name
from askbot.models.user import EmailFeedSetting, ActivityAuditStatus, Activity
from askbot.models.user import GroupMembership, GroupProfile
-from askbot.models.post import Post, PostRevision, PostFlagReason, AnonymousAnswer
+from askbot.models.post import Post, PostRevision
+from askbot.models.post import PostFlagReason, AnonymousAnswer
+from askbot.models.post import PostToGroup
+from askbot.models.post import DraftAnswer
from askbot.models.reply_by_email import ReplyAddress
from askbot.models import signals
from askbot.models.badges import award_badges_signal, get_badge, BadgeData
@@ -57,6 +64,26 @@ def get_admins_and_moderators():
models.Q(is_superuser=True) | models.Q(status='m')
)
+def get_admin():
+ """returns admin with the lowest user ID
+ if there are no users at all - creates one
+ with name "admin" and unusable password
+ otherwise raises User.DoesNotExist
+ """
+ try:
+ return User.objects.filter(
+ is_superuser=True
+ ).order_by('id')[0]
+ except IndexError:
+ if User.objects.filter(username='_admin_').count() == 0:
+ admin = User.objects.create_user('_admin_', '')
+ admin.set_unusable_password()
+ admin.set_admin_status()
+ admin.save()
+ return admin
+ else:
+ raise User.DoesNotExist
+
def get_users_by_text_query(search_query, users_query_set = None):
"""Runs text search in user names and profile.
For postgres, search also runs against user group names.
@@ -87,6 +114,7 @@ User.add_to_class(
choices = const.USER_STATUS_CHOICES
)
)
+User.add_to_class('is_fake', models.BooleanField(default=False))
User.add_to_class('email_isvalid', models.BooleanField(default=False)) #@UndefinedVariable
User.add_to_class('email_key', models.CharField(max_length=32, null=True))
@@ -354,6 +382,24 @@ def user_can_post_by_email(self):
return askbot_settings.REPLY_BY_EMAIL and \
self.reputation > askbot_settings.MIN_REP_TO_POST_BY_EMAIL
+def user_get_or_create_fake_user(self, username, email):
+ """
+ Get's or creates a user, most likely with the purpose
+ of posting under that account.
+ """
+ assert(self.is_administrator())
+
+ try:
+ user = User.objects.get(username=username)
+ except User.DoesNotExist:
+ user = User()
+ user.username = username
+ user.email = email
+ user.is_fake = True
+ user.set_unusable_password()
+ user.save()
+ return user
+
def _assert_user_can(
user = None,
post = None, #related post (may be parent)
@@ -562,9 +608,16 @@ def user_assert_can_post_question(self):
)
-def user_assert_can_post_answer(self):
+def user_assert_can_post_answer(self, thread = None):
"""same as user_can_post_question
"""
+ limit_answers = askbot_settings.LIMIT_ONE_ANSWER_PER_USER
+ if limit_answers and thread.has_answer_by_user(self):
+ message = _(
+ 'Sorry, you already gave an answer, please edit it instead.'
+ )
+ raise askbot_exceptions.AnswerAlreadyGiven(message)
+
self.assert_can_post_question()
@@ -848,7 +901,9 @@ def user_assert_can_close_question(self, question = None):
def user_assert_can_reopen_question(self, question = None):
assert(question.post_type == 'question')
+ #for some reason rep to reopen own questions != rep to close own q's
owner_min_rep_setting = askbot_settings.MIN_REP_TO_REOPEN_OWN_QUESTIONS
+ min_rep_setting = askbot_settings.MIN_REP_TO_CLOSE_OTHERS_QUESTIONS
general_error_message = _(
'Sorry, only administrators, moderators '
@@ -861,15 +916,27 @@ def user_assert_can_reopen_question(self, question = None):
'a minimum reputation of %(min_rep)s is required'
) % {'min_rep': owner_min_rep_setting}
+ blocked_error_message = _(
+ 'Sorry, you cannot reopen questions '
+ 'because your account is blocked'
+ )
+
+ suspended_error_message = _(
+ 'Sorry, you cannot reopen questions '
+ 'because your account is suspended'
+ )
+
_assert_user_can(
user = self,
post = question,
- admin_or_moderator_required = True,
owner_can = True,
suspended_owner_cannot = True,
owner_min_rep_setting = owner_min_rep_setting,
+ min_rep_setting = min_rep_setting,
owner_low_rep_error_message = owner_low_rep_error_message,
- general_error_message = general_error_message
+ general_error_message = general_error_message,
+ blocked_error_message = blocked_error_message,
+ suspended_error_message = suspended_error_message
)
@@ -1100,6 +1167,8 @@ def user_post_comment(
added_at = timestamp,
by_email = by_email
)
+ comment.add_to_groups([self.get_personal_group()])
+
parent_post.thread.invalidate_cached_data()
award_badges_signal.send(
None,
@@ -1450,6 +1519,7 @@ def user_post_question(
wiki = False,
is_anonymous = False,
is_private = False,
+ group_id = None,
timestamp = None,
by_email = False,
email_address = None
@@ -1480,6 +1550,7 @@ def user_post_question(
wiki = wiki,
is_anonymous = is_anonymous,
is_private = is_private,
+ group_id = group_id,
by_email = by_email,
email_address = email_address
)
@@ -1725,7 +1796,7 @@ def user_post_answer(
assert(error_message is not None)
raise django_exceptions.PermissionDenied(error_message)
- self.assert_can_post_answer()
+ self.assert_can_post_answer(thread = question.thread)
if getattr(question, 'post_type', '') != 'question':
raise TypeError('question argument must be provided')
@@ -1751,6 +1822,7 @@ def user_post_answer(
is_private = is_private,
by_email = by_email
)
+ answer_post.add_to_groups([self.get_personal_group()])
answer_post.thread.invalidate_cached_data()
award_badges_signal.send(None,
@@ -2123,10 +2195,14 @@ def get_profile_link(self):
return mark_safe(profile_link)
-def user_get_groups(self):
+def user_get_groups(self, private=False):
"""returns a query set of groups to which user belongs"""
#todo: maybe cache this query
- return Tag.group_tags.get_for_user(self)
+ return Tag.group_tags.get_for_user(self, private=private)
+
+def user_get_personal_group(self):
+ group_name = format_personal_group_name(self)
+ return Tag.group_tags.get(name=group_name)
def user_get_foreign_groups(self):
"""returns a query set of groups to which user does not belong"""
@@ -2136,7 +2212,7 @@ def user_get_foreign_groups(self):
def user_can_make_group_private_posts(self):
"""simplest implementation: user belongs to at least one group"""
- return self.get_groups().count() > 0
+ return self.get_groups(private=True).count() > 0
def user_get_groups_membership_info(self, groups):
"""returts a defaultdict with values that are
@@ -2189,14 +2265,14 @@ def user_get_badge_summary(self):
bit = ungettext(
'one silver badge',
'%(count)d silver badges',
- self.gold
+ self.silver
) % {'count': self.silver}
badge_bits.append(bit)
- if self.silver:
+ if self.bronze:
bit = ungettext(
'one bronze badge',
'%(count)d bronze badges',
- self.gold
+ self.bronze
) % {'count': self.bronze}
badge_bits.append(bit)
@@ -2531,6 +2607,12 @@ def user_edit_group_membership(self, user = None, group = None, action = None):
else:
raise ValueError('invalid action')
+def user_join_group(self, group):
+ self.edit_group_membership(group=group, user=self, action='add')
+
+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 self.group_memberships.filter(group = group).count() == 1
@@ -2554,10 +2636,12 @@ User.add_to_class('get_absolute_url', user_get_absolute_url)
User.add_to_class('get_avatar_url', user_get_avatar_url)
User.add_to_class('get_default_avatar_url', user_get_default_avatar_url)
User.add_to_class('get_gravatar_url', user_get_gravatar_url)
+User.add_to_class('get_or_create_fake_user', user_get_or_create_fake_user)
User.add_to_class('get_marked_tags', user_get_marked_tags)
User.add_to_class('get_marked_tag_names', user_get_marked_tag_names)
User.add_to_class('get_groups', user_get_groups)
User.add_to_class('get_foreign_groups', user_get_foreign_groups)
+User.add_to_class('get_personal_group', user_get_personal_group)
User.add_to_class('strip_email_signature', user_strip_email_signature)
User.add_to_class('get_groups_membership_info', user_get_groups_membership_info)
User.add_to_class('get_anonymous_name', user_get_anonymous_name)
@@ -2609,6 +2693,8 @@ User.add_to_class('is_administrator', user_is_administrator)
User.add_to_class('is_administrator_or_moderator', user_is_administrator_or_moderator)
User.add_to_class('set_admin_status', user_set_admin_status)
User.add_to_class('edit_group_membership', user_edit_group_membership)
+User.add_to_class('join_group', user_join_group)
+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)
@@ -2720,6 +2806,8 @@ def format_instant_notification_email(
assert(isinstance(post, Post) and post.is_question())
elif update_type == 'new_question':
assert(isinstance(post, Post) and post.is_question())
+ elif update_type == 'post_shared':
+ pass
else:
raise ValueError('unexpected update_type %s' % update_type)
@@ -2744,9 +2832,11 @@ def format_instant_notification_email(
content_preview += '<p>======= Full thread summary =======</p>'
- content_preview += post.thread.format_for_email()
+ content_preview += post.thread.format_for_email(user=to_user)
- if post.is_comment():
+ if update_type == 'post_shared':
+ user_action = _('%(user)s shared a %(post_link)s.')
+ elif post.is_comment():
if update_type.endswith('update'):
user_action = _('%(user)s edited a %(post_link)s.')
else:
@@ -2865,7 +2955,7 @@ def send_instant_notifications_about_activity_in_post(
newly mentioned users are carried through to reduce
database hits
"""
- if askbot_settings.ENABLE_CONTENT_MODERATION and post.approved == False:
+ if post.is_approved() is False:
return
if recipients is None:
@@ -2888,6 +2978,10 @@ def send_instant_notifications_about_activity_in_post(
)
#send email for all recipients
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(
@@ -3232,6 +3326,37 @@ def send_respondable_email_validation_message(
)
+def add_user_to_global_group(sender, instance, created, **kwargs):
+ """auto-joins user to the global group
+ ``instance`` is an instance of ``User`` class
+ """
+ if created:
+ from askbot.models.tag import get_global_group
+ instance.edit_group_membership(
+ group=get_global_group(),
+ user=instance,
+ action='add'
+ )
+
+
+def add_user_to_personal_group(sender, instance, created, **kwargs):
+ """auto-joins user to his/her personal group
+ ``instance`` is an instance of ``User`` class
+ """
+ if created:
+ #todo: groups will indeed need to be separated from tags
+ #so that we can use less complicated naming scheme
+ #in theore here we may have two users that will have
+ #identical group names!!!
+ group_name = format_personal_group_name(instance)
+ group = Tag.group_tags.get_or_create(
+ group_name=group_name, user=instance
+ )
+ instance.edit_group_membership(
+ group=group, user=instance, action='add'
+ )
+
+
def greet_new_user(user, **kwargs):
"""sends welcome email to the newly created user
@@ -3257,7 +3382,6 @@ def greet_new_user(user, **kwargs):
)
-
def complete_pending_tag_subscriptions(sender, request, *args, **kwargs):
"""save pending tag subscriptions saved in the session"""
if 'subscribe_for_tags' in request.session:
@@ -3324,6 +3448,8 @@ def make_admin_if_first_user(instance, **kwargs):
django_signals.pre_save.connect(make_admin_if_first_user, sender=User)
django_signals.pre_save.connect(calculate_gravatar_hash, sender=User)
django_signals.post_save.connect(add_missing_subscriptions, sender=User)
+django_signals.post_save.connect(add_user_to_global_group, sender=User)
+django_signals.post_save.connect(add_user_to_personal_group, sender=User)
django_signals.post_save.connect(record_award_event, sender=Award)
django_signals.post_save.connect(notify_award_message, sender=Award)
django_signals.post_save.connect(record_answer_accepted, sender=Post)
@@ -3368,11 +3494,14 @@ __all__ = [
'QuestionView',
'FavoriteQuestion',
'AnonymousQuestion',
+ 'DraftQuestion',
'AnonymousAnswer',
+ 'DraftAnswer',
'Post',
'PostRevision',
+ 'PostToGroup',
'Tag',
'Vote',
@@ -3396,5 +3525,5 @@ __all__ = [
'get_model',
'get_admins_and_moderators',
'get_group_names',
- 'get_grous'
+ 'get_groups'
]
diff --git a/askbot/models/base.py b/askbot/models/base.py
index b3a12fbf..74b8c2dd 100644
--- a/askbot/models/base.py
+++ b/askbot/models/base.py
@@ -41,7 +41,7 @@ class BaseQuerySetManager(models.Manager):
return getattr(self.get_query_set(), attr, *args)
-class AnonymousContent(models.Model):
+class DraftContent(models.Model):
"""Base class for AnonymousQuestion and AnonymousAnswer"""
session_key = models.CharField(max_length=40) #session id for anonymous questions
wiki = models.BooleanField(default=False)
diff --git a/askbot/models/post.py b/askbot/models/post.py
index 21723128..8d2d05a4 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -17,25 +17,43 @@ 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.exceptions import ValidationError
+from django.core.urlresolvers import reverse
from django.contrib.contenttypes.models import ContentType
import askbot
from askbot.utils.slug import slugify
from askbot import const
+from askbot.models.user import Activity
from askbot.models.user import EmailFeedSetting
+from askbot.models.user import GroupMembership
from askbot.models.tag import Tag, MarkedTag
from askbot.models.tag import get_groups, tags_match_some_wildcard
+from askbot.models.tag import get_global_group
from askbot.conf import settings as askbot_settings
from askbot import exceptions
from askbot.utils import markup
from askbot.utils.html import sanitize_html
-from askbot.models.base import BaseQuerySetManager, AnonymousContent
+from askbot.models.base import BaseQuerySetManager, DraftContent
#todo: maybe merge askbot.utils.markup and forum.utils.html
from askbot.utils.diff import textDiff as htmldiff
from askbot.utils import mysql
+
+class PostToGroup(models.Model):
+ """the "trough" table for the
+ relation of groups to posts
+ """
+ post = models.ForeignKey('Post')
+ tag = models.ForeignKey('Tag')
+
+ class Meta:
+ unique_together = ('post', 'tag')
+ db_table = 'askbot_post_groups'
+ app_label = 'askbot'
+
+
class PostQuerySet(models.query.QuerySet):
"""
Custom query set subclass for :class:`~askbot.models.Post`
@@ -151,10 +169,10 @@ class PostManager(BaseQuerySetManager):
if askbot_settings.GROUPS_ENABLED:
if user is None or user.is_anonymous():
- exclude_groups = get_groups()
+ groups = [get_global_group()]
else:
- exclude_groups = user.get_foreign_groups()
- answers = answers.exclude(groups__in = exclude_groups)
+ groups = user.get_groups()
+ answers = answers.filter(groups__in = groups).distinct()
return answers
@@ -305,7 +323,7 @@ class Post(models.Model):
parent = models.ForeignKey('Post', blank=True, null=True, related_name='comments') # Answer or Question for Comment
thread = models.ForeignKey('Thread', blank=True, null=True, default = None, related_name='posts')
- groups = models.ManyToManyField('Tag', related_name = 'group_posts')#used for group-private posts
+ groups = models.ManyToManyField('Tag', through='PostToGroup', related_name = 'group_posts')#used for group-private posts
author = models.ForeignKey(User, related_name='posts')
added_at = models.DateTimeField(default=datetime.datetime.now)
@@ -479,17 +497,21 @@ class Post(models.Model):
created = self.pk is None
is_private = kwargs.pop('is_private', False)
+ group_id = kwargs.pop('group_id', None)
#this save must precede saving the mention activity
#as well as assigning groups to the post
#because generic relation needs primary key of the related object
super(self.__class__, self).save(**kwargs)
- if author.can_make_group_private_posts():
- if is_private:
- self.make_private(author)
- else:
- self.make_public(author)
+ if self.is_comment():
+ #copy groups from the parent post into the comment
+ groups = self.parent.groups.all()
+ self.add_to_groups(groups)
+ elif is_private or group_id:
+ self.make_private(author, group_id = group_id)
+ else:
+ self.make_public()
if last_revision:
diff = htmldiff(
@@ -536,26 +558,141 @@ class Post(models.Model):
def is_reject_reason(self):
return self.post_type == 'reject_reason'
- def make_private(self, user):
- """makes post private within user's groups"""
- groups = user.get_groups()
- self.groups.add(*groups)
- if self.is_question():
- self.thread.groups.add(*groups)
+ def add_to_groups(self, groups):
+ #todo: use bulk-creation
+ for group in groups:
+ PostToGroup.objects.get_or_create(post=self, tag=group)
+ if self.is_answer() or self.is_question():
+ comments = self.comments.all()
+ for group in groups:
+ for comment in comments:
+ PostToGroup.objects.get_or_create(post=comment, tag=group)
+
+
+ def remove_from_groups(self, groups):
+ PostToGroup.objects.filter(post=self, tag__in=groups).delete()
+ if self.is_answer() or self.is_question():
+ comment_ids = self.comments.all().values_list('id', flat=True)
+ PostToGroup.objects.filter(
+ post__id__in=comment_ids,
+ tag__in=groups
+ ).delete()
+
+
+ def issue_update_notifications(
+ self,
+ updated_by=None,
+ notify_sets=None,
+ activity_type=None,
+ timestamp=None,
+ diff=None
+ ):
+ """Called when a post is updated. Arguments:
+
+ * ``notify_sets`` - result of ``Post.get_notify_sets()`` method
- def make_public(self, user):
+ The method does two things:
+
+ * records "red envelope" recipients of the post
+ * sends email alerts to all subscribers to the post
+ """
+ assert(activity_type is not None)
+ if self.is_comment():
+ #it's just a comment!
+ summary = self.text
+ else:
+ #summary = post.get_latest_revision().summary
+ if diff:
+ summary = diff
+ else:
+ summary = self.text
+
+ update_activity = Activity(
+ user = updated_by,
+ active_at = timestamp,
+ content_object = self,
+ activity_type = activity_type,
+ question = self.get_origin_post(),
+ summary = summary
+ )
+ update_activity.save()
+
+ update_activity.add_recipients(notify_sets['for_inbox'])
+
+ #create new mentions (barring the double-adds)
+ for u in notify_sets['for_mentions'] - notify_sets['for_inbox']:
+ Activity.objects.create_new_mention(
+ mentioned_whom = u,
+ mentioned_in = self,
+ mentioned_by = updated_by,
+ mentioned_at = timestamp
+ )
+
+ for user in (notify_sets['for_inbox'] | notify_sets['for_mentions']):
+ user.update_response_counts()
+
+ #shortcircuit if the email alerts are disabled
+ if askbot_settings.ENABLE_EMAIL_ALERTS == False:
+ return
+ #todo: fix this temporary spam protection plug
+ if askbot_settings.MIN_REP_TO_TRIGGER_EMAIL:
+ if not (updated_by.is_administrator() or updated_by.is_moderator()):
+ if updated_by.reputation < askbot_settings.MIN_REP_TO_TRIGGER_EMAIL:
+ notify_sets['for_email'] = \
+ [u for u in notify_sets['for_email'] if u.is_administrator()]
+
+ 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'],
+ )
+
+ def make_private(self, user, group_id = None):
+ """makes post private within user's groups
+ todo: this is a copy-paste in thread and post
+ """
+ if group_id:
+ group = Tag.group_tags.get(id=group_id)
+ groups = [group]
+ self.add_to_groups(groups)
+
+ global_group = get_global_group()
+ if group != global_group:
+ self.remove_from_groups((global_group,))
+ else:
+ groups = user.get_groups(private=True)
+ self.add_to_groups(groups)
+ self.remove_from_groups((get_global_group(),))
+
+ if len(groups) == 0:
+ message = 'Sharing did not work, because group is unknown'
+ user.message_set.create(message=message)
+
+ def make_public(self):
"""removes the privacy mark from users groups"""
- groups = user.get_groups()
- self.groups.remove(*groups)
- if self.is_question():
- self.thread.groups.remove(*groups)
+ groups = (get_global_group(),)
+ self.add_to_groups(groups)
def is_private(self):
- """true, if post is private within any groups"""
- return askbot_settings.GROUPS_ENABLED and self.groups.count() > 0
+ """true, if post belongs to the global group"""
+ if askbot_settings.GROUPS_ENABLED:
+ group = get_global_group()
+ return not self.groups.filter(id=group.id).exists()
+ return False
+
+ def is_approved(self):
+ """``False`` only when moderation is ``True`` and post
+ ``self.approved is False``
+ """
+ if askbot_settings.ENABLE_CONTENT_MODERATION:
+ if self.approved == False:
+ return False
+ return True
def needs_moderation(self):
- return self.approved == False
+ #todo: do we need this, can't we just use is_approved()?
+ return self.approved is False
def get_absolute_url(self, no_slug = False, question_post=None, thread=None):
from askbot.utils.slug import slugify
@@ -585,15 +722,6 @@ class Post(models.Model):
raise NotImplementedError
- def get_authorized_group_ids(self):
- """returns values list query set for the group id's of the post"""
- if self.is_comment():
- return self.parent.get_authorized_group_ids()
- elif self.is_answer() or self.is_question():
- return self.groups.values_list('id', flat = True)
- else:
- raise NotImplementedError()
-
def delete(self, **kwargs):
"""deletes comment and concomitant response activity
records, as well as mention records, while preserving
@@ -661,21 +789,29 @@ class Post(models.Model):
if askbot_settings.GROUPS_ENABLED == False:
return candidates
else:
- #here we filter candidates against the post authorized groups
- authorized_group_ids = list(self.get_authorized_group_ids())
-
- if len(authorized_group_ids) == 0:#if there are no groups - all ok
+ if len(candidates) == 0:
return candidates
-
- #and filter the users by those groups
- filtered_candidates = list()
+ #get post groups
+ groups = list(self.groups.all())
+
+ if len(groups) == 0:
+ logging.critical('post %d is groupless' % self.id)
+ return list()
+
+ #load group memberships for the candidates
+ memberships = GroupMembership.objects.filter(
+ user__in=candidates,
+ group__in=groups
+ )
+ user_ids = set(memberships.values_list('user__id', flat=True))
+
+ #scan through the user ids and see which are group members
+ filtered_candidates = set()
for candidate in candidates:
- c_groups = candidate.get_groups()
- if c_groups.filter(id__in = authorized_group_ids).count():
- filtered_candidates.append(candidate)
+ if candidate.id in user_ids:
+ filtered_candidates.add(candidate)
return filtered_candidates
-
def format_for_email(
self, quote_level = 0, is_leaf_post = False, format = None
@@ -741,7 +877,7 @@ class Post(models.Model):
does not talk to the actual cache system
"""
self._cached_comments = comments
-
+
def get_cached_comments(self):
try:
return self._cached_comments
@@ -999,10 +1135,7 @@ class Post(models.Model):
#print 'answer subscribers: ', answer_subscribers
#print 'exclude_list is ', exclude_list
- subscriber_set -= set(exclude_list)
-
- #print 'final subscriber set is ', subscriber_set
- return list(subscriber_set)
+ return subscriber_set - set(exclude_list)
def _comment__get_instant_notification_subscribers(
self,
@@ -1068,13 +1201,7 @@ class Post(models.Model):
subscriber_set.update(global_subscribers)
- #print 'exclude list is: ', exclude_list
- if exclude_list:
- subscriber_set -= set(exclude_list)
-
- #print 'final list of subscribers:', subscriber_set
-
- return list(subscriber_set)
+ return subscriber_set - set(exclude_list)
def get_instant_notification_subscribers(
self, potential_subscribers = None,
@@ -1093,7 +1220,7 @@ class Post(models.Model):
exclude_list=exclude_list
)
elif self.is_tag_wiki() or self.is_reject_reason():
- return list()
+ return set()
else:
raise NotImplementedError
@@ -1101,6 +1228,34 @@ class Post(models.Model):
# for subscriber in subscribers:
return self.filter_authorized_users(subscribers)
+ def get_notify_sets(self, mentioned_users=None, exclude_list=None):
+ """returns three lists in a dictionary with keys:
+ * 'for_inbox' - users for which to add inbox items
+ * 'for_mentions' - for whom mentions are added
+ * 'for_email' - to whom email notifications should be sent
+ """
+ result = dict()
+ result['for_mentions'] = set(mentioned_users) - set(exclude_list)
+ #what users are included depends on the post type
+ #for example for question - all Q&A contributors
+ #are included, for comments only authors of comments and parent
+ #post are included
+ result['for_inbox'] = self.get_response_receivers(exclude_list=exclude_list)
+
+ if askbot_settings.ENABLE_EMAIL_ALERTS == False:
+ result['for_email'] = set()
+ else:
+ #todo: weird thing is that only comments need the recipients
+ #todo: debug these calls and then uncomment in the repo
+ #argument to this call
+ result['for_email'] = self.get_instant_notification_subscribers(
+ potential_subscribers=result['for_inbox'],
+ mentioned_users=result['for_mentions'],
+ exclude_list=exclude_list
+ )
+ return result
+
+
def get_latest_revision(self):
return self.revisions.order_by('-revised_at')[0]
@@ -1291,9 +1446,8 @@ class Post(models.Model):
def _question__assert_is_visible_to(self, user):
"""raises QuestionHidden"""
- if askbot_settings.ENABLE_CONTENT_MODERATION:
- if self.approved == False:
- raise exceptions.QuestionHidden()
+ if self.is_approved() is False:
+ raise exceptions.QuestionHidden()
if self.deleted:
message = _(
'Sorry, this question has been '
@@ -1353,7 +1507,8 @@ class Post(models.Model):
is hidden due to group memberships"""
assert(self.is_comment() == False)
post_groups = self.groups.all()
- if post_groups.count() == 0:
+ global_group_name = askbot_settings.GLOBAL_GROUP_NAME
+ if post_groups.filter(name=global_group_name).count() == 1:
return
if self.is_question():#todo maybe merge the "hidden" exceptions
@@ -1413,7 +1568,6 @@ class Post(models.Model):
else:
return const.TYPE_ACTIVITY_UPDATE_REJECT_REASON, self
-
raise NotImplementedError
def get_tag_names(self):
@@ -1421,13 +1575,14 @@ class Post(models.Model):
def __apply_edit(
self,
- edited_at = None,
- edited_by = None,
- text = None,
- comment = None,
- wiki = False,
- edit_anonymously = False,
- by_email = False
+ edited_at=None,
+ edited_by=None,
+ text=None,
+ comment=None,
+ wiki=False,
+ edit_anonymously=False,
+ is_private=False,
+ by_email=False
):
if text is None:
text = self.get_latest_revision().text
@@ -1455,7 +1610,7 @@ class Post(models.Model):
by_email = by_email
)
- self.parse_and_save(author = edited_by)
+ self.parse_and_save(author=edited_by, is_private=is_private)
def _answer__apply_edit(
self,
@@ -1468,20 +1623,21 @@ class Post(models.Model):
by_email = False
):
- #it is important to do this before __apply_edit b/c of signals!!!
+ ##it is important to do this before __apply_edit b/c of signals!!!
if self.is_private() != is_private:
if is_private:
self.make_private(self.author)
else:
- self.make_public(self.author)
+ self.make_public()
self.__apply_edit(
- edited_at = edited_at,
- edited_by = edited_by,
- text = text,
- comment = comment,
- wiki = wiki,
- by_email = by_email
+ edited_at=edited_at,
+ edited_by=edited_by,
+ text=text,
+ comment=comment,
+ wiki=wiki,
+ by_email=by_email,
+ is_private=is_private
)
if edited_at is None:
@@ -1516,20 +1672,22 @@ class Post(models.Model):
self.thread.tagnames = tags
self.thread.save()
+ ##it is important to do this before __apply_edit b/c of signals!!!
if self.is_private() != is_private:
if is_private:
self.make_private(self.author)
else:
- self.make_public(self.author)
+ self.make_public()
self.__apply_edit(
- edited_at = edited_at,
- edited_by = edited_by,
- text = text,
- comment = comment,
- wiki = wiki,
- edit_anonymously = edit_anonymously,
- by_email = by_email
+ edited_at=edited_at,
+ edited_by=edited_by,
+ text=text,
+ comment=comment,
+ wiki=wiki,
+ edit_anonymously=edit_anonymously,
+ is_private=is_private,
+ by_email=by_email
)
self.thread.set_last_activity(last_activity_at=edited_at, last_activity_by=edited_by)
@@ -1640,9 +1798,7 @@ class Post(models.Model):
for answer in question.thread.posts.get_answers().all():
recipients.update(answer.get_author_list())
- recipients -= set(exclude_list)
-
- return list(recipients)
+ return recipients - set(exclude_list)
def _question__get_response_receivers(self, exclude_list = None):
"""returns list of users who might be interested
@@ -1663,8 +1819,7 @@ class Post(models.Model):
for a in self.thread.posts.get_answers().all():
recipients.update(a.get_author_list())
- recipients -= set(exclude_list)
- return recipients
+ return recipients - set(exclude_list)
def _comment__get_response_receivers(self, exclude_list = None):
"""Response receivers are commenters of the
@@ -1678,9 +1833,7 @@ class Post(models.Model):
include_comments = True,
)
)
- users -= set(exclude_list)
- return list(users)
-
+ return users - set(exclude_list)
def get_response_receivers(self, exclude_list = None):
"""returns a list of response receiving users
@@ -1693,7 +1846,7 @@ class Post(models.Model):
elif self.is_comment():
receivers = self._comment__get_response_receivers(exclude_list)
elif self.is_tag_wiki() or self.is_reject_reason():
- return list()#todo: who should get these?
+ return set()#todo: who should get these?
else:
raise NotImplementedError
@@ -1938,12 +2091,13 @@ class PostRevision(models.Model):
super(PostRevision, self).save(**kwargs)
- @models.permalink
def get_absolute_url(self):
if self.post.is_question():
- return 'question_revisions', (self.post.id,), {}
+ return reverse('question_revisions', args = (self.post.id,))
elif self.post.is_answer():
- return 'answer_revisions', (), {'id':self.post.id}
+ return reverse('answer_revisions', kwargs = {'id':self.post.id})
+ else:
+ return self.post.get_absolute_url()
def get_question_title(self):
#INFO: ack-grepping shows that it's only used for Questions, so there's no code for Answers
@@ -1980,7 +2134,21 @@ class PostFlagReason(models.Model):
app_label = 'askbot'
-class AnonymousAnswer(AnonymousContent):
+class DraftAnswer(models.Model):
+ """Provides space for draft answers,
+ note that unlike ``AnonymousAnswer`` the foreign key
+ is going to ``Thread`` as it should.
+ """
+ thread = models.ForeignKey('Thread', related_name='draft_answers')
+ author = models.ForeignKey(User, related_name='draft_answers')
+ text = models.TextField(null=True)
+
+ class Meta:
+ app_label = 'askbot'
+
+
+class AnonymousAnswer(DraftContent):
+ """Todo: re-route the foreign key to ``Thread``"""
question = models.ForeignKey(Post, related_name='anonymous_answers')
def publish(self, user):
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 4e2b3d0d..a700352b 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -15,12 +15,16 @@ import askbot
from askbot.conf import settings as askbot_settings
from askbot import mail
from askbot.mail import messages
-from askbot.models.tag import Tag, get_groups, get_tags_by_names
+from askbot.models.tag import Tag
+from askbot.models.tag import get_groups
+from askbot.models.tag import get_global_group
+from askbot.models.tag import get_tags_by_names
from askbot.models.tag import filter_accepted_tags, filter_suggested_tags
from askbot.models.tag import delete_tags, separate_unused_tags
-from askbot.models.base import AnonymousContent, BaseQuerySetManager
+from askbot.models.base import DraftContent, BaseQuerySetManager
from askbot.models.tag import Tag, get_groups
from askbot.models.post import Post, PostRevision
+from askbot.models.post import PostToGroup
from askbot.models import signals
from askbot import const
from askbot.utils.lists import LazyList
@@ -30,14 +34,13 @@ from askbot.skins.loaders import get_template #jinja2 template loading enviromen
from askbot.search.state_manager import DummySearchState
class ThreadQuerySet(models.query.QuerySet):
- def exclude_group_private(self, user):
+ def get_visible(self, user):
"""filters out threads not belonging to the user groups"""
if user.is_authenticated():
- groups = user.get_foreign_groups()
+ groups = user.get_groups()
else:
- groups = get_groups()
- #todo: maybe use is_private field
- return self.exclude(groups__in = groups)
+ groups = [get_global_group()]
+ return self.filter(groups__in=groups).distinct()
class ThreadManager(BaseQuerySetManager):
@@ -90,6 +93,7 @@ class ThreadManager(BaseQuerySetManager):
tagnames = None,
is_anonymous = False,
is_private = False,
+ group_id = None,
by_email = False,
email_address = None
):
@@ -131,17 +135,23 @@ class ThreadManager(BaseQuerySetManager):
question.parse_and_save(author = author, is_private = is_private)
revision = question.add_revision(
- author = author,
- is_anonymous = is_anonymous,
- text = text,
- comment = const.POST_STATUS['default_version'],
- revised_at = added_at,
- by_email = by_email,
- email_address = email_address
+ author=author,
+ is_anonymous=is_anonymous,
+ text=text,
+ comment=const.POST_STATUS['default_version'],
+ revised_at=added_at,
+ by_email=by_email,
+ email_address=email_address
)
- if is_private:#add groups to thread and question
- thread.make_private(author)
+ author_group = author.get_personal_group()
+ thread.add_to_groups([author_group])
+ question.add_to_groups([author_group])
+
+ if is_private or group_id:#add groups to thread and question
+ thread.make_private(author, group_id=group_id)
+ else:
+ thread.make_public()
# INFO: Question has to be saved before update_tags() is called
thread.update_tags(tagnames = tagnames, user = author, timestamp = added_at)
@@ -198,7 +208,7 @@ class ThreadManager(BaseQuerySetManager):
#that are private in groups to which current user does not belong
if askbot_settings.GROUPS_ENABLED:
#get group names
- qs = qs.exclude_group_private(user = request_user)
+ qs = qs.get_visible(user=request_user)
#run text search while excluding any modifier in the search string
@@ -488,7 +498,7 @@ class Thread(models.Model):
"""returns answer count depending on who the user is.
When user groups are enabled and some answers are hidden,
the answer count to show must be reflected accordingly"""
- if askbot_settings.GROUPS_ENABLED == False or user is None:
+ if askbot_settings.GROUPS_ENABLED == False:
return self.answer_count
else:
return self.get_answers(user).count()
@@ -556,9 +566,9 @@ class Thread(models.Model):
else:
return self.title
- def format_for_email(self):
+ def format_for_email(self, user=None):
"""experimental function: output entire thread for email"""
- question, answers, junk = self.get_cached_post_data()
+ question, answers, junk = self.get_cached_post_data(user=user)
output = question.format_for_email_as_subthread()
if answers:
answer_heading = ungettext(
@@ -571,6 +581,14 @@ class Thread(models.Model):
output += answer.format_for_email_as_subthread()
return output
+ def get_answers_by_user(self, user):
+ """regardless - deleted or not"""
+ return self.posts.filter(post_type = 'answer', author = user)
+
+ def has_answer_by_user(self, user):
+ #use len to cache the queryset
+ return len(self.get_answers_by_user(user)) > 0
+
def tagname_meta_generator(self):
return u','.join([unicode(tag) for tag in self.get_tag_names()])
@@ -588,7 +606,8 @@ class Thread(models.Model):
return self.posts.get_answers(user = user)
else:
return self.posts.get_answers(user = user).filter(
- models.Q(deleted = False) | models.Q(author = user) \
+ models.Q(deleted = False) \
+ | models.Q(author = user) \
| models.Q(deleted_by = user)
)
@@ -634,10 +653,12 @@ class Thread(models.Model):
thread_posts = self.posts.all()
if askbot_settings.GROUPS_ENABLED:
if user is None or user.is_anonymous():
- exclude_groups = get_groups()
+ groups = (get_global_group(),)
else:
- exclude_groups = user.get_foreign_groups()
- thread_posts = thread_posts.exclude(groups__in = exclude_groups)
+ groups = user.get_groups()
+
+ thread_posts = thread_posts.filter(groups__in=groups)
+ thread_posts = thread_posts.distinct()#important for >1 group
thread_posts = thread_posts.order_by(
{
@@ -656,7 +677,7 @@ class Thread(models.Model):
#pass through only deleted question posts
if post.deleted and post.post_type != 'question':
continue
- if post.approved == False:#hide posts on the moderation queue
+ if post.is_approved() is False:#hide posts on the moderation queue
continue
post_to_author[post.id] = post.author_id
@@ -717,6 +738,8 @@ class Thread(models.Model):
"""
def get_data():
+ # todo: code in this function would be simpler if
+ # we had question post id denormalized on the thread
tags_list = self.get_tag_names()
similar_threads = Thread.objects.filter(
tags__name__in=tags_list
@@ -735,20 +758,28 @@ class Thread(models.Model):
similar_threads = similar_threads[:10]
# Denormalize questions to speed up template rendering
+ # todo: just denormalize question_post_id on the thread!
thread_map = dict([(thread.id, thread) for thread in similar_threads])
questions = Post.objects.get_questions()
questions = questions.select_related('thread').filter(thread__in=similar_threads)
for q in questions:
thread_map[q.thread_id].question_denorm = q
- # Postprocess data
- similar_threads = [
- {
- 'url': thread.question_denorm.get_absolute_url(),
- 'title': thread.get_title(thread.question_denorm)
- } for thread in similar_threads
- ]
- return similar_threads
+ # Postprocess data for the final output
+ result = list()
+ for thread in similar_threads:
+ question_post = getattr(thread, 'question_denorm', None)
+ # unfortunately the if statement below is necessary due to
+ # a possible bug
+ # all this proves that it's wrong to reference threads by
+ # the question post id in the question page urls!!!
+ # this is a "legacy" problem inherited from the old models
+ if question_post:
+ url = question_post.get_absolute_url()
+ title = thread.get_title(question_post)
+ result.append({'url': url, 'title': title})
+
+ return result
def get_cached_data():
"""similar thread data will expire
@@ -782,13 +813,77 @@ class Thread(models.Model):
return self.followed_by.filter(id = user.id).count() > 0
return False
- def make_private(self, user):
- groups = list(user.get_groups())
+ def add_child_posts_to_groups(self, groups):
+ """adds questions and answers of the thread to
+ given groups, comments are taken care of implicitly
+ by the underlying ``Post`` methods
+ """
+ post_types = ('question', 'answer')
+ posts = self.posts.filter(post_type__in=post_types)
+ for post in posts:
+ post.add_to_groups(groups)
+
+ def remove_child_posts_from_groups(self, groups):
+ """removes child posts from given groups"""
+ post_ids = self.posts.all().values_list('id', flat=True)
+ group_ids = [group.id for group in groups]
+ PostToGroup.objects.filter(
+ post__id__in=post_ids,
+ tag__id__in=group_ids
+ ).delete()
+
+ def add_to_groups(self, groups, recursive=False):
+ """adds thread to a list of groups
+ ``groups`` argument may be any iterable of groups
+ """
self.groups.add(*groups)
- self._question_post().groups.add(*groups)
+ if recursive == True:
+ #comments are taken care of automatically
+ self.add_child_posts_to_groups(groups)
+
+ def remove_from_groups(self, groups, recursive=False):
+ self.groups.remove(*groups)
+ if recursive == True:
+ self.remove_child_posts_from_groups(groups)
+
+ def make_public(self, recursive=False):
+ """adds the global group to the thread"""
+ groups = (get_global_group(), )
+ self.add_to_groups(groups, recursive=recursive)
+ if recursive == False:
+ self._question_post().make_public()
+
+ def make_private(self, user, group_id = None):
+ """adds thread to all user's groups, excluding
+ the global, or to a group given by id.
+ The add by ID now only works if user belongs to that group
+ """
+ if group_id:
+ group = Tag.group_tags.get(id=group_id)
+ groups = [group]
+ self.add_to_groups(groups)
+
+ global_group = get_global_group()
+ if group != global_group:
+ self.remove_from_groups((global_group,))
+ else:
+ groups = user.get_groups(private=True)
+ self.add_to_groups(groups)
+ self.remove_from_groups((get_global_group(),))
+
+ self._question_post().make_private(user, group_id)
+
+ if len(groups) == 0:
+ message = 'Sharing did not work, because group is unknown'
+ user.message_set.create(message=message)
def is_private(self):
- return askbot_settings.GROUPS_ENABLED and self.groups.count() > 0
+ """true, if thread belongs to the global group"""
+ if askbot_settings.GROUPS_ENABLED:
+ group = get_global_group()
+ return not self.groups.filter(id=group.id).exists()
+ return False
+
def remove_tags_by_names(self, tagnames):
"""removes tags from thread by names"""
@@ -818,6 +913,9 @@ class Thread(models.Model):
*IMPORTANT*: self._question_post() has to
exist when update_tags() is called!
"""
+ if tagnames.strip() == '':
+ return
+
previous_tags = list(self.tags.filter(status = Tag.STATUS_ACCEPTED))
ordered_updated_tagnames = [t for t in tagnames.strip().split(' ')]
@@ -1049,7 +1147,20 @@ class FavoriteQuestion(models.Model):
return '[%s] favorited at %s' %(self.user, self.added_at)
-class AnonymousQuestion(AnonymousContent):
+class DraftQuestion(models.Model):
+ """Provides space to solve unpublished draft
+ questions. Contents is used to populate the Ask form.
+ """
+ author = models.ForeignKey(User)
+ title = models.CharField(max_length=300, null=True)
+ text = models.TextField(null=True)
+ tagnames = models.CharField(max_length=125, null=True)
+
+ class Meta:
+ app_label = 'askbot'
+
+
+class AnonymousQuestion(DraftContent):
"""question that was asked before logging in
maybe the name is a little misleading, the user still
may or may not want to stay anonymous after the question
@@ -1061,6 +1172,7 @@ class AnonymousQuestion(AnonymousContent):
def publish(self,user):
added_at = datetime.datetime.now()
+ #todo: wrong - use User.post_question() instead
Thread.objects.create_new(
title = self.title,
added_at = added_at,
diff --git a/askbot/models/tag.py b/askbot/models/tag.py
index 50edb801..7f5126c8 100644
--- a/askbot/models/tag.py
+++ b/askbot/models/tag.py
@@ -1,4 +1,5 @@
import re
+import logging
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
@@ -7,6 +8,25 @@ from askbot import const
from askbot.conf import settings as askbot_settings
from askbot.utils import category_tree
+def get_global_group():
+ """Returns the global group,
+ if necessary, creates one
+ """
+ #todo: when groups are disconnected from tags,
+ #find comment as shown below in the test cases and
+ #revert the values
+ #todo: change groups to django groups
+ group_name = askbot_settings.GLOBAL_GROUP_NAME
+ try:
+ return Tag.group_tags.get(name=group_name)
+ except Tag.DoesNotExist:
+ from askbot.models import get_admin
+ return Tag.group_tags.get_or_create(
+ group_name=group_name,
+ user=get_admin(),
+ is_open=False
+ )
+
def delete_tags(tags):
"""deletes tags in the list"""
tag_ids = [tag.id for tag in tags]
@@ -40,6 +60,11 @@ def filter_accepted_tags(tags):
def filter_suggested_tags(tags):
return filter_tags_by_status(tags, status = Tag.STATUS_SUGGESTED)
+def format_personal_group_name(user):
+ #todo: after migration of groups away from tags,
+ #this function will be moved somewhere else
+ return '_internal_%s_%d' % (user.username, user.id)
+
def is_preapproved_tag_name(tag_name):
"""true if tag name is in the category tree
or any other container of preapproved tags"""
@@ -256,8 +281,14 @@ class TagManager(BaseQuerySetManager):
class GroupTagQuerySet(TagQuerySet):
"""Custom query set for the group"""
- def get_for_user(self, user = None):
- return self.filter(user_memberships__user = user)
+ def get_for_user(self, user=None, private=False):
+ if private:
+ global_group = get_global_group()
+ return self.filter(
+ user_memberships__user=user
+ ).exclude(id=global_group.id)
+ else:
+ return self.filter(user_memberships__user = user)
def get_all(self):
return self.annotate(
@@ -282,7 +313,7 @@ class GroupTagManager(BaseQuerySetManager):
def get_query_set(self):
return GroupTagQuerySet(self.model)
- def get_or_create(self, group_name = None, user = None):
+ def get_or_create(self, group_name = None, user = None, is_open=True):
"""creates a group tag or finds one, if exists"""
#todo: here we might fill out the group profile
@@ -296,7 +327,7 @@ class GroupTagManager(BaseQuerySetManager):
tag = self.model(name = group_name, created_by = user)
tag.save()
from askbot.models.user import GroupProfile
- group_profile = GroupProfile(group_tag = tag)
+ group_profile = GroupProfile(group_tag = tag, is_open=is_open)
group_profile.save()
return tag
diff --git a/askbot/models/user.py b/askbot/models/user.py
index e4077ea5..14c1d189 100644
--- a/askbot/models/user.py
+++ b/askbot/models/user.py
@@ -11,6 +11,7 @@ from django.forms import EmailField, URLField
from django.utils.translation import ugettext as _
from django.utils.html import strip_tags
from askbot import const
+from askbot.conf import settings as askbot_settings
from askbot.utils import functions
from askbot.models.tag import Tag
from askbot.forms import DomainNameField
@@ -380,6 +381,10 @@ class GroupProfile(models.Model):
if user.is_anonymous():
return False
+ #a special case - automatic global group cannot be joined or left
+ if self.group_tag.name == askbot_settings.GLOBAL_GROUP_NAME:
+ return False
+
if self.is_open:
return True
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index 3e121ca6..7e65c833 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -181,6 +181,8 @@ INSTALLED_APPS = (
CACHE_BACKEND = 'locmem://'
#needed for django-keyedcache
CACHE_TIMEOUT = 6000
+#sets a special timeout for livesettings if you want to make them different
+LIVESETTINGS_CACHE_TIMEOUT = CACHE_TIMEOUT
CACHE_PREFIX = 'askbot' #make this unique
CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
#If you use memcache you may want to uncomment the following line to enable memcached based sessions
diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache
index 820ba308..56b15da0 100644
--- a/askbot/setup_templates/settings.py.mustache
+++ b/askbot/setup_templates/settings.py.mustache
@@ -180,6 +180,8 @@ INSTALLED_APPS = (
CACHE_BACKEND = 'locmem://'
#needed for django-keyedcache
CACHE_TIMEOUT = 6000
+#sets a special timeout for livesettings if you want to make them different
+LIVESETTINGS_CACHE_TIMEOUT = CACHE_TIMEOUT
CACHE_PREFIX = 'askbot' #make this unique
CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
#If you use memcache you may want to uncomment the following line to enable memcached based sessions
diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js
index 0df9da64..46d19e6d 100644
--- a/askbot/skins/common/media/js/post.js
+++ b/askbot/skins/common/media/js/post.js
@@ -206,6 +206,148 @@ var CPValidator = function(){
/**
* @constructor
+ */
+var DraftPost = function() {
+ WrappedElement.call(this);
+};
+inherits(DraftPost, WrappedElement);
+
+/**
+ * @return {string}
+ */
+DraftPost.prototype.getUrl = function() {
+ throw 'Not Implemented';
+};
+
+/**
+ * @return {boolean}
+ */
+DraftPost.prototype.shouldSave = function() {
+ throw 'Not Implemented';
+};
+
+/**
+ * @return {object} data dict
+ */
+DraftPost.prototype.getData = function() {
+ throw 'Not Implemented';
+};
+
+DraftPost.prototype.backupData = function() {
+ this._old_data = this.getData();
+};
+
+DraftPost.prototype.showNotification = function() {
+ var note = $('.editor-status span');
+ note.hide();
+ note.html(gettext('draft saved...'));
+ note.fadeIn().delay(3000).fadeOut();
+};
+
+DraftPost.prototype.getSaveHandler = function() {
+ var me = this;
+ return function(save_synchronously) {
+ if (me.shouldSave()) {
+ $.ajax({
+ type: 'POST',
+ cache: false,
+ dataType: 'json',
+ async: save_synchronously ? false : true,
+ url: me.getUrl(),
+ data: me.getData(),
+ success: function(data) {
+ if (data['success'] && !save_synchronously) {
+ me.showNotification();
+ }
+ me.backupData();
+ }
+ });
+ }
+ };
+};
+
+DraftPost.prototype.decorate = function(element) {
+ this._element = element;
+ this.assignContentElements();
+ this.backupData();
+ setInterval(this.getSaveHandler(), 5000);//auto-save twice a minute
+ var me = this;
+ window.onbeforeunload = function() {
+ var saveHandler = me.getSaveHandler();
+ saveHandler(true);
+ //var msg = gettext("%s, we've saved your draft, but...");
+ //return interpolate(msg, [askbot['data']['userName']]);
+ };
+};
+
+
+/**
+ * @contstructor
+ */
+var DraftQuestion = function() {
+ DraftPost.call(this);
+};
+inherits(DraftQuestion, DraftPost);
+
+DraftQuestion.prototype.getUrl = function() {
+ return askbot['urls']['saveDraftQuestion'];
+};
+
+DraftQuestion.prototype.shouldSave = function() {
+ var newd = this.getData();
+ var oldd = this._old_data;
+ return (
+ newd['title'] !== oldd['title'] ||
+ newd['text'] !== oldd['text'] ||
+ newd['tagnames'] !== oldd['tagnames']
+ );
+};
+
+DraftQuestion.prototype.getData = function() {
+ return {
+ 'title': this._title_element.val(),
+ 'text': this._text_element.val(),
+ 'tagnames': this._tagnames_element.val()
+ };
+};
+
+DraftQuestion.prototype.assignContentElements = function() {
+ this._title_element = $('#id_title');
+ this._text_element = $('#editor');
+ this._tagnames_element = $('#id_tags');
+};
+
+var DraftAnswer = function() {
+ DraftPost.call(this);
+};
+inherits(DraftAnswer, DraftPost);
+
+DraftAnswer.prototype.setThreadId = function(id) {
+ this._threadId = id;
+};
+
+DraftAnswer.prototype.getUrl = function() {
+ return askbot['urls']['saveDraftAnswer'];
+};
+
+DraftAnswer.prototype.shouldSave = function() {
+ return this.getData()['text'] !== this._old_data['text'];
+};
+
+DraftAnswer.prototype.getData = function() {
+ return {
+ 'text': this._textElement.val(),
+ 'thread_id': this._threadId
+ };
+};
+
+DraftAnswer.prototype.assignContentElements = function() {
+ this._textElement = $('#editor');
+};
+
+
+/**
+ * @constructor
* @extends {SimpleControl}
* @param {Comment} comment to upvote
*/
@@ -3676,6 +3818,60 @@ $(document).ready(function() {
questionRetagger.init();
}
socialSharing.init();
+
+ var proxyUserNameInput = $('#id_post_author_username');
+ var proxyUserEmailInput = $('#id_post_author_email');
+ if (proxyUserNameInput.length === 1) {
+ var tip = new TippedInput();
+ tip.decorate(proxyUserNameInput);
+
+ var userSelectHandler = function(data) {
+ proxyUserEmailInput.val(data['data'][0]);
+ };
+
+ var fakeUserAc = new AutoCompleter({
+ url: '/get-users-info/',//askbot['urls']['get_users_info'],
+ preloadData: true,
+ minChars: 1,
+ useCache: true,
+ matchInside: true,
+ maxCacheLength: 100,
+ delay: 10,
+ onItemSelect: userSelectHandler
+ });
+ fakeUserAc.decorate(proxyUserNameInput);
+ }
+ if (proxyUserEmailInput.length === 1) {
+ var tip = new TippedInput();
+ tip.decorate(proxyUserEmailInput);
+ }
+ //if groups are enabled - activate share functions
+ var groupsInput = $('#share_group_name');
+ if (groupsInput.length === 1) {
+ var groupsAc = new AutoCompleter({
+ url: askbot['urls']['getGroupsList'],
+ preloadData: true,
+ minChars: 1,
+ useCache: false,
+ matchInside: true,
+ maxCacheLength: 100,
+ delay: 10
+ });
+ groupsAc.decorate(groupsInput);
+ }
+ var usersInput = $('#share_user_name');
+ if (usersInput.length === 1) {
+ var usersAc = new AutoCompleter({
+ url: '/get-users-info/',
+ preloadData: true,
+ minChars: 1,
+ useCache: false,
+ matchInside: true,
+ maxCacheLength: 100,
+ delay: 10
+ });
+ usersAc.decorate(usersInput);
+ }
});
diff --git a/askbot/skins/common/media/js/user.js b/askbot/skins/common/media/js/user.js
index ad0b8365..4795b7d2 100644
--- a/askbot/skins/common/media/js/user.js
+++ b/askbot/skins/common/media/js/user.js
@@ -112,8 +112,8 @@ var setup_inbox = function(){
}
);
- var reject_post_dialog = new RejectPostDialog();
- reject_post_dialog.decorate($('#reject-edit-modal'));
+ var rejectPostDialog = new RejectPostDialog();
+ rejectPostDialog.decorate($('#reject-edit-modal'));
setupButtonEventHandlers(
$('#re_delete_post'),
function(){
@@ -121,10 +121,21 @@ var setup_inbox = function(){
if (data['id_list'].length === 0){
return;
}
- reject_post_dialog.setSelectedEditData(data);
- reject_post_dialog.show();
+ rejectPostDialog.setSelectedEditData(data);
+ rejectPostDialog.show();
}
);
+
+ if ($('body').hasClass('inbox-flags')) {
+ var responses = $('.response-parent');
+ responses.each(function(idx, response) {
+ var control = new PostModerationControls();
+ control.setParent($(response));
+ control.setReasonsDialog(rejectPostDialog);
+ rejectPostDialog.addPostModerationControl(control);
+ $(response).append(control.getElement());
+ });
+ }
//setupButtonEventHandlers($('.re_expand'),
// function(e){
// e.preventDefault();
@@ -154,6 +165,125 @@ var setup_badge_details_toggle = function(){
});
};
+var PostModerationControls = function() {
+ WrappedElement.call(this);
+};
+inherits(PostModerationControls, WrappedElement);
+
+PostModerationControls.prototype.setParent = function(parent_element) {
+ this._parent_element = parent_element;
+};
+
+PostModerationControls.prototype.setReasonsDialog = function(dialog) {
+ this._reasonsDialog = dialog;
+};
+
+PostModerationControls.prototype.getMemoId = function() {
+ return this._parent_element.data('responseId');
+};
+
+PostModerationControls.prototype.removeMemo = function() {
+ var reId = this.getMemoId();
+ $('#re_' + reId).remove();
+};
+
+PostModerationControls.prototype.addReason = function(id, title) {
+ var li = this.makeElement('li');
+ var anchor = this.makeElement('a');
+ anchor.html(title);
+ anchor.data('postId', id);
+ li.append(anchor);
+ var adderLink = this._reasonList.children().last();
+ adderLink.before(li);
+ //attach event handler
+ var me = this;
+ setupButtonEventHandlers(anchor, function() { me.moderatePost(id, 'delete_post') });
+};
+
+PostModerationControls.prototype.moderatePost = function(reasonId, actionType){
+ var me = this;
+ var data = {
+ reject_reason_id: reasonId,
+ memo_list: [me.getMemoId()],
+ action_type: actionType
+ };
+ $.ajax({
+ type: 'POST',
+ dataType: 'json',
+ cache: false,
+ data: JSON.stringify(data),
+ url: askbot['urls']['manageInbox'],
+ success: function(data){
+ if (data['success']){
+ me.removeMemo();
+ me.dispose();
+ if (actionType === 'delete') {
+ notify.show(gettext('Post deleted'));
+ } else if (actionType === 'remove_flag') {
+ notify.show(gettext('Post approved'));
+ }
+ } else {
+ notify.show(data['message']);
+ }
+ }
+ });
+};
+
+
+PostModerationControls.prototype.createDom = function() {
+ var toolbar = this.makeElement('div');
+ toolbar.addClass('btn-toolbar post-moderation-controls');
+ this._element = toolbar;
+
+ var div = this.makeElement('div');
+ div.addClass('btn-group');
+ toolbar.append(div);
+
+ var acceptBtn = this.makeElement('a');
+ acceptBtn.addClass('btn save-reason');
+ acceptBtn.html(gettext('Accept'));
+ div.append(acceptBtn);
+
+ div = this.makeElement('div');
+ div.addClass('btn-group dropdown');
+ toolbar.append(div);
+
+ var toggle = this.makeElement('button');
+ toggle.addClass('btn btn-danger dropdown-toggle');
+ toggle.append($('<span>' + gettext('Reject') + '</span>'));
+ toggle.append($('<span class="caret"></span>'));
+ div.append(toggle);
+
+ toggle.dropdown();
+
+ var ul = this.makeElement('ul');
+ ul.addClass('dropdown-menu');
+ div.append(ul);
+
+ this._reasonList = ul;
+
+ //reason adder
+ var li = this.makeElement('li');
+ var anchor = this.makeElement('a');
+ anchor.html(gettext('add new reject reason'));
+ li.append(anchor);
+ ul.append(li);
+
+ //append menu items
+ var me = this;
+ $.each(askbot['data']['postRejectReasons'], function(idx, reason) {
+ me.addReason(reason['id'], reason['title']);
+ });
+
+ var reasonsDlg = this._reasonsDialog;
+ setupButtonEventHandlers(anchor, function() {
+ reasonsDlg.show();
+ });
+ setupButtonEventHandlers(acceptBtn, function() {
+ me.moderatePost(null, 'remove_flag');
+ });
+};
+
/**
* @constructor
* manages post/edit reject reasons
@@ -164,6 +294,7 @@ var RejectPostDialog = function(){
this._selected_edit_ids = null;
this._selected_reason_id = null;
this._state = null;//'select', 'preview', 'add-new'
+ this._postModerationControls = [];
};
inherits(RejectPostDialog, WrappedElement);
@@ -171,6 +302,10 @@ RejectPostDialog.prototype.setSelectedEditData = function(data){
this._selected_edit_data = data;
};
+RejectPostDialog.prototype.addPostModerationControl = function(control) {
+ this._postModerationControls.push(control);
+};
+
RejectPostDialog.prototype.setState = function(state){
this._state = state;
this.clearErrors();
@@ -274,6 +409,13 @@ RejectPostDialog.prototype.addSelectableReason = function(data){
var title = data['title'];
var details = data['details'];
this._select_box.addItem(id, title, details);
+
+ askbot['data']['postRejectReasons'].push(
+ {id: data['reason_id'], title: data['title']}
+ );
+ $.each(this._postModerationControls, function(idx, control) {
+ control.addReason(data['reason_id'], data['title']);
+ });
};
RejectPostDialog.prototype.startSavingReason = function(callback){
@@ -313,10 +455,10 @@ RejectPostDialog.prototype.startSavingReason = function(callback){
success: function(data){
if (data['success']){
//show current reason data and focus on it
+ me.addSelectableReason(data);
if (callback){
callback(data);
} else {
- me.addSelectableReason(data);
me.setState('select');
}
} else {
@@ -334,7 +476,7 @@ RejectPostDialog.prototype.rejectPost = function(reason_id){
reject_reason_id: reason_id,
memo_list: memo_ids,
action_type: 'delete_post'
- }
+ };
$.ajax({
type: 'POST',
dataType: 'json',
@@ -427,7 +569,7 @@ RejectPostDialog.prototype.decorate = function(element){
var title_input = new TippedInput();
title_input.decorate($(reject_title_input));
this._title_input = title_input;
-
+
var reject_details_input = $(this._element)
.find('textarea.reject-reason-details');
@@ -789,10 +931,10 @@ GroupAdderWidget.prototype.decorate = function(element){
this._input = input;
var groupsAc = new AutoCompleter({
- url: askbot['urls']['get_groups_list'],
+ url: askbot['urls']['getGroupsList'],
preloadData: true,
minChars: 1,
- useCache: true,
+ useCache: false,
matchInside: false,
maxCacheLength: 100,
delay: 10
diff --git a/askbot/skins/common/media/js/utils.js b/askbot/skins/common/media/js/utils.js
index 15bac827..e8974e3b 100644
--- a/askbot/skins/common/media/js/utils.js
+++ b/askbot/skins/common/media/js/utils.js
@@ -1,6 +1,10 @@
//var $, scriptUrl, askbotSkin
+/**
+ * attention - this function needs to be retired
+ * as it cannot accurately give url to the media file
+ */
var mediaUrl = function(resource){
- return askbot['settings']['static_url'] + askbotSkin + '/' + resource;
+ return askbot['settings']['static_url'] + 'default' + '/' + resource;
};
var cleanUrl = function(url){
@@ -110,7 +114,7 @@ var setCheckBoxesIn = function(selector, value){
var notify = function() {
var visible = false;
return {
- show: function(html) {
+ show: function(html, autohide) {
if (html) {
$("body").addClass('user-messages');
var par = $('<p class="notification"></p>');
@@ -119,7 +123,19 @@ var notify = function() {
}
$(".notify").fadeIn("slow");
visible = true;
+ if (autohide) {
+ setTimeout(
+ function() {
+ notify.close(false);
+ notify.clear();
+ },
+ 3000
+ );
+ }
},
+ clear: function() {
+ $('.notify').empty();
+ },
close: function(doPostback) {
if (doPostback) {
$.post(
@@ -362,6 +378,7 @@ TippedInput.prototype.decorate = function(element){
var instruction_text = this.getVal();
this._instruction = instruction_text;
+ this.reset();
var me = this;
$(element).focus(function(){
if (me.isBlank()){
diff --git a/askbot/skins/common/templates/widgets/edit_post.html b/askbot/skins/common/templates/widgets/edit_post.html
index a8cbbd00..89d7f6f3 100644
--- a/askbot/skins/common/templates/widgets/edit_post.html
+++ b/askbot/skins/common/templates/widgets/edit_post.html
@@ -25,6 +25,7 @@
{% endif %}
<div class="form-item">
<label for="editor" class="form-error">{{ post_form.text.errors }}</label>
+ <p class="editor-status action-status"><span></span></p>
</div>
{# need label element for resizable input, b/c form validation won't find span #}
{% if post_type == 'question' %}
@@ -72,6 +73,7 @@
<div class="form-error" >{{ post_form.summary.errors }}</div>
</div>
{% endif %}
+
{% if editor_type == 'markdown' %}
<div class="preview-toggle">
<span
@@ -83,3 +85,31 @@
</div>
<div id="previewer" class="wmd-preview"></div>
{% endif %}
+
+{% if user and user.is_authenticated() and user.is_administrator() %}
+ {# admin can post answers or questions on behalf of anyone. #}
+ <table class="proxy-user-info">
+ <tbody>
+ <tr><td colspan="2">
+ <label>
+ {% trans %}To post on behalf of someone else, enter user name <strong>and</strong> email below.{% endtrans %}
+ </label>
+ </td></tr>
+ <tr>
+ <td>
+ <div class="form-item">
+ {{ post_form.post_author_username }}
+ </div>
+ <div class="form-item">
+ {{ post_form.post_author_email }}
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <span class="form-error">{{ post_form.post_author_username.errors }}</span>
+ <span class="form-error">{{ post_form.post_author_email.errors }}</span>
+ </td>
+ </tbody>
+ </table>
+{% endif %}
diff --git a/askbot/skins/default/media/images/OpenSans-CondBold.ttf b/askbot/skins/default/media/images/OpenSans-CondBold.ttf
new file mode 100644
index 00000000..83966f21
--- /dev/null
+++ b/askbot/skins/default/media/images/OpenSans-CondBold.ttf
Binary files differ
diff --git a/askbot/skins/default/media/images/OpenSans-CondLight.ttf b/askbot/skins/default/media/images/OpenSans-CondLight.ttf
new file mode 100644
index 00000000..97c355b9
--- /dev/null
+++ b/askbot/skins/default/media/images/OpenSans-CondLight.ttf
Binary files differ
diff --git a/askbot/skins/default/media/images/OpenSans-CondLightItalic.ttf b/askbot/skins/default/media/images/OpenSans-CondLightItalic.ttf
new file mode 100644
index 00000000..0b45898d
--- /dev/null
+++ b/askbot/skins/default/media/images/OpenSans-CondLightItalic.ttf
Binary files differ
diff --git a/askbot/skins/default/media/style/lib_style.less b/askbot/skins/default/media/style/lib_style.less
index 63389526..05ab38f5 100644
--- a/askbot/skins/default/media/style/lib_style.less
+++ b/askbot/skins/default/media/style/lib_style.less
@@ -14,13 +14,12 @@
@body-font:Arial; /* "Trebuchet MS", sans-serif;*/
@sort-font:Georgia, serif;
-@main-font:'Yanone Kaffeesatz', Arial, sans-serif;
+@main-font:'Open Sans Condensed', Arial, sans-serif;
@secondary-font:Arial;
/* Buttons */
-.button-style(@w:100px ,@h:20px, @f:14px){
- width:@w;
+.button-style(@h:20px, @f:14px){
height:@h;
font-size:@f;
text-align:center;
diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css
index 348eb6b2..c8d92f60 100644
--- a/askbot/skins/default/media/style/style.css
+++ b/askbot/skins/default/media/style/style.css
@@ -195,7 +195,7 @@ body.user-messages {
text-align: center;
background-color: #f5dd69;
border-top: #fff 1px solid;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.notify p.notification {
margin-top: 6px;
@@ -222,7 +222,7 @@ body.user-messages {
#header {
margin-top: 0px;
background: #16160f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.content-wrapper {
/* wrapper positioning class */
@@ -275,12 +275,13 @@ body.user-messages {
float: right;
/* for #header.with-logo it is modified */
+ margin-right: 7px;
}
#metaNav a {
color: #e2e2ae;
padding: 0px 0px 0px 35px;
height: 25px;
- line-height: 30px;
+ line-height: 25px;
margin: 5px 0px 0px 10px;
font-size: 18px;
font-weight: 100;
@@ -337,7 +338,7 @@ body.user-messages {
border-bottom: #d3d3c2 1px solid;
border-top: #fcfcfc 1px solid;
margin-bottom: 10px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
#secondaryHeader #homeButton {
border-right: #afaf9e 1px solid;
@@ -359,11 +360,11 @@ body.user-messages {
float: left;
}
#secondaryHeader #scopeWrapper .scope-selector {
- font-size: 21px;
- color: #5a5a4b;
+ font-size: 20px;
+ color: #7a7a6b;
height: 55px;
line-height: 55px;
- margin-left: 24px;
+ margin-left: 16px;
}
#secondaryHeader #scopeWrapper .on {
background: url(../images/scopearrow.png) no-repeat center bottom;
@@ -376,7 +377,7 @@ body.user-messages {
display: inline-block;
background-color: #fff;
- width: 412px;
+ width: 400px;
border: 1px solid #c9c9b5;
float: right;
height: 42px;
@@ -384,21 +385,22 @@ body.user-messages {
}
#searchBar .searchInput,
#searchBar .searchInputCancelable {
- font-size: 30px;
- height: 40px;
+ font-size: 26px;
+ height: 39px;
font-weight: 300;
background: #FFF;
border: 0px;
color: #484848;
padding-left: 10px;
+ padding-top: 1px;
font-family: Arial;
- vertical-align: middle;
+ vertical-align: top;
}
#searchBar .searchInput {
- width: 352px;
+ width: 340px;
}
#searchBar .searchInputCancelable {
- width: 317px;
+ width: 305px;
}
#searchBar .logoutsearch {
width: 337px;
@@ -450,14 +452,13 @@ body.anon #searchBar .searchInputCancelable {
margin-top: 6px;
float: right;
text-transform: uppercase;
- width: 200px;
height: 42px;
- font-size: 23px;
+ font-size: 20px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -478,6 +479,9 @@ body.anon #searchBar .searchInputCancelable {
-webkit-box-shadow: 1px 1px 2px #636363;
-moz-box-shadow: 1px 1px 2px #636363;
box-shadow: 1px 1px 2px #636363;
+ width: 200px;
+ /* to match width of sidebar */
+
}
#askButton:hover {
background-color: #cde5e9;
@@ -522,7 +526,7 @@ body.anon #searchBar .searchInputCancelable {
.box p {
margin-bottom: 4px;
color: #707070;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.box p.info-box-follow-up-links {
text-align: right;
@@ -539,7 +543,7 @@ body.anon #searchBar .searchInputCancelable {
color: #656565;
padding-right: 10px;
margin-bottom: 10px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
width: 190px;
}
.box h3 {
@@ -547,7 +551,7 @@ body.anon #searchBar .searchInputCancelable {
font-size: 18px;
text-align: left;
font-weight: normal;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
padding-left: 0px;
}
.box .contributorback {
@@ -556,12 +560,10 @@ body.anon #searchBar .searchInputCancelable {
.box label {
color: #707070;
font-size: 15px;
- display: block;
- float: right;
+ vertical-align: bottom;
+ display: inline;
text-align: left;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
- width: 80px;
- margin-right: 18px;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.box #displayTagFilterControl label,
.box #emailTagFilterControl label {
@@ -594,7 +596,7 @@ body.anon #searchBar .searchInputCancelable {
.box .inputs #ignoredTagInput,
.box .inputs #subscribedTagInput,
.box .inputs #ab-tag-search {
- width: 156px;
+ width: 152px;
padding-left: 5px;
border: #c9c9b5 1px solid;
height: 25px;
@@ -609,14 +611,13 @@ body.anon #searchBar .searchInputCancelable {
border: 0;
font-weight: bold;
margin-top: -2px;
- width: 30px;
height: 27px;
font-size: 14px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -672,14 +673,13 @@ body.anon #searchBar .searchInputCancelable {
font-weight: normal;
margin-top: 3px;
display: block;
- width: 120px;
height: 34px;
font-size: 21px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -702,6 +702,7 @@ body.anon #searchBar .searchInputCancelable {
box-shadow: 1px 1px 2px #636363;
margin: 0 auto;
padding: 0;
+ width: 130px;
}
.box a.followed:hover,
.box a.follow:hover {
@@ -740,7 +741,7 @@ body.anon #searchBar .searchInputCancelable {
text-align: center;
}
.box .notify-sidebar #question-subscribe-sidebar {
- margin: 7px 0 0 3px;
+ margin: 0 0 0 3px;
}
.users-page .box label {
display: inline;
@@ -877,20 +878,20 @@ body.anon #searchBar .searchInputCancelable {
/* ----- Headline, containing number of questions and tags selected, check main_page/headline.html ----- */
#questionCount {
font-weight: bold;
- font-size: 23px;
+ font-size: 20px;
color: #7ea9b3;
width: 200px;
float: left;
- margin-bottom: 8px;
+ margin-bottom: 6px;
padding-top: 6px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
#listSearchTags {
float: left;
margin-top: 3px;
color: #707070;
font-size: 16px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
ul#searchTags {
margin-left: 10px;
@@ -904,7 +905,7 @@ ul#searchTags {
margin: 5px 0 10px 0;
padding: 0px;
float: left;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.search-tips a {
text-decoration: underline;
@@ -918,28 +919,26 @@ ul#searchTags {
padding: 0;
width: 100%;
}
-.main-page #question-list {
- margin-top: 10px;
-}
.short-summary {
position: relative;
filter: inherit;
- padding: 10px;
+ padding: 10px 0 3px 0;
border-bottom: 1px solid #DDDBCE;
margin-bottom: 1px;
overflow: hidden;
- width: 710px;
+ width: 733px;
float: left;
- background: url(../images/summary-background.png) repeat-x;
+ /*background: url(../images/summary-background.png) repeat-x;*/
+
}
.short-summary h2 {
- font-size: 24px;
+ font-size: 20px;
font-weight: normal;
line-height: 26px;
padding-left: 0;
- margin-bottom: 6px;
+ margin-bottom: 7px;
display: block;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.short-summary a {
color: #464646;
@@ -964,12 +963,12 @@ ul#searchTags {
.short-summary .counts {
float: right;
margin: 4px 0 0 5px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.short-summary .counts .item-count {
padding: 0px 5px 0px 5px;
font-size: 25px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.short-summary .counts .votes div,
.short-summary .counts .views div,
@@ -981,7 +980,7 @@ ul#searchTags {
color: #646464;
}
.short-summary .tags {
- margin-top: 0;
+ margin: 0 0 0 1px;
}
.short-summary .votes,
.short-summary .answers,
@@ -1179,6 +1178,10 @@ ul.tags.marked-tags li,
ul#ab-user-tags li {
width: 160px;
margin: 5px;
+ margin-left: 0;
+}
+.tags-page ul.tags {
+ margin-left: 5px;
}
ul#related-tags li {
margin: 0 5px 8px 0;
@@ -1284,7 +1287,7 @@ ul#related-tags li {
/* ----- Ask and Edit Question Form template----- */
.section-title {
color: #7ea9b3;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
font-weight: bold;
font-size: 24px;
}
@@ -1355,7 +1358,9 @@ ul#related-tags li {
font-size: 13px;
}
.ask-page #id_tags,
-.edit-question-page #id_tags {
+.edit-question-page #id_tags,
+#id_user,
+#id_user_author {
border: #cce6ec 3px solid;
height: 25px;
padding-left: 5px;
@@ -1373,14 +1378,13 @@ ul#related-tags li {
float: left;
font-weight: normal;
margin-top: 3px;
- width: 160px;
height: 34px;
font-size: 21px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -1560,11 +1564,11 @@ ul#related-tags li {
/* ----- Question template ----- */
.question-page h1 {
padding-top: 0px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.question-page h1 a {
color: #464646;
- font-size: 30px;
+ font-size: 26px;
font-weight: normal;
line-height: 1;
}
@@ -1573,12 +1577,12 @@ ul#related-tags li {
clear: both;
padding: 3px 0 0 23px;
font-size: 15px;
- width: 110px;
+ width: 130px;
background-position: center left;
margin-left: 0px !important;
}
.question-page p.rss a {
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
vertical-align: top;
}
.question-page .question-content {
@@ -1592,7 +1596,7 @@ ul#related-tags li {
}
.question-page #question-table,
.question-page .answer-table {
- margin: 6px 0 6px 0;
+ margin: 8px 0 6px 0;
border-spacing: 0px;
width: 670px;
padding-right: 10px;
@@ -1743,7 +1747,7 @@ ul#related-tags li {
}
.question-page #questionCount {
float: left;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
line-height: 15px;
}
.question-page .question-img-upvote,
@@ -1792,7 +1796,7 @@ ul#related-tags li {
color: #7ea9b3;
width: 200px;
float: left;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.question-page .comments {
font-size: 12px;
@@ -1855,14 +1859,13 @@ ul#related-tags li {
.question-page .comments button {
line-height: 25px;
margin-bottom: 5px;
- width: 100px;
height: 27px;
font-size: 12px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -2035,8 +2038,8 @@ ul#related-tags li {
float: left;
text-align: center;
padding-top: 2px;
- margin: 10px 10px 0px 3px;
- /* smalls IE fixes */
+ margin: 0px 10px 0px 3px;
+ /* small IE fixes */
*margin: 0;
*height: 210px;
@@ -2046,15 +2049,16 @@ ul#related-tags li {
cursor: pointer;
}
.question-page .vote-number {
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
- padding: 0px 0 5px 0;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
+ padding: 2px 0 5px 0;
font-size: 25px;
font-weight: bold;
color: #777;
}
.question-page .vote-buttons .notify-sidebar {
text-align: left;
- width: 120px;
+ width: 130px;
+ margin-top: 7px;
}
.question-page .vote-buttons .notify-sidebar label {
vertical-align: top;
@@ -2124,7 +2128,7 @@ ul#related-tags li {
margin-top: 10px;
}
.question-page #fmanswer h2 {
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
color: #7ea9b3;
font-size: 24px;
}
@@ -2245,14 +2249,13 @@ ul#related-tags li {
.user-profile-page input.submit {
font-weight: normal;
margin: 5px 0px;
- width: 100px;
height: 26px;
font-size: 15px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -2341,14 +2344,13 @@ ul#related-tags li {
#local_login_buttons .submit-b,
#password-fs .submit-b,
#openid-fs .submit-b {
- width: 100px;
height: 24px;
font-size: 15px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -2492,7 +2494,7 @@ a:hover.medal {
}
.user-profile-page h2 {
padding: 10px 0px 10px 0px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
.user-details {
font-size: 13px;
@@ -2517,14 +2519,13 @@ a:hover.medal {
font-weight: bold;
line-height: 26px;
margin-top: -2px;
- width: 100px;
height: 26px;
font-size: 14px;
text-align: center;
text-decoration: none;
cursor: pointer;
color: #4a757f;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
text-shadow: 0px 1px 0px #c6d9dd;
-moz-text-shadow: 0px 1px 0px #c6d9dd;
-webkit-text-shadow: 0px 1px 0px #c6d9dd;
@@ -2579,13 +2580,13 @@ a:hover.medal {
display: none;
}
.count {
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
font-size: 200%;
font-weight: 700;
color: #777777;
}
.scoreNumber {
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
font-size: 35px;
font-weight: 800;
color: #777;
@@ -2696,7 +2697,7 @@ a:hover.medal {
color: #525252;
}
.revision h3 {
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
font-size: 21px;
padding-left: 0px;
}
@@ -2803,7 +2804,7 @@ ins {
padding: 6px 0 0 0;
background: #16160f;
font-size: 16px;
- font-family: 'Yanone Kaffeesatz', Arial, sans-serif;
+ font-family: 'Open Sans Condensed', Arial, sans-serif;
}
#ground p {
margin-bottom: 0;
@@ -3375,6 +3376,9 @@ p.signup_p {
padding: 0;
margin-top: -3px;
}
+.user-profile-page ul.tags {
+ margin-left: 5px;
+}
.userList {
font-size: 13px;
}
@@ -3518,7 +3522,6 @@ body.anon.lang-es #searchBar .searchInputCancelable {
margin: 0 5px 3px 0;
}
.group-wiki .follow-toggle.group-join-btn {
- width: 150px;
margin: 4px auto 10px auto;
display: block;
}
diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less
index 3ee5109b..2e43d7be 100644
--- a/askbot/skins/default/media/style/style.less
+++ b/askbot/skins/default/media/style/style.less
@@ -269,12 +269,13 @@ body.user-messages {
#metaNav {/* Top Navigation bar containing links for tags, people and badges, check widgets/header.html */
float: right;/* for #header.with-logo it is modified */
+ margin-right: 7px;
a {
color: #e2e2ae;
padding: 0px 0px 0px 35px;
height: 25px;
- line-height: 30px;
+ line-height: 25px;
margin:5px 0px 0px 10px;
font-size: 18px;
font-weight: 100;
@@ -367,11 +368,11 @@ body.user-messages {
}
.scope-selector{
- font-size:21px;
- color:#5a5a4b;
+ font-size:20px;
+ color:#7a7a6b;
height:55px;
line-height:55px;
- margin-left:24px
+ margin-left:16px
}
.on{
background:url(../images/scopearrow.png) no-repeat center bottom;
@@ -384,32 +385,33 @@ body.user-messages {
}
#searchBar { /* Main search form , check widgets/search_bar.html */
- display:inline-block;
+ display: inline-block;
background-color: #fff;
- width:412px;
+ width: 400px;
border: 1px solid #c9c9b5;
float:right;
height:42px;
margin:6px 0px 0px 15px;
.searchInput, .searchInputCancelable{
- font-size: 30px;
- height: 40px;
+ font-size: 26px;
+ height: 39px;
font-weight:300;
background:#FFF;
border:0px;
color:#484848;
padding-left:10px;
+ padding-top: 1px;
font-family:@body-font;
- vertical-align: middle;
+ vertical-align: top;
}
.searchInput,{
- width: 352px;
+ width: 340px;
}
.searchInputCancelable {
- width: 317px;
+ width: 305px;
}
.logoutsearch {
@@ -471,7 +473,8 @@ body.anon {
margin-top:6px;
float:right;
text-transform:uppercase;
- .button-style(200px, 42px, 23px);
+ .button-style(42px, 20px);
+ width: 200px;/* to match width of sidebar */
}
#askButton:hover{
@@ -545,12 +548,10 @@ body.anon {
label {
color: @info-text;
font-size:15px;
- display: block;
- float: right;
+ vertical-align: bottom;
+ display: inline;
text-align:left;
font-family:@main-font;
- width:80px;
- margin-right:18px;
}
#displayTagFilterControl label,
@@ -586,10 +587,11 @@ body.anon {
#ignoredTagInput,
#subscribedTagInput,
#ab-tag-search {
- width:156px;
+ width:152px;
padding-left:5px;
border:#c9c9b5 1px solid;
height:25px;
+ font-size: 14px;
}
#ab-tag-search {
width: 138px;
@@ -601,7 +603,7 @@ body.anon {
border:0;
font-weight:bold;
margin-top:-2px;
- .button-style(30px, 27px, 14px);
+ .button-style(27px, 14px);
.rounded-corners(4px);
}
#interestingTagAdd:hover,
@@ -626,8 +628,9 @@ body.anon {
font-weight:normal;
margin-top:3px;
display:block;
- .button-style(120px,34px,21px);
+ .button-style(34px,21px);
.center;
+ width: 130px;
}
a.followed:hover, a.follow:hover{
@@ -658,7 +661,7 @@ body.anon {
/* notify by email box */
.notify-sidebar #question-subscribe-sidebar {
- margin: 7px 0 0 3px;
+ margin: 0 0 0 3px;
}
}
@@ -672,7 +675,7 @@ body.anon {
font-size:16px;
border-bottom:#cccccc 1px solid;
font-size:13px;
-
+
strong{
float:right;
padding-right:10px;
@@ -680,16 +683,23 @@ body.anon {
}
.questions-related {
word-wrap: break-word;
-
+
p {
line-height: 20px;
- padding: 4px 0px 4px 0px;
+ padding: 4px 0px 9px 0px;
font-size: 16px;
font-weight:normal;
border-bottom:#cccccc 1px solid;
}
- a{
+ p:first-child {
+ margin-top: -4px;
+ }
+ p:last-child {
+ border: none;
+ }
+ a {
font-size:13px;
+ line-height: 1.3;
}
}
/* tips and markdown help are widgets for ask template */
@@ -812,12 +822,12 @@ body.anon {
#questionCount{
font-weight:bold;
- font-size:23px;
+ font-size:20px;
color:@section-title;
width:200px;
float:left;
- margin-bottom:8px;
- padding-top:6px;
+ margin-bottom:6px;
+ padding-top: 6px;
font-family:@main-font;
}
@@ -860,27 +870,23 @@ ul#searchTags {
width: 100%;
}
-.main-page #question-list {
- margin-top: 10px;
-}
-
.short-summary {
position: relative;
filter: inherit;
- padding: 10px;
+ padding: 10px 0 3px 0;
border-bottom: 1px solid #DDDBCE;
margin-bottom:1px;
overflow: hidden;
- width: 710px;
+ width: 733px;
float: left;
- background: url(../images/summary-background.png) repeat-x;
-
+ /*background: url(../images/summary-background.png) repeat-x;*/
+
h2 {
- font-size: 24px;
+ font-size: 20px;
font-weight:normal;
line-height: 26px;
padding-left: 0;
- margin-bottom:6px;
+ margin-bottom:7px;
display:block;
font-family:@main-font;
}
@@ -934,7 +940,7 @@ ul#searchTags {
}
.tags {
- margin-top: 0;
+ margin: 0 0 0 1px;
}
.votes, .answers, .favorites, .views {
@@ -1398,15 +1404,77 @@ ul#related-tags li {
font-size:13px;
}
- #id_tags{
+ #id_tags {
border:#cce6ec 3px solid;
height:25px;
padding-left:5px;
+ font-size:14px;
width:395px;
+ }
+}
+
+.ask-page,
+.question-page,
+.edit-question-page,
+.edit-answer-page {
+ #id_post_author_username,
+ #id_post_author_email {
+ border:#cce6ec 3px solid;
+ height:25px;
+ padding-left:5px;
font-size:14px;
+ width:186px;
+ }
+ #id_post_author_email {
+ margin-left: 10px;
+ }
+ table.proxy-user-info {
+ border-spacing: 0px;
+
+ .form-item {
+ float: left;
+ }
}
}
+.groups-input,
+.users-input {
+ width:152px;
+ padding-left:5px;
+ border:#c9c9b5 1px solid;
+ height:25px;
+ font-size: 14px;
+}
+
+.add-groups,
+.add-users {
+ border:0;
+ font-weight:bold;
+ margin-top:-2px;
+ .button-style(27px, 14px);
+ .rounded-corners(4px);
+}
+
+.add-everyone-group {
+ text-align: center;
+ margin: auto;
+ display: block;
+ padding: 0 10px;
+}
+
+.add-groups:hover {
+ .button-style-hover;
+}
+
+#id_user,
+#id_user_author {
+ border:#cce6ec 3px solid;
+ height:25px;
+ padding-left:5px;
+ width:395px;
+ font-size:14px;
+}
+
.title-desc {
color: @info-text;
font-size: 13px;
@@ -1427,7 +1495,7 @@ ul#related-tags li {
float: left;
font-weight:normal;
margin-top:3px;
- .button-style(160px,34px,21px);
+ .button-style(34px,21px);
margin-right:7px;
}
@@ -1593,14 +1661,14 @@ ul#related-tags li {
h1{
padding-top:0px;
- font-family:@main-font;
- }
-
- h1 a{
- color:@question-link;
- font-size:30px;
- font-weight:normal;
- line-height:1;
+ font-family:@main-font;
+
+ a {
+ color:@question-link;
+ font-size:26px;
+ font-weight:normal;
+ line-height:1;
+ }
}
p.rss {
@@ -1608,7 +1676,7 @@ ul#related-tags li {
clear:both;
padding: 3px 0 0 23px;
font-size: 15px;
- width:110px;
+ width:130px;
background-position:center left;
margin-left:0px !important;
}
@@ -1617,7 +1685,7 @@ ul#related-tags li {
font-family:@main-font;
vertical-align: top;
}
-
+
.question-content{
float:right;
width:682px;
@@ -1637,7 +1705,7 @@ ul#related-tags li {
#question-table,
.answer-table {
- margin: 6px 0 6px 0;
+ margin: 8px 0 6px 0;
border-spacing: 0px;
width: 670px;
padding-right:10px;
@@ -1781,7 +1849,7 @@ ul#related-tags li {
.tabBar{
width:100%;
}
-
+
#questionCount{
float:left;
font-family:@main-font;
@@ -1816,9 +1884,6 @@ ul#related-tags li {
#fmanswer_button{
margin:8px 0px;
}
- #fmanswer_button.answer-own-question {
- width: 150px;
- }
.question-img-favorite:hover {
background: url(../images/vote-favorite-on.png)
}
@@ -1901,7 +1966,7 @@ ul#related-tags li {
button{
line-height:25px;
margin-bottom:5px;
- .button-style(100px, 27px, 12px);
+ .button-style(27px, 12px);
font-family:@body-font;
font-weight:bold;
}
@@ -1955,9 +2020,9 @@ ul#related-tags li {
margin: -3px 0px 0px -2px;
}
.content {
- margin-bottom: 7px;
+ margin-bottom: 7px;
}
-
+
.comment-votes {
float: left;
width: 37px;
@@ -2063,7 +2128,7 @@ ul#related-tags li {
.vote-number {
font-family: @main-font;
- padding: 0px 0 5px 0;
+ padding: 2px 0 5px 0;
font-size: 25px;
font-weight: bold;
color: #777;
@@ -2071,10 +2136,11 @@ ul#related-tags li {
.vote-buttons .notify-sidebar {
text-align: left;
- width:120px;
- }
- .vote-buttons .notify-sidebar label {
- vertical-align: top;
+ width:130px;
+ margin-top: 7px;
+ label {
+ vertical-align: top;
+ }
}
.tabBar-answer{
@@ -2091,7 +2157,7 @@ ul#related-tags li {
.accepted-answer {
background-color: #f7fecc;
border-bottom-color: #9BD59B;
-
+
.vote-buttons {
width:27px;
margin-right:10px;
@@ -2253,7 +2319,7 @@ ul#related-tags li {
input.submit{
font-weight:normal;
margin:5px 0px;
- .button-style(100px,26px,15px);
+ .button-style(26px,15px);
font-family:@body-font;
}
input.submit:hover{
@@ -2266,6 +2332,27 @@ ul#related-tags li {
.cancel:hover{
background:url(../images/small-button-cancel.png) repeat-x bottom !important;
}
+ .re {
+ float: left;
+ width: 960px;
+ }
+}
+
+.inbox-flags.user-profile-page {
+ .re {
+ width: 810px;
+ }
+ .post-moderation-controls {
+ float: left;
+ width: 150px;
+ margin-top: 23px;
+ text-align: right;
+ }
+ .dropdown:hover {
+ ul.dropdown-menu {
+ display: block;
+ }
+ }
}
.openid-signin form {
@@ -2284,7 +2371,7 @@ ul#related-tags li {
width:200px;
}
.submit-b{
- .button-style(100px,24px,15px);
+ .button-style(24px,15px);
font-family:@body-font;
font-weight:bold;
padding-right:10px;
@@ -2442,7 +2529,7 @@ a:hover.medal {
font-weight:bold;
line-height:26px;
margin-top:-2px;
- .button-style(100px,26px,14px);
+ .button-style(26px,14px);
}
.follow-toggle:hover, .submit:hover {
@@ -2688,7 +2775,7 @@ ins .post-tag, ins p, ins {
width: 100%;
clear: both;
border-top: 1px solid #000;
- padding: 6px 0 0 0;
+ padding: 16px 0 0 0;
background: @header-color;
font-size:16px;
font-family:@main-font;
@@ -2701,7 +2788,7 @@ ins .post-tag, ins p, ins {
.footer-links {
color: #EEE;
text-align:left;
- width:500px;
+ width:450px;
float:left;
a {
color: #e7e8a8;
@@ -2709,7 +2796,7 @@ ins .post-tag, ins p, ins {
}
.powered-link{
- width:500px;
+ width:450px;
float:left;
text-align:left;
a{
@@ -2719,7 +2806,7 @@ ins .post-tag, ins p, ins {
.copyright{
color:#616161;
- width:450px;
+ width:500px;
float:right;
text-align:right;
@@ -3522,7 +3609,6 @@ body.anon.lang-es {
margin: 0 5px 3px 0;
}
.follow-toggle.group-join-btn {
- width: 150px;
margin: 4px auto 10px auto;
display: block;
}
@@ -3637,6 +3723,16 @@ textarea.tipped-input {
}
}
+.editor-status {
+ float: right;
+ margin: 7px 350px 0 0;
+ font-weight: bold;
+
+ span {
+ display: none;
+ }
+}
+
/* tag editor */
.tag-editor {
height: 64px;
diff --git a/askbot/skins/default/templates/ask.html b/askbot/skins/default/templates/ask.html
index e9e53338..5f072577 100644
--- a/askbot/skins/default/templates/ask.html
+++ b/askbot/skins/default/templates/ask.html
@@ -37,6 +37,7 @@
{% endif %}
<script type='text/javascript'>
askbot['urls']['api_get_questions'] = '{% url api_get_questions %}';
+ askbot['urls']['saveDraftQuestion'] = '{% url save_draft_question %}';
{% if settings.ENABLE_MATHJAX or settings.MARKUP_CODE_FRIENDLY %}
var codeFriendlyMarkdown = true;
{% else %}
@@ -67,6 +68,17 @@
setupFormValidation($("#fmask"), CPValidator.getQuestionFormRules(), CPValidator.getQuestionFormMessages());
lanai.highlightSyntax();
+
+ if (askbot['data']['userIsAuthenticated']) {
+ var draftHandler = new DraftQuestion();
+ draftHandler.decorate($(document));
+ window.onbeforeunload = function() {
+ var saveHandler = draftHandler.getSaveHandler();
+ saveHandler(true);
+ //var msg = gettext("%s, we've saved your draft, but...");
+ //return interpolate(msg, [askbot['data']['userName']]);
+ };
+ }
});
</script>
{% endblock %}
diff --git a/askbot/skins/default/templates/ask_by_widget.html b/askbot/skins/default/templates/ask_by_widget.html
new file mode 100644
index 00000000..f700f83a
--- /dev/null
+++ b/askbot/skins/default/templates/ask_by_widget.html
@@ -0,0 +1,16 @@
+{% extends "widget_base.html" %}
+{% block forestyle %}
+{%endblock%}
+
+{%block body%}
+Enter your question
+<form action="." method="POST" accept-charset="utf-8">
+ {% csrf_token %}
+ {{form.title}}
+ {% if form.ask_anonymously %}
+ {{form.ask_anonymously}}
+ {%endif%}
+ <input type="submit" value="Ask your question" />
+</form>
+{{form.errors}}
+{%endblock%}
diff --git a/askbot/skins/default/templates/ask_widget_complete.html b/askbot/skins/default/templates/ask_widget_complete.html
new file mode 100644
index 00000000..82fe570c
--- /dev/null
+++ b/askbot/skins/default/templates/ask_widget_complete.html
@@ -0,0 +1,8 @@
+{% extends "widget_base.html" %}
+{% block forestyle %}
+{%endblock%}
+
+{%block body%}
+<a href="{{question_url}}" >Question posted</a>
+{%endblock%}
+
diff --git a/askbot/skins/default/templates/authopenid/widget_signin.html b/askbot/skins/default/templates/authopenid/widget_signin.html
new file mode 100644
index 00000000..c3dbcfde
--- /dev/null
+++ b/askbot/skins/default/templates/authopenid/widget_signin.html
@@ -0,0 +1,231 @@
+{% extends "widget_base.html" %}
+{% import "authopenid/authopenid_macros.html" as login_macros %}
+{% from "macros.html" import timeago %}
+<!-- signin.html -->
+{% block title %}{% spaceless %}{% trans %}User login{% endtrans %}{% endspaceless %}{% endblock %}
+{% block forestyle %}
+ <link rel="stylesheet" type="text/css" media="screen" href="{{"/jquery-openid/openid.css"|media}}"/>
+{% endblock %}
+{% block forejs %}
+ {% include "meta/bottom_scripts.html" %}
+ {% include "meta/html_head_javascript.html" %}
+{% endblock %}
+{% block content %}
+{% if have_buttons or view_subtype == 'email_sent' %}
+ <h1 class="section-title">{{page_title}}</h1>
+{% endif %}
+ {% if answer %}
+ <div class="message">
+ {% trans title=answer.question.title|escape, summary=answer.summary|escape %}
+ Your answer to {{title}} {{summary}} will be posted once you log in
+ {% endtrans %}
+ </div>
+ {% endif %}
+ {% if question %}
+ <div class="message">
+ {% trans title=question.title|escape, summary=question.summary|escape %}Your question
+ {{title}} {{summary}} will be posted once you log in
+ {% endtrans %}
+ </div>
+ {% endif %}
+ <p id='login-intro'>
+ {% if view_subtype == 'default' and have_buttons %}
+ {% trans %}Choose your favorite service below to sign in using secure OpenID or similar technology. Your external service password always stays confidential and you don't have to rememeber or create another one.{% endtrans %}
+ {% elif view_subtype == 'add_openid' and have_buttons %}
+ {% if existing_login_methods %}
+ {% trans %}It's a good idea to make sure that your existing login methods still work, or add a new one. Please click any of the icons below to check/change or add new login methods.{% endtrans %}
+ {% else %}
+ {% trans %}Please add a more permanent login method by clicking one of the icons below, to avoid logging in via email each time.{% endtrans %}
+ {% endif %}
+ {% elif view_subtype == 'change_openid' and have_buttons %}
+ {% if existing_login_methods %}
+ {% trans %}Click on one of the icons below to add a new login method or re-validate an existing one.{% endtrans %}
+ {% else %}
+ {% trans %}You don't have a method to log in right now, please add one or more by clicking any of the icons below.{% endtrans %}
+ {% endif %}
+ {% elif view_subtype == 'email_sent' %}
+ {% trans %}Please check your email and visit the enclosed link to re-connect to your account{% endtrans %}
+ {% endif %}
+ </p>
+ {% if openid_error_message %}
+ <p class="warning">{{ openid_error_message }}</p>
+ {% endif %}
+ {% if view_subtype != 'email_sent' and view_subtype != 'bad_key' %}
+ <form id="signin-form" method="post" action="{% url widget_signin %}">{% csrf_token %}
+ {# in this branch - the real signin view we display the login icons
+ here we hide the local login button only if admin
+ wants to always show the password login form - then
+ the button is useless.
+ #}
+ {{ login_form.login_provider_name }}
+ {{ login_form.next }}
+ {{
+ login_macros.provider_buttons(
+ login_form = login_form,
+ major_login_providers = major_login_providers,
+ minor_login_providers = minor_login_providers,
+ hide_local_login = settings.SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN,
+ settings = settings,
+ logged_in = user.is_authenticated(),
+ show_buttons = have_buttons
+ )
+ }}
+ {% if use_password_login == True %}
+ <fieldset
+ id="password-fs"
+ {% if user.is_anonymous() %}
+ {% if not login_form.username.errors and not login_form.password_login_failed %}
+ {% if not settings.SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN %}
+ style="display:none;"
+ {%endif%}
+ {% endif %}
+ {% else %}
+ {% if not login_form.new_password.errors and not login_form.new_password_retyped.errors %}
+ {% if not settings.SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN %}
+ style="display:none;"
+ {% endif%}
+ {% endif %}
+ {% endif %}
+ >
+ {{login_form.password_action}}
+ {% if user.is_anonymous() %}
+ {% if have_buttons %}
+ <h2 id="password-heading">
+ {% trans %}or enter your <span>user name and password</span>, then sign in{% endtrans %}
+ </h2>
+ {% else %}
+ <h1 class="section-title">
+ {% trans %}Please, sign in{% endtrans %}
+ </h1>
+ {% endif %}
+ {% if have_buttons %}
+ <p class="hint">{% trans %}(or select another login method above){% endtrans %}</p>
+ {% endif %}
+ {% if login_form.password_login_failed %}
+ <p class="error">{% trans %}Login failed, please try again{% endtrans %}</p>
+ {% endif %}
+ <table class="login">
+ <tr>
+ <td><label for="id_username">{% trans %}Login or email{% endtrans %}</label></td>
+ <td>{{login_form.username}}</td>
+ </tr>
+ <tr>
+ <td><label for="id_password">{% trans %}Password{% endtrans %}</label></td>
+ <td>{{login_form.password}}</td>
+ </tr>
+ </table>
+ <p id="local_login_buttons">
+ <input class="submit-b" name="login_with_password" type="submit" value="{% trans %}Sign in{% endtrans %}" />
+ {% if settings.USE_LDAP_FOR_PASSWORD_LOGIN == False %}
+ <a class="create-password-account" style="vertical-align:middle" href="{% url user_signup_with_password %}?login_provider=local">{% trans %}Create a password-protected account{% endtrans %}</a>
+ {% endif %}
+ </p>
+ {% elif settings.USE_LDAP_FOR_PASSWORD_LOGIN == False %}
+ <h2 id="password-heading">
+ {% trans %}To change your password - please enter the new one twice, then submit{% endtrans %}
+ </h2>
+ <table class="login">
+ <tr>
+ <td><label for="id_new_password">{% trans %}New password{% endtrans %}</label></td>
+ <td>
+ {{login_form.new_password}}
+ </td>
+ <td>
+ <span class="error">{{login_form.new_password.errors[0]}}</span>
+ </td>
+ </tr>
+ <tr>
+ <td><label for="id_new_password_retyped">{% trans %}Please, retype{% endtrans %}</label></td>
+ <td>
+ {{login_form.new_password_retyped}}
+ </td>
+ <td>
+ <span class="error">{{login_form.new_password_retyped.errors[0]}}</span>
+ </td>
+ </tr>
+ </table>
+ <p id="local_login_buttons">
+ <input class="submit-b" name="change_password" type="submit" value="{% trans %}Change password{% endtrans %}" />
+ </p>
+ {% endif %}
+ </fieldset>
+ {% endif %}
+ </form>
+ {% if user.is_authenticated() and existing_login_methods and settings.ALLOW_ADD_REMOVE_LOGIN_METHODS %}
+ <div
+ id='existing-login-methods'
+ {% if login_form.password_change_failed %}
+ style="display:none";
+ {% endif %}
+ >
+ <h2 id='ab-show-login-methods'>
+ {% trans %}Here are your current login methods{% endtrans %}
+ </h2>
+ <table id='ab-existing-login-methods'>
+ <tr>
+ <th>{% trans %}provider{% endtrans %}</th>
+ <th>{% trans %}last used{% endtrans %}</th>
+ <th>{% trans %}delete, if you like{% endtrans %}</th>
+ </tr>
+ {% for login_method in existing_login_methods %}
+ <tr class="ab-provider-row">
+ <td class="ab-provider-name">
+ {{login_method.provider_name}}
+ </td>
+ <td>
+ {% if login_method.last_used_timestamp %}
+ {{ timeago(login_method.last_used_timestamp) }}
+ {% endif %}
+ </td>
+ <td>
+ {% if login_method.is_deletable %}
+ <button>{% trans %}delete{% endtrans %}</button>
+ {% else %}
+ {% trans %}cannot be deleted{% endtrans %}
+ {% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ {% endif %}
+ {% endif %}
+ {% if view_subtype != 'email_sent' or view_subtype == 'bad_key' %}
+ {% if user.is_anonymous() and settings.ALLOW_ACCOUNT_RECOVERY_BY_EMAIL %}
+ <form id="account-recovery-form" action="{% url user_account_recover %}" method="post">{% csrf_token %}
+ {% if view_subtype != 'bad_key' %}
+ <h2 id='account-recovery-heading'>{% trans %}Still have trouble signing in?{% endtrans %}</h2>
+ {% endif %}
+ <p class="hint">
+ <span class="text">
+ {% if view_subtype == 'bad_key' %}
+ {% trans %}Please, enter your email address below and obtain a new key{% endtrans %}
+ {% else %}
+ {% trans %}Please, enter your email address below to recover your account{% endtrans %}
+ {% endif %}
+ </span>
+ <span style="display:none" class="link"> - <a href="#">{% trans %}recover your account via email{% endtrans %}</a></span>
+ </p>
+ <fieldset id='email-input-fs'>
+ {% if account_recovery_form.email.errors %}
+ <p class="error">{{account_recovery_form.email.errors[0]}}</p>
+ {% endif %}
+ {{ account_recovery_form.email }}
+ <input
+ class="submit-b"
+ type="submit"
+ {% if view_subtype == 'bad_key' %}
+ value="{% trans %}Send a new recovery key{% endtrans %}"
+ {% else %}
+ value="{% trans %}Recover your account via email{% endtrans %}"
+ {% endif %}
+ />
+ </fieldset>
+ </form>
+ {% endif %}
+ {% endif %}
+{% endblock %}
+{% block endjs %}
+{% include "authopenid/providers_javascript.html" %}
+{% endblock %}
+<!-- end signin.html -->
diff --git a/askbot/skins/default/templates/django_error.html b/askbot/skins/default/templates/django_error.html
new file mode 100644
index 00000000..c1bfcc20
--- /dev/null
+++ b/askbot/skins/default/templates/django_error.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <title>Internal Server Error</title>
+ </head>
+ <body>
+ <h1>Internal Server Error</h1>
+ <p>
+ Most likely this is caused by an import error
+ within Django due to an incomplete setup of your
+ django project.
+ </p>
+ <p>
+ Please look into your error logs for more details.
+ </p>
+ <p>
+ Have you installed the database binding module?
+ </p>
+ <p>
+ If you made your own customizations - have you forgotten to
+ install some dependency module? Please note
+ that dependency modules may have their own dependencies, etc,
+ and they should also be satisfied.
+ </p>
+ <p>
+ If you need further assistance, please email at
+ <a href="mailto:support@askbot.com">support@askbot.com</a>,
+ post your question at <a href="http://askbot.org/en/questions/">AskBot Support Forum</a>
+ or call at +1-301-747-1533 (US).
+ <p>
+ </body>
+</html>
diff --git a/askbot/skins/default/templates/email/ask_for_signature.html b/askbot/skins/default/templates/email/ask_for_signature.html
index cafeee2b..93e2fa4b 100644
--- a/askbot/skins/default/templates/email/ask_for_signature.html
+++ b/askbot/skins/default/templates/email/ask_for_signature.html
@@ -4,6 +4,7 @@
</p>
<p>
{% trans %}Your post could not be published, because we could not detect signature in your email.{% endtrans %}<br/>
+ {% trans %}This happened either because this is your first post or you have changed your email signature.{% endtrans %}<br/>
{% trans %}Please make a simple response, without editing this message.{% endtrans %}<br/>
{% trans %}We will then attempt to detect the signature in your response and you should be able to post.{% endtrans %}
</p>
diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html
index 499acf41..c03980aa 100644
--- a/askbot/skins/default/templates/macros.html
+++ b/askbot/skins/default/templates/macros.html
@@ -6,23 +6,6 @@
>{% if icon == False %}{% if site_label %}{{ site_label }}{% else %}{{ site }}{% endif %}{% endif %}</a>
{%- endmacro -%}
-{%- macro follow_toggle(follow, name, alias, id) -%}
- {# follow - boolean; name - object type name; alias - e.g. users name; id - object id #}
- <div
- class="follow-toggle follow-user-toggle"
- id="follow-{{ name|escape }}-{{ id }}"
- >
- {% if follow %}
- <div class="follow">{% trans %}follow {{alias}}{% endtrans %}</div>
- {% else %}
- <div class="unfollow">
- <div class="unfollow-red">{% trans %}unfollow {{alias}}{% endtrans %}</div>
- <div class="unfollow-green">{% trans %}following {{alias}}{% endtrans %}</div>
- </div>
- {% endif %}
- </div>
-{%- endmacro -%}
-
{%- macro inbox_post_snippet(response, inbox_section) -%}
<div id="re_{{response.id}}" class="re{% if response.is_new %} new highlight{% else %} seen{% endif %}">
<input type="checkbox" />
@@ -40,7 +23,7 @@
{% if inbox_section == 'flags' %}
<a class="re_expand" href="{{ response.response_url }}">
<!--div class="re_snippet">{{ response.response_snippet|escape }}</div-->
- <div class="re_content">{{ response.response_content|escape }}</div>
+ <div class="re_content">{{ response.response_content }}</div>
</a>
{% endif %}
</div>
@@ -249,7 +232,7 @@ poor design of the data or methods on data objects #}
{%- endmacro -%}
{%- macro group_join_button(group_id = None, can_join = False, is_member = False) -%}
- {% if can_join or is_member %}
+ {% if can_join %}
<button
class="group-join-btn follow-toggle {% if is_member %}on on-state{% endif %}"
data-group-id="{{group_id}}"
@@ -500,7 +483,8 @@ for the purposes of the AJAX comment editor #}
edit_title = False,
use_category_selector = False,
tag_names = None,
- editor_type = None
+ editor_type = None,
+ user = None
)
-%}
{%include "widgets/edit_post.html" %}
@@ -534,8 +518,8 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_
{%- macro follow_toggle(follow, name, alias, id) -%}
{# follow - boolean; name - object type name; alias - e.g. users name; id - object id #}
<div
- class="follow-toggle"
- id="follow-{{ name }}-{{ id }}"
+ class="follow-toggle follow-user-toggle"
+ id="follow-{{ name|escape }}-{{ id }}"
>
{% if follow %}
<div class="follow">{% trans %}follow {{alias}}{% endtrans %}</div>
diff --git a/askbot/skins/default/templates/meta/bottom_scripts.html b/askbot/skins/default/templates/meta/bottom_scripts.html
index b7113e85..0773aea8 100644
--- a/askbot/skins/default/templates/meta/bottom_scripts.html
+++ b/askbot/skins/default/templates/meta/bottom_scripts.html
@@ -71,6 +71,9 @@
{% else %}
animateHashes();
{% endif %}
+ if (askbot['data']['userIsAdminOrMod']) {
+ $('body').addClass('admin');
+ }
});
{% if user_messages %}
$('#validate_email_alert').click(function(){notify.close(true)})
diff --git a/askbot/skins/default/templates/meta/fonts.html b/askbot/skins/default/templates/meta/fonts.html
index f55d567c..e8e54a8f 100644
--- a/askbot/skins/default/templates/meta/fonts.html
+++ b/askbot/skins/default/templates/meta/fonts.html
@@ -1,20 +1,8 @@
<style type="text/css">
@font-face {
- font-family: 'Yanone Kaffeesatz';
- font-style: normal;
- font-weight: 400;
- src: url('{{"/images/YanoneKaffeesatz-Regular.ttf"|media}}');
-}
-@font-face {
- font-family: 'Yanone Kaffeesatz';
+ font-family: 'Open Sans Condensed';
font-style: normal;
font-weight: 700;
- src: url('{{"/images/YanoneKaffeesatz-Bold.ttf"|media}}');
-}
-@font-face {
- font-family: 'Yanone Kaffeesatz';
- font-style: normal;
- font-weight: 300;
- src: url('{{"/images/YanoneKaffeesatz-Light.ttf"|media}}');
+ src: url('{{"/images/OpenSans-CondBold.ttf"|media}}');
}
</style>
diff --git a/askbot/skins/default/templates/meta/html_head_javascript.html b/askbot/skins/default/templates/meta/html_head_javascript.html
index 6f022a52..ffcd5e53 100644
--- a/askbot/skins/default/templates/meta/html_head_javascript.html
+++ b/askbot/skins/default/templates/meta/html_head_javascript.html
@@ -6,6 +6,7 @@
{% if request.user.is_authenticated() %}
askbot['data']['userIsAuthenticated'] = true;
askbot['data']['userId'] = {{request.user.id}};
+ askbot['data']['userName'] = '{{ request.user.username }}';
askbot['data']['userIsAdminOrMod'] = {% if
request.user.is_administrator()
or request.user.is_moderator()
diff --git a/askbot/skins/default/templates/meta/html_head_stylesheets.html b/askbot/skins/default/templates/meta/html_head_stylesheets.html
index bb90e3f8..0c8a2726 100644
--- a/askbot/skins/default/templates/meta/html_head_stylesheets.html
+++ b/askbot/skins/default/templates/meta/html_head_stylesheets.html
@@ -1,4 +1,5 @@
{% if settings.ASKBOT_CSS_DEVEL == False %}
+
<link href="{{"/style/style.css"|media }}" rel="stylesheet" type="text/css" />
{% else %}
<link href="{{"/style/style.less"|media }}" rel="stylesheet/less" type="text/css" />
@@ -8,7 +9,7 @@
{% if settings.USE_LOCAL_FONTS %}
{% include "meta/fonts.html" %}
{% else %}
- <link href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700' rel='stylesheet' type='text/css'>
+ <link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:400,700&subset=latin,cyrillic-ext,latin-ext' rel='stylesheet' type='text/css'>
{% endif %}
{{ skin.get_extra_css_link() }}
{% if settings.USE_CUSTOM_CSS %}
diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html
index 691ec122..4abde70a 100644
--- a/askbot/skins/default/templates/question.html
+++ b/askbot/skins/default/templates/question.html
@@ -97,7 +97,7 @@
{% if user_can_post_comment %}
can_add = true;
{% else %}
- if (post_id in data['user_posts']){
+ if (data['user_posts'] && post_id in data['user_posts']){
can_add = true;
}
{% endif %}
diff --git a/askbot/skins/default/templates/question/content.html b/askbot/skins/default/templates/question/content.html
index 58b1bcca..66b3014b 100644
--- a/askbot/skins/default/templates/question/content.html
+++ b/askbot/skins/default/templates/question/content.html
@@ -3,11 +3,6 @@
{# ==== BEGIN: question/question_card.html ==== #}
{% include "question/question_card.html" %}
{# ==== END: question/question_card.html ==== #}
-{% if thread.closed %}
- {# ==== START: question/closed_question_info.html ==== #}
- {% include "question/closed_question_info.html" %}
- {# ==== END: question/closed_question_info.html ==== #}
-{% endif %}
{% if answers %}
<div class="clean"></div>
@@ -34,8 +29,21 @@
{% endif %}
{# ==== START: question/new_answer_form.html ==== #}
-{% include "question/new_answer_form.html" %}
-{# ==== END: question/new_answer_form.html ==== #}
-{% if request.user == question.author %}{# this is outside the form on purpose #}
- <input type="button" class="submit after-editor answer-own-question" id="fmanswer_button" value="{% trans %}Answer Your Own Question{% endtrans %}"/>
-{%endif%}
+{# buttons below cannot be cached yet #}
+{% if user_already_gave_answer %}
+ <a
+ class="submit"
+ href="{% url "edit_answer" previous_answer.id %}"
+ >{% trans %}Edit Your Previous Answer{% endtrans %}</a>
+ <span>{% trans %}(only one answer per question is allowed){% endtrans %}</span>
+{% else %}
+ {% include "question/new_answer_form.html" %}
+{% endif %}
+{% if question.closed == False and request.user == question.author %}{# this is outside the form on purpose #}
+<input
+ type="button"
+ class="submit after-editor answer-own-question"
+ id="fmanswer_button"
+ value="{% trans %}Answer Your Own Question{% endtrans %}"
+/>
+{% endif %}
diff --git a/askbot/skins/default/templates/question/javascript.html b/askbot/skins/default/templates/question/javascript.html
index f0d6ed3a..4399c823 100644
--- a/askbot/skins/default/templates/question/javascript.html
+++ b/askbot/skins/default/templates/question/javascript.html
@@ -1,49 +1,44 @@
-{% if not thread.closed %}
- <script type='text/javascript' src='{{"/js/editor.js"|media}}'></script>
- <script type='text/javascript'>
- {% if settings.ENABLE_MATHJAX or settings.MARKUP_CODE_FRIENDLY %}
- var codeFriendlyMarkdown = true;
- {% else %}
- var codeFriendlyMarkdown = false;
- {% endif %}
- var maxCommentLength = {{settings.MAX_COMMENT_LENGTH}};
- askbot['urls']['postComments'] = '{% url post_comments %}';
- askbot['urls']['editComment'] = '{% url edit_comment %}';
- askbot['urls']['deleteComment'] = '{% url delete_comment %}';
- askbot['urls']['getComment'] = '{% url get_comment %}';
- askbot['urls']['question_url_template'] = scriptUrl + '{{ 'question/'|transurl }}{{ "{{QuestionID}}/{{questionSlug}}" }}';{# yes it needs to be that whacky #}
- askbot['urls']['vote_url_template'] = scriptUrl + '{{ 'questions/'|transurl }}{{ "{{QuestionID}}/" }}{{ 'vote/'|transurl }}';
- askbot['urls']['user_signin'] = '{{ settings.LOGIN_URL }}';
- askbot['urls']['swap_question_with_answer'] = '{% url swap_question_with_answer %}';
- askbot['urls']['upvote_comment'] = '{% url upvote_comment %}';
- askbot['urls']['delete_post'] = '{% url delete_post %}';
- askbot['urls']['get_html_template'] = '{% url get_html_template %}';
- askbot['messages']['addComment'] = '{% trans %}post a comment{% endtrans %}';
- {% if settings.SAVE_COMMENT_ON_ENTER %}
- askbot['settings']['saveCommentOnEnter'] = true;
- {% else %}
- askbot['settings']['saveCommentOnEnter'] = false;
- {% endif %}
- askbot['settings']['tagSource'] = '{{ settings.TAG_SOURCE }}';
- </script>
- {% if settings.EDITOR_TYPE == 'markdown' %}
- <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script>
- <script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script>
+<script type='text/javascript' src='{{"/js/editor.js"|media}}'></script>
+<script type='text/javascript'>
+ {% if settings.ENABLE_MATHJAX or settings.MARKUP_CODE_FRIENDLY %}
+ var codeFriendlyMarkdown = true;
{% else %}
- {% include "meta/tinymce.html" %}
+ var codeFriendlyMarkdown = false;
{% endif %}
+ var maxCommentLength = {{settings.MAX_COMMENT_LENGTH}};
+ askbot['urls']['postComments'] = '{% url post_comments %}';
+ askbot['urls']['editComment'] = '{% url edit_comment %}';
+ askbot['urls']['deleteComment'] = '{% url delete_comment %}';
+ askbot['urls']['getComment'] = '{% url get_comment %}';
+ askbot['urls']['saveDraftAnswer'] = '{% url save_draft_answer %}';
+ askbot['urls']['question_url_template'] = scriptUrl + '{{ 'question/'|transurl }}{{ "{{QuestionID}}/{{questionSlug}}" }}';{# yes it needs to be that whacky #}
+ askbot['urls']['vote_url_template'] = scriptUrl + '{{ 'questions/'|transurl }}{{ "{{QuestionID}}/" }}{{ 'vote/'|transurl }}';
+ askbot['urls']['user_signin'] = '{{ settings.LOGIN_URL }}';
+ askbot['urls']['swap_question_with_answer'] = '{% url swap_question_with_answer %}';
+ askbot['urls']['upvote_comment'] = '{% url upvote_comment %}';
+ askbot['urls']['delete_post'] = '{% url delete_post %}';
+ askbot['urls']['get_html_template'] = '{% url get_html_template %}';
+ askbot['urls']['getGroupsList'] = '{% url get_groups_list %}';
+ askbot['messages']['addComment'] = '{% trans %}post a comment{% endtrans %}';
+ {% if settings.SAVE_COMMENT_ON_ENTER %}
+ askbot['settings']['saveCommentOnEnter'] = true;
+ {% else %}
+ askbot['settings']['saveCommentOnEnter'] = false;
+ {% endif %}
+ askbot['settings']['tagSource'] = '{{ settings.TAG_SOURCE }}';
+</script>
+{% if settings.EDITOR_TYPE == 'markdown' %}
+ <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script>
+ <script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script>
+{% else %}
+ {% include "meta/tinymce.html" %}
{% endif %}
<script type='text/javascript' src='{{"/js/jquery.validate.min.js"|media}}'></script>
<script type='text/javascript' src='{{"/js/post.js"|media}}'></script>
<script type="text/javascript">
// define reputation needs for comments
var repNeededForComments = 50;
- $().ready(function(){
- {% if request.user.is_authenticated() %}
- if ($('#question-subscribe-updates')[0].checked){
- $('#question-subscribe-sidebar').attr({'checked': 'checked'});
- }
- {%endif%}
+ $(document).ready(function(){
$("#nav_questions").attr('className',"on");
var answer_sort_tab = "{{ tab_id }}";
$("#" + answer_sort_tab).attr('className',"on");
@@ -65,6 +60,12 @@
$("#fmanswer_button").hide();
});
{%endif%}
+
+ if (askbot['data']['userIsAuthenticated']) {
+ var draftHandler = new DraftAnswer();
+ draftHandler.setThreadId({{ thread.id }});
+ draftHandler.decorate($(document));
+ }
});
$(window).bind('hashchange', animate_hashes);
diff --git a/askbot/skins/default/templates/question/new_answer_form.html b/askbot/skins/default/templates/question/new_answer_form.html
index c590b469..9c0258f7 100644
--- a/askbot/skins/default/templates/question/new_answer_form.html
+++ b/askbot/skins/default/templates/question/new_answer_form.html
@@ -39,7 +39,12 @@
{% endif %}
</p>
{% endif %}
- {{ macros.edit_post(answer, editor_type = settings.EDITOR_TYPE) }}
+ {{ macros.edit_post(
+ answer,
+ user = request.user,
+ editor_type = settings.EDITOR_TYPE
+ )
+ }}
<input id="add-answer-btn" type="submit" class="submit after-editor" style="float:left"/>
<script type="text/javascript">
askbot['functions']['renderAddAnswerButton']();
diff --git a/askbot/skins/default/templates/question/question_card.html b/askbot/skins/default/templates/question/question_card.html
index dd52ea0f..c787bf34 100644
--- a/askbot/skins/default/templates/question/question_card.html
+++ b/askbot/skins/default/templates/question/question_card.html
@@ -25,6 +25,12 @@
}
})();
</script>
+ {% if thread.closed %}
+ <div class="clearfix"></div>
+ {# ==== START: question/closed_question_info.html ==== #}
+ {% include "question/closed_question_info.html" %}
+ {# ==== END: question/closed_question_info.html ==== #}
+ {% endif %}
{% include "question/question_comments.html" %}
</div>
diff --git a/askbot/skins/default/templates/question/sidebar.html b/askbot/skins/default/templates/question/sidebar.html
index 2450a384..ef99e988 100644
--- a/askbot/skins/default/templates/question/sidebar.html
+++ b/askbot/skins/default/templates/question/sidebar.html
@@ -1,4 +1,4 @@
-{% from "macros.html" import timeago %}
+{% import "macros.html" as macros %}
{% if settings.SIDEBAR_QUESTION_HEADER %}
<div class="box">
{{ settings.SIDEBAR_QUESTION_HEADER }}
@@ -27,7 +27,13 @@
</div>
<div class="notify-sidebar">
{%if request.user.is_authenticated() %}
- <input type="checkbox" id="question-subscribe-sidebar"/>
+ <input
+ type="checkbox"
+ id="question-subscribe-sidebar"
+ {% if thread.is_followed_by(request.user) %}
+ checked="checked"
+ {% endif %}
+ />
<label for="question-subscribe-sidebar">{% trans %}email the updates{% endtrans %}</label>
{%else%}
<input type="checkbox" id="question-subscribe-sidebar"/>
@@ -42,12 +48,65 @@
</div>
</div>
+<div class="clearfix"></div>
+<div class="box sharing-widget">
+ {% if thread.is_private() %}
+ <h2>{% trans %}Share{% endtrans %}</h2>
+ <label for="share_user_name">{% trans %}Share with users{% endtrans %}</label>
+ <p>{% trans %}Yourself{% endtrans %}</p>
+ {% for group in thread.groups.all() %}
+ {% if group.name.startswith('_internal_') %}
+ <p>{{ group.created_by.get_profile_link() }}<p>
+ {% endif %}
+ {% endfor %}
+ <form action="{% url share_question_with_user %}" method="post">{% csrf_token %}
+ <input id="share_user_name" type="text" class="groups-input" name="recipient_name" />
+ <input type="hidden" name="thread_id" value="{{ thread.id }}"/>
+ <input type="submit" class="add-groups" value="{% trans %}add{% endtrans %}"/>
+ </form>
+ {#% if thread.groups.count() %}
+ <label for="group_name">{% trans %}Shared with groups:{% endtrans %}</label>
+ {% else %}
+ <label for="group_name">{% trans %}Share with a group{% endtrans %}</label>
+ {% endif %#}
+ <label for="share_group_name">{% trans %}Share with groups{% endtrans %}</label>
+ {% for group in thread.groups.all() %}
+ {% if not group.name.startswith('_internal_') %}{# todo: fix this hack #}
+ <p>{{ macros.user_group(group) }}</p>
+ {% endif %}
+ {% endfor %}
+ <form action="{% url share_question_with_group %}" method="post">{% csrf_token %}
+ <input id="share_group_name" type="text" class="groups-input" name="recipient_name" />
+ <input type="hidden" name="thread_id" value="{{ thread.id }}"/>
+ <input type="submit" class="add-groups" value="{% trans %}add{% endtrans %}"/>
+ </form>
+ <p style="text-align: center">{% trans %}or{% endtrans %}</p>
+ <form action="{% url share_question_with_group %}" method="post">{% csrf_token %}
+ <input
+ type="hidden"
+ name="recipient_name"
+ value="{{ settings.GLOBAL_GROUP_NAME }}"
+ />
+ <input type="hidden" name="thread_id" value="{{ thread.id }}"/>
+ <input
+ type="submit"
+ class="add-groups add-everyone-group"
+ value="{% trans %}share with everyone{% endtrans %}"
+ />
+ </form>
+
+ {% else %}
+ <h2>{% trans %}Public thread{% endtrans %}</h2>
+ <p>{% trans site_name=settings.APP_SHORT_NAME %}This thread is public, all members of {{ site_name }} can read this page.{% endtrans %}</p>
+ {% endif %}
+</div>
+
{% if settings.SIDEBAR_QUESTION_SHOW_META %}
<div class="box statsWidget">
<div class="clearfix"></div>
<h2>{% trans %}Stats{% endtrans %}</h2>
<p>
- {% trans %}Asked{% endtrans %}: <strong>{{ timeago(question.added_at) }}</strong>
+ {% trans %}Asked{% endtrans %}: <strong>{{ macros.timeago(question.added_at) }}</strong>
</p>
<p>
{% trans %}Seen{% endtrans %}: <strong>{{ thread.view_count|intcomma }} {% trans %}times{% endtrans %}</strong>
diff --git a/askbot/skins/default/templates/question_edit.html b/askbot/skins/default/templates/question_edit.html
index a6efe913..f4e4fbbf 100644
--- a/askbot/skins/default/templates/question_edit.html
+++ b/askbot/skins/default/templates/question_edit.html
@@ -26,7 +26,8 @@
mandatory_tags = mandatory_tags,
use_category_selector = use_category_selector,
tag_names = tag_names,
- editor_type = settings.EDITOR_TYPE
+ editor_type = settings.EDITOR_TYPE,
+ user = request.user
)
}}
<div class="after-editor">
diff --git a/askbot/skins/default/templates/reopen.html b/askbot/skins/default/templates/reopen.html
index 52d926ce..4ddd6f31 100644
--- a/askbot/skins/default/templates/reopen.html
+++ b/askbot/skins/default/templates/reopen.html
@@ -9,8 +9,8 @@
<span class="big">{{ question.get_question_title()|escape }}</span>
</a>
</p>
-<p>{% trans %}This question has been closed by
- <a href="{{closed_by_profile_url}}">{{closed_by_username|escape}}</a>
+<p>{% trans username = closed_by_username|escape %}This question has been closed by
+ <a href="{{closed_by_profile_url}}">{{username}}</a>
{% endtrans %}
</p>
<p>
diff --git a/askbot/skins/default/templates/revisions.html b/askbot/skins/default/templates/revisions.html
index aa1996a9..a0531b80 100644
--- a/askbot/skins/default/templates/revisions.html
+++ b/askbot/skins/default/templates/revisions.html
@@ -79,8 +79,7 @@
$("#nav_questions").attr('className',"on");
$('div.revision div[id^=rev-header-]').bind('click', function(){
var revId = this.id.substr(11);
- toggleRev(revId);
-
+ toggleRev(revId);
});
lanai.highlightSyntax();
});
@@ -88,13 +87,13 @@
function toggleRev(id) {
var arrow = $("#rev-arrow-" + id);
var visible = arrow.attr("src").indexOf("hide") > -1;
- var path = mediaUrl(
- "media/images/expander-arrow-" +
- (visible ? "show" : "hide") +
- ".gif" +
- "?v={{settings.MEDIA_RESOURCE_REVISION}}"
- );
- arrow.attr("src", path);
+ if (visible) {
+ var image_path = '{{ "/images/expander-arrow-show.gif"|media }}';
+ } else {
+ var image_path = '{{ "/images/expander-arrow-hide.gif"|media }}';
+ }
+ image_path = image_path + "?v={{settings.MEDIA_RESOURCE_REVISION}}";
+ arrow.attr("src", image_path);
$("#rev-body-" + id).slideToggle("fast");
}
</script>
diff --git a/askbot/skins/default/templates/user_profile/user.html b/askbot/skins/default/templates/user_profile/user.html
index 2f06a3c9..3aee3cfa 100644
--- a/askbot/skins/default/templates/user_profile/user.html
+++ b/askbot/skins/default/templates/user_profile/user.html
@@ -24,7 +24,7 @@
askbot['data']['viewUserName'] = '{{ view_user.username|escape }}';
askbot['data']['viewUserId'] = {{view_user.id}};
askbot['urls']['edit_group_membership'] = '{% url edit_group_membership %}';
- askbot['urls']['get_groups_list'] = '{% url get_groups_list %}';
+ askbot['urls']['getGroupsList'] = '{% url get_groups_list %}';
</script>
{% if request.user|can_moderate_user(view_user) %}
<script type='text/javascript' src='{{"/js/jquery.form.js"|media}}'></script>
diff --git a/askbot/skins/default/templates/user_profile/user_inbox.html b/askbot/skins/default/templates/user_profile/user_inbox.html
index 62dd3e24..9f3461f1 100644
--- a/askbot/skins/default/templates/user_profile/user_inbox.html
+++ b/askbot/skins/default/templates/user_profile/user_inbox.html
@@ -70,7 +70,7 @@ inbox_section - forum|flags
{% include "user_profile/reject_post_dialog.html" %}
<div id="responses">
{% for response in responses %}
- <div class="response-parent">
+ <div class="response-parent" data-response-id="{{response.id}}">
<p class="headline">
<strong>"{{ response.response_title.strip()|escape}}"</strong>
</p>
@@ -79,6 +79,7 @@ inbox_section - forum|flags
{{ macros.inbox_post_snippet(nested_response, inbox_section) }}
{%endfor%}
</div>
+ <div class="clearfix"></div>
{% endfor %}
</div>
</div>
@@ -90,7 +91,15 @@ inbox_section - forum|flags
askbot['urls']['manageInbox'] = '{% url manage_inbox %}';
askbot['urls']['save_post_reject_reason'] = '{% url save_post_reject_reason %}';
askbot['urls']['delete_post_reject_reason'] = '{% url delete_post_reject_reason %}';
+ {% if request.user.is_administrator_or_moderator() %}
+ askbot['data']['postRejectReasons'] = [
+ {% for reason in post_reject_reasons %}
+ {'id': {{reason.id}}, 'title': '{{reason.title|escapejs}}'},
+ {% endfor %}
+ ];
+ {% endif %}
$(document).ready(function(){
+ $('body').addClass('inbox-{{ inbox_section }}');
setup_inbox();
});
</script>
diff --git a/askbot/skins/default/templates/widget_base.html b/askbot/skins/default/templates/widget_base.html
new file mode 100644
index 00000000..fd5e739e
--- /dev/null
+++ b/askbot/skins/default/templates/widget_base.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!-- template widget_base.html -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+ {% spaceless %}
+ <head>
+ {% include "meta/bottom_scripts.html" %}
+ {% block before_css %}{% endblock %}
+ {% block forestyle %}{% endblock %}
+ {% block forejs %}{% endblock %}
+ </head>
+ {% endspaceless %}
+ <body class="lang-{{settings.LANGUAGE_CODE}}">
+ {% block body%}
+ {% endblock %}
+ {% block content%}
+ {% endblock %}
+ {% block endjs %}
+ {% endblock %}
+ </body>
+</html>
+<!-- end template widget_base.html -->
diff --git a/askbot/skins/default/templates/widgets/ask_button.html b/askbot/skins/default/templates/widgets/ask_button.html
index 31448b73..e202b110 100644
--- a/askbot/skins/default/templates/widgets/ask_button.html
+++ b/askbot/skins/default/templates/widgets/ask_button.html
@@ -2,5 +2,8 @@
{% if not search_state %} {# get empty SearchState() if there's none #}
{% set search_state=search_state|get_empty_search_state %}
{% endif %}
- <a id="askButton" href="{{ search_state.full_ask_url() }}">{% trans %}Ask Your Question{% endtrans %}</a>
+ <a
+ id="askButton"
+ href="{{ search_state.full_ask_url() }}{% if group %}{% if '?' in search_state.full_ask_url() %}&{% else %}?{% endif %}group_id={{ group.id }}{% endif %}"
+ >{% if group %}{% trans %}Ask the Group{% endtrans %}{% else %}{% trans %}Ask Your Question{% endtrans %}{% endif %}</a>
{% endif %}
diff --git a/askbot/skins/default/templates/widgets/ask_form.html b/askbot/skins/default/templates/widgets/ask_form.html
index 86f10782..d528609f 100644
--- a/askbot/skins/default/templates/widgets/ask_form.html
+++ b/askbot/skins/default/templates/widgets/ask_form.html
@@ -3,7 +3,6 @@
<div class="form-item">
<div id="askFormBar">
{% if not request.user.is_authenticated() %}
- <p>{% trans %}login to post question info{% endtrans %}</p>
<p>{% trans %}<span class=\"strong big\">You are welcome to start submitting your question anonymously</span>. When you submit the post, you will be redirected to the login/signup page. Your question will be saved in the current session and will be published after you log in. Login/signup process is very simple. Login takes about 30 seconds, initial signup takes a minute or less.{% endtrans %}</p>
{% else %}
{% if settings.EMAIL_VALIDATION %}
@@ -28,9 +27,11 @@
edit_title = False,
mandatory_tags = mandatory_tags,
use_category_selector = (settings.TAG_SOURCE == 'category-tree'),
- editor_type = settings.EDITOR_TYPE
+ editor_type = settings.EDITOR_TYPE,
+ user = request.user
)
}}
+ {{ form.group_id }}
<div class="question-options">
{% if settings.WIKI_ON %}
{{ macros.checkbox_in_div(form.wiki) }}
diff --git a/askbot/tasks.py b/askbot/tasks.py
index 5b695a8c..4aa11798 100644
--- a/askbot/tasks.py
+++ b/askbot/tasks.py
@@ -27,8 +27,7 @@ from celery.decorators import task
from askbot.conf import settings as askbot_settings
from askbot import const
from askbot import mail
-from askbot.models import Activity, Post, Thread, User, ReplyAddress
-from askbot.models import send_instant_notifications_about_activity_in_post
+from askbot.models import Post, Thread, User, ReplyAddress
from askbot.models.badges import award_badges_signal
# TODO: Make exceptions raised inside record_post_update_celery_task() ...
@@ -102,122 +101,36 @@ def record_post_update_celery_task(
diff = None,
):
#reconstitute objects from the database
- updated_by = User.objects.get(id = updated_by_id)
- post_content_type = ContentType.objects.get(id = post_content_type_id)
- post = post_content_type.get_object_for_this_type(id = post_id)
+ updated_by = User.objects.get(id=updated_by_id)
+ post_content_type = ContentType.objects.get(id=post_content_type_id)
+ post = post_content_type.get_object_for_this_type(id=post_id)
newly_mentioned_users = User.objects.filter(
- id__in = newly_mentioned_user_id_list
+ id__in=newly_mentioned_user_id_list
)
try:
- record_post_update(
- post = post,
- updated_by = updated_by,
- newly_mentioned_users = newly_mentioned_users,
- timestamp = timestamp,
- created = created,
- diff = diff
+ notify_sets = post.get_notify_sets(
+ mentioned_users=newly_mentioned_users,
+ exclude_list=[updated_by,]
+ )
+ #todo: take into account created == True case
+ #update_object is not used
+ (activity_type, update_object) = post.get_updated_activity_data(created)
+
+ post.issue_update_notifications(
+ updated_by=updated_by,
+ notify_sets=notify_sets,
+ activity_type=activity_type,
+ timestamp=timestamp,
+ diff=diff
)
+
except Exception:
- # HACK: exceptions from Celery job don;t propagate upwards to Django test runner
- # so at least le't sprint tracebacks
+ # HACK: exceptions from Celery job don't propagate upwards
+ # to the Django test runner
+ # so at least let's print tracebacks
print >>sys.stderr, traceback.format_exc()
raise
-def record_post_update(
- post = None,
- updated_by = None,
- newly_mentioned_users = None,
- timestamp = None,
- created = False,
- diff = None
- ):
- """Called when a post is updated. Arguments:
-
- * ``newly_mentioned_users`` - users who are mentioned in the
- post for the first time
- * ``created`` - a boolean. True when ``post`` has just been created
- * remaining arguments are self - explanatory
-
- The method does two things:
-
- * records "red envelope" recipients of the post
- * sends email alerts to all subscribers to the post
- """
- #todo: take into account created == True case
- (activity_type, update_object) = post.get_updated_activity_data(created)
-
- if post.is_comment():
- #it's just a comment!
- summary = post.text
- else:
- #summary = post.get_latest_revision().summary
- summary = diff
-
- update_activity = Activity(
- user = updated_by,
- active_at = timestamp,
- content_object = post,
- activity_type = activity_type,
- question = post.get_origin_post(),
- summary = summary
- )
- update_activity.save()
-
- #what users are included depends on the post type
- #for example for question - all Q&A contributors
- #are included, for comments only authors of comments and parent
- #post are included
- recipients = post.get_response_receivers(
- exclude_list = [updated_by, ]
- )
-
- update_activity.add_recipients(recipients)
-
- #create new mentions
- for u in newly_mentioned_users:
- #todo: a hack - some users will not have record of a mention
- #may need to fix this in the future. Added this so that
- #recipients of the response who are mentioned as well would
- #not get two notifications in the inbox for the same post
- if u in recipients:
- continue
- Activity.objects.create_new_mention(
- mentioned_whom = u,
- mentioned_in = post,
- mentioned_by = updated_by,
- mentioned_at = timestamp
- )
-
- assert(updated_by not in recipients)
-
- for user in (set(recipients) | set(newly_mentioned_users)):
- user.update_response_counts()
-
- #shortcircuit if the email alerts are disabled
- if askbot_settings.ENABLE_EMAIL_ALERTS == False:
- return
-
- #todo: weird thing is that only comments need the recipients
- #todo: debug these calls and then uncomment in the repo
- #argument to this call
- notification_subscribers = post.get_instant_notification_subscribers(
- potential_subscribers = recipients,
- mentioned_users = newly_mentioned_users,
- exclude_list = [updated_by, ]
- )
- #todo: fix this temporary spam protection plug
- #if created:
- # if not (updated_by.is_administrator() or updated_by.is_moderator()):
- # if updated_by.reputation < 15:
- # notification_subscribers = \
- # [u for u in notification_subscribers if u.is_administrator()]
- send_instant_notifications_about_activity_in_post(
- update_activity = update_activity,
- post = post,
- recipients = notification_subscribers,
- )
-
-
@task(ignore_result = True)
def record_question_visit(
question_post = None,
diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py
index c20b7f67..056320f5 100644
--- a/askbot/tests/__init__.py
+++ b/askbot/tests/__init__.py
@@ -15,4 +15,6 @@ from askbot.tests.markup_test import *
from askbot.tests.post_model_tests import *
from askbot.tests.thread_model_tests import *
from askbot.tests.reply_by_email_tests import *
+from askbot.tests.widget_tests import *
from askbot.tests.category_tree_tests import CategoryTreeTests
+from askbot.tests.user_model_tests import UserModelTests
diff --git a/askbot/tests/__init__.py.orig b/askbot/tests/__init__.py.orig
new file mode 100644
index 00000000..905c90df
--- /dev/null
+++ b/askbot/tests/__init__.py.orig
@@ -0,0 +1,19 @@
+from askbot.tests.cache_tests import *
+from askbot.tests.email_alert_tests import *
+from askbot.tests.on_screen_notification_tests import *
+from askbot.tests.page_load_tests import *
+from askbot.tests.permission_assertion_tests import *
+from askbot.tests.db_api_tests import *
+from askbot.tests.skin_tests import *
+from askbot.tests.badge_tests import *
+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 *
+from askbot.tests.reply_by_email_tests import *
+from askbot.tests.category_tree_tests import CategoryTreeTests
+from askbot.tests.user_model_tests import UserModelTests
diff --git a/askbot/tests/badge_tests.py b/askbot/tests/badge_tests.py
index dbb37dde..b66eadcc 100644
--- a/askbot/tests/badge_tests.py
+++ b/askbot/tests/badge_tests.py
@@ -68,7 +68,8 @@ class BadgeTests(AskbotTestCase):
self.assert_have_badge(badge_key, recipient = self.u2, expected_count = 1)
#post another question and check that there are no new badges
- answer2 = self.post_answer(user = self.u2, question = question)
+ question2 = self.post_question(user = self.u1)
+ answer2 = self.post_answer(user = self.u2, question = question2)
answer2.score = min_score - 1
answer2.save()
self.u1.upvote(answer2)
@@ -269,7 +270,8 @@ class BadgeTests(AskbotTestCase):
answer = self.post_answer(user = self.u2, question = question)
self.u1.accept_best_answer(answer)
self.assert_have_badge('scholar', recipient = self.u1)
- answer2 = self.post_answer(user = self.u2, question = question)
+ question2 = self.post_question(user = self.u1)
+ answer2 = self.post_answer(user = self.u2, question = question2)
self.u1.accept_best_answer(answer2)
self.assert_have_badge(
'scholar',
diff --git a/askbot/tests/cache_tests.py b/askbot/tests/cache_tests.py
index a8416e99..5740cc2a 100644
--- a/askbot/tests/cache_tests.py
+++ b/askbot/tests/cache_tests.py
@@ -24,20 +24,3 @@ class CacheTests(AskbotTestCase):
#second hit to the same question should give fewer queries
self.assertTrue(counter > len(connection.queries))
settings.DEBUG = False
-
- def test_authentificated_no_question_cache(self):
- url = reverse('question', kwargs={'id': self.question.id})
-
- password = '123'
- self.other_user.set_password(password)
- self.client.login(username=self.other_user.username, password=password)
-
- self.visit_question()
- counter = len(connection.queries)
- self.visit_question()
-
- #expect the same number of queries both times
- self.assertEqual(counter, len(connection.queries))
- settings.DEBUG = False
-
-
diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py
index 3a0c9582..07590487 100644
--- a/askbot/tests/db_api_tests.py
+++ b/askbot/tests/db_api_tests.py
@@ -7,11 +7,13 @@ from django.core import exceptions
from django.core.urlresolvers import reverse
from django.test.client import Client
from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
from django import forms
from askbot.tests.utils import AskbotTestCase
from askbot import models
from askbot import const
from askbot.conf import settings as askbot_settings
+from askbot.models.tag import get_global_group
import datetime
class DBApiTests(AskbotTestCase):
@@ -46,6 +48,13 @@ class DBApiTests(AskbotTestCase):
self.assertTrue(post.deleted_by == None)
self.assertTrue(post.deleted_at == None)
+ def test_blank_tags_impossible(self):
+ self.post_question(tags='')
+ self.assertEqual(
+ models.Tag.objects.filter(name='').count(),
+ 0
+ )
+
def test_flag_question(self):
self.user.set_status('m')
self.user.flag_post(self.question)
@@ -395,6 +404,32 @@ class CommentTests(AskbotTestCase):
class TagAndGroupTests(AskbotTestCase):
def setUp(self):
self.u1 = self.create_user('u1')
+ askbot_settings.update('GROUPS_ENABLED', True)
+
+ def tearDown(self):
+ askbot_settings.update('GROUPS_ENABLED', False)
+
+ def assertObjectGroupsEqual(self, obj, expected_groups):
+ self.assertEqual(set(obj.groups.all()), set(expected_groups))
+
+ def post_question_answer_and_comments(self, is_private=False):
+ question = self.post_question(user=self.u1, is_private=is_private)
+ answer = self.post_answer(
+ user=self.u1, question=question, is_private=is_private
+ )
+ question_comment = self.post_comment(
+ user=self.u1, parent_post=question
+ )
+ answer_comment = self.post_comment(
+ user=self.u1, parent_post=answer
+ )
+ return {
+ 'thread': question.thread,
+ 'question': question,
+ 'answer': answer,
+ 'question_comment': question_comment,
+ 'answer_comment': answer_comment
+ }
def test_group_cannot_create_case_variant_tag(self):
self.post_question(user = self.u1, tags = 'one two three')
@@ -402,3 +437,145 @@ class TagAndGroupTests(AskbotTestCase):
tag_one = models.Tag.objects.filter(name__iexact = 'one')
self.assertEqual(tag_one.count(), 1)
self.assertEqual(tag_one[0].name, 'one')
+
+ def test_posts_added_to_global_group(self):
+ q = self.post_question(user=self.u1)
+ group_name = askbot_settings.GLOBAL_GROUP_NAME
+ self.assertEqual(q.groups.filter(name=group_name).exists(), True)
+
+ a = self.post_answer(question=q, user=self.u1)
+ self.assertEqual(a.groups.filter(name=group_name).exists(), True)
+
+ c = self.post_comment(parent_post=a, user=self.u1)
+ self.assertEqual(c.groups.filter(name=group_name).exists(), True)
+
+ def test_posts_added_to_private_group(self):
+ group = self.create_group(group_name='private', user=self.u1)
+ self.u1.join_group(group)
+
+ q = self.post_question(user=self.u1, is_private=True)
+ self.assertEqual(q.groups.count(), 2)
+ self.assertEqual(q.groups.filter(name='private').exists(), True)
+
+ a = self.post_answer(question=q, user=self.u1, is_private=True)
+ self.assertEqual(a.groups.count(), 2)
+ self.assertEqual(a.groups.filter(name='private').exists(), True)
+
+ qc = self.post_comment(parent_post=q, user=self.u1)#w/o private arg
+ self.assertEqual(qc.groups.count(), 2)
+ self.assertEqual(qc.groups.filter(name='private').exists(), True)
+
+ qa = self.post_comment(parent_post=a, user=self.u1)#w/o private arg
+ self.assertEqual(qa.groups.count(), 2)
+ self.assertEqual(qa.groups.filter(name='private').exists(), True)
+
+ def test_global_group_name_setting_changes_group_name(self):
+ askbot_settings.update('GLOBAL_GROUP_NAME', 'all-people')
+ group = get_global_group()
+ self.assertEqual(group.name, 'all-people')
+
+ def test_ask_global_group_by_id_works(self):
+ group = get_global_group()
+ q = self.post_question(user=self.u1, group_id=group.id)
+ self.assertEqual(q.groups.count(), 2)
+ self.assertEqual(q.groups.filter(name=group.name).exists(), True)
+
+ def test_making_public_question_private_works(self):
+ question = self.post_question(user=self.u1)
+ comment = self.post_comment(parent_post=question, user=self.u1)
+ group = self.create_group(group_name='private', user=self.u1)
+ self.u1.join_group(group)
+ self.edit_question(question=question, user=self.u1, is_private=True)
+ self.assertEqual(question.groups.count(), 2)
+ self.assertEqual(question.groups.filter(id=group.id).count(), 1)
+ #comment inherits sharing scope
+ self.assertEqual(comment.groups.count(), 2)
+ self.assertEqual(comment.groups.filter(id=group.id).count(), 1)
+
+ def test_making_public_answer_private_works(self):
+ question = self.post_question(user=self.u1)
+ answer = self.post_answer(question=question, user=self.u1)
+ comment = self.post_comment(parent_post=answer, user=self.u1)
+ group = self.create_group(group_name='private', user=self.u1)
+ self.u1.join_group(group)
+ self.edit_answer(user=self.u1, answer=answer, is_private=True)
+ self.assertEqual(answer.groups.count(), 2)
+ self.assertEqual(answer.groups.filter(id=group.id).count(), 1)
+ #comment inherits the sharing scope
+ self.assertEqual(comment.groups.count(), 2)
+ self.assertEqual(comment.groups.filter(id=group.id).count(), 1)
+
+ def test_public_question_private_answer_works(self):
+ question = self.post_question(self.u1)
+
+ u2 = self.create_user('u2')
+ group = self.create_group(group_name='private', user=u2)
+ u2.join_group(group)
+
+ answer = self.post_answer(question=question, user=u2, is_private=True)
+
+ threads = models.Thread.objects
+ #u2 will see question and answer
+ self.assertEqual(answer.thread.get_answer_count(user=u2), 1)
+ self.assertEqual(threads.get_visible(u2).count(), 1)
+ #u1 will see only question
+ self.assertEqual(answer.thread.get_answer_count(user=self.u1), 0)
+ self.assertEqual(threads.get_visible(self.u1).count(), 1)
+ #anonymous will see question
+ self.assertEqual(answer.thread.get_answer_count(), 0)
+ anon = AnonymousUser()
+ self.assertEqual(threads.get_visible(anon).count(), 1)
+
+ def test_thread_answer_count_for_multiple_groups(self):
+ question = self.post_question(self.u1)
+ group = self.create_group(group_name='private', user=self.u1)
+ self.u1.join_group(group)
+ answer = self.post_answer(question=question, user=self.u1)
+ answer.add_to_groups((group,))
+ self.assertEqual(answer.groups.count(), 3)
+ self.assertEqual(answer.thread.posts.get_answers(self.u1).count(), 1)
+
+ def test_thread_make_public_recursive(self):
+ private_group = self.create_group(group_name='private', user=self.u1)
+ self.u1.join_group(private_group)
+ data = self.post_question_answer_and_comments(is_private=True)
+
+ groups = [private_group, self.u1.get_personal_group()]
+ self.assertObjectGroupsEqual(data['thread'], groups)
+ self.assertObjectGroupsEqual(data['question'], groups)
+ self.assertObjectGroupsEqual(data['question_comment'], groups)
+ self.assertObjectGroupsEqual(data['answer'], groups)
+ self.assertObjectGroupsEqual(data['answer_comment'], groups)
+
+ data['thread'].make_public(recursive=True)
+
+ global_group = get_global_group()
+ groups = [global_group, private_group, self.u1.get_personal_group()]
+ self.assertObjectGroupsEqual(data['thread'], groups)
+ self.assertObjectGroupsEqual(data['question'], groups)
+ self.assertObjectGroupsEqual(data['question_comment'], groups)
+ self.assertObjectGroupsEqual(data['answer'], groups)
+ self.assertObjectGroupsEqual(data['answer_comment'], groups)
+
+ def test_thread_add_to_groups_recursive(self):
+ data = self.post_question_answer_and_comments()
+
+ private_group = self.create_group(group_name='private', user=self.u1)
+ thread = data['thread']
+ thread.add_to_groups([private_group], recursive=True)
+
+ global_group = get_global_group()
+ groups = [global_group, private_group, self.u1.get_personal_group()]
+ self.assertObjectGroupsEqual(thread, groups)
+ self.assertObjectGroupsEqual(data['question'], groups)
+ self.assertObjectGroupsEqual(data['question_comment'], groups)
+ self.assertObjectGroupsEqual(data['answer'], groups)
+ self.assertObjectGroupsEqual(data['answer_comment'], groups)
+
+ def test_private_thread_is_invisible_to_anonymous_user(self):
+ group = self.create_group(group_name='private', user=self.u1)
+ self.u1.join_group(group)
+ self.post_question(user=self.u1, is_private=True)
+
+ visible_threads = models.Thread.objects.get_visible(AnonymousUser())
+ self.assertEqual(visible_threads.count(), 0)
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index 9ec1a412..b377a429 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -713,6 +713,8 @@ class FeedbackTests(utils.AskbotTestCase):
def assert_feedback_works(self):
outbox = django.core.mail.outbox
self.assertEqual(len(outbox), 1)
+ #todo: change groups to django groups
+ #then replace to 4 back to 3 in the line below
self.assertEqual(len(outbox[0].recipients()), 3)
def test_feedback_post_form(self):
@@ -898,6 +900,8 @@ class UnansweredReminderTests(EmailReminderTestCase):
days_ago = self.wait_days + (self.max_emails - 1)*self.recurrence_days - 1
timestamp = datetime.datetime.now() - datetime.timedelta(days_ago, 3600)
self.do_post(timestamp)
+ #todo: change groups to django groups
+ #then replace to 2 back to 1 in the line below
self.assert_have_emails(1)
diff --git a/askbot/tests/form_tests.py b/askbot/tests/form_tests.py
index 654272b3..be88cf39 100644
--- a/askbot/tests/form_tests.py
+++ b/askbot/tests/form_tests.py
@@ -72,8 +72,8 @@ class AskByEmailFormTests(AskbotTestCase):
askbot_settings.update('TAGS_ARE_REQUIRED', setting_backup)
def test_email(self):
- """loops through variants of the from field
- in the emails and tests the email address
+ """loops through variants of the from field
+ in the emails and tests the email address
extractor"""
for test_case in EMAIL_CASES:
self.data['sender'] = test_case[0]
@@ -178,7 +178,7 @@ class EditQuestionAnonymouslyFormTests(AskbotTestCase):
def setup_data(self, is_anon, can_be_anon, is_owner, box_checked):
"""sets up data in the same order as shown in the
truth table above
-
+
the four positional arguments are in the same order
"""
askbot_settings.update('ALLOW_ASK_ANONYMOUSLY', can_be_anon)
@@ -264,7 +264,7 @@ class UserStatusFormTest(AskbotTestCase):
self.moderator.set_status('m')
self.subject = self.create_user('normal_user')
self.subject.set_status('a')
- self.form = forms.ChangeUserStatusForm(data, moderator = self.moderator,
+ self.form = forms.ChangeUserStatusForm(data, moderator = self.moderator,
subject = self.subject)
def test_moderator_can_suspend_user(self):
self.setup_data('s')
@@ -292,7 +292,7 @@ class UserNameFieldTest(AskbotTestCase):
self.username_field.skip_clean = True
self.assertEquals(self.username_field.clean('bar'), 'bar')#will pass anything
- self.username_field.skip_clean = False
+ self.username_field.skip_clean = False
#will not pass b/c instance is not User model
self.username_field.user_instance = dict(foo=1)
@@ -328,9 +328,57 @@ class AnswerEditorFieldTests(AskbotTestCase):
self.field.clean,
'a'
)
-
+
def test_pass_long_body(self):
self.assertEquals(
self.field.clean(10*'a'),
10*'a'
)
+
+
+class PostAsSomeoneFormTests(AskbotTestCase):
+
+ form = forms.PostAsSomeoneForm
+
+ def setUp(self):
+ self.good_data = {
+ 'username': 'me',
+ 'email': 'me@example.com'
+ }
+
+ def test_blank_form_validates(self):
+ form = forms.PostAsSomeoneForm({})
+ self.assertEqual(form.is_valid(), True)
+
+ def test_complete_form_validates(self):
+ form = forms.PostAsSomeoneForm(self.good_data)
+ self.assertEqual(form.is_valid(), True)
+
+ def test_missing_email_fails(self):
+ form = forms.PostAsSomeoneForm({'post_author_username': 'me'})
+ self.assertEqual(form.is_valid(), False)
+
+ def test_missing_username_fails(self):
+ form = forms.PostAsSomeoneForm({'post_author_email': 'me@example.com'})
+ self.assertEqual(form.is_valid(), False)
+
+class AskWidgetFormTests(AskbotTestCase):
+
+ form = forms.AskWidgetForm
+
+ def setUp(self):
+ self.good_data = {'title': "What's the price of a house in london?"}
+ self.good_data_anon = {'title': "What's the price of a house in london?",
+ 'ask_anonymously': True}
+
+ self.bad_data = {'title': ''}
+
+ def test_valid_input(self):
+ form_object = self.form(self.good_data)
+ self.assertTrue(form_object.is_valid())
+ form_object = self.form(self.good_data_anon)
+ self.assertTrue(form_object.is_valid())
+
+ def test_invalid_input(self):
+ form_object = self.form(self.bad_data)
+ self.assertFalse(form_object.is_valid())
diff --git a/askbot/tests/management_command_tests.py b/askbot/tests/management_command_tests.py
index d6be1a16..e9341a8b 100644
--- a/askbot/tests/management_command_tests.py
+++ b/askbot/tests/management_command_tests.py
@@ -46,6 +46,8 @@ class ManagementCommandTests(AskbotTestCase):
# Explicitly check that the values assigned to user_one are now user_two's
self.assertEqual(user_two.posts.get_questions().filter(pk=question.id).count(), 1)
self.assertEqual(user_two.posts.get_comments().filter(pk=comment.id).count(), 1)
+ #todo: change groups to django groups
+ #then replace to 3 back to 2 in the line below
user_two = models.User.objects.get(pk=2)
self.assertEqual(user_two.gold, number_of_gold)
self.assertEqual(user_two.reputation, reputation)
diff --git a/askbot/tests/permission_assertion_tests.py b/askbot/tests/permission_assertion_tests.py
index 2f746ef9..8061bdb8 100644
--- a/askbot/tests/permission_assertion_tests.py
+++ b/askbot/tests/permission_assertion_tests.py
@@ -447,9 +447,9 @@ class ReopenQuestionPermissionAssertionTests(utils.AskbotTestCase):
)
- def test_high_rep_nonowner_cannot_reopen(self):
+ def test_high_rep_nonowner_can_reopen(self):
self.other_user.reputation = 1000000
- self.assert_cannot_reopen(user = self.other_user)
+ self.assert_can_reopen(user = self.other_user)
def test_low_rep_admin_can_reopen(self):
self.other_user.set_admin_status()
@@ -482,7 +482,7 @@ class ReopenQuestionPermissionAssertionTests(utils.AskbotTestCase):
self.assert_cannot_reopen(user = self.other_user)
class EditQuestionPermissionAssertionTests(utils.AskbotTestCase):
-
+
def setUp(self):
self.create_user()
self.create_user(username = 'other_user')
diff --git a/askbot/tests/user_model_tests.py b/askbot/tests/user_model_tests.py
new file mode 100644
index 00000000..7a8fd9f2
--- /dev/null
+++ b/askbot/tests/user_model_tests.py
@@ -0,0 +1,21 @@
+from askbot.tests.utils import AskbotTestCase
+from django.contrib.auth.models import User
+from askbot import models
+from askbot.models.tag import format_personal_group_name
+
+class UserModelTests(AskbotTestCase):
+ """test user model"""
+
+ def test_new_user_has_personal_group(self):
+ user = User.objects.create_user('somebody', 'somebody@example.com')
+ group_name = format_personal_group_name(user)
+ group = models.Tag.objects.filter(name=group_name)
+ self.assertEqual(group.count(), 1)
+
+ group_profile = models.GroupProfile.objects.filter(group_tag=group)
+ self.assertEqual(group_profile.count(), 1)
+
+ memberships = models.GroupMembership.objects.filter(
+ group=group, user=user
+ )
+ self.assertEqual(memberships.count(), 1)
diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py
index 7b4134d1..dd46e31d 100644
--- a/askbot/tests/utils.py
+++ b/askbot/tests/utils.py
@@ -92,7 +92,6 @@ class AskbotTestCase(TestCase):
)
setattr(self, username, user_object)
-
return user_object
def assertRaisesRegexp(self, *args, **kwargs):
@@ -119,6 +118,7 @@ class AskbotTestCase(TestCase):
wiki = False,
is_anonymous = False,
is_private = False,
+ group_id = None,
follow = False,
timestamp = None,
):
@@ -134,14 +134,15 @@ class AskbotTestCase(TestCase):
user = self.user
question = user.post_question(
- title = title,
- body_text = body_text,
- tags = tags,
- by_email = by_email,
- wiki = wiki,
- is_anonymous = is_anonymous,
- is_private = is_private,
- timestamp = timestamp
+ title=title,
+ body_text=body_text,
+ tags=tags,
+ by_email=by_email,
+ wiki=wiki,
+ is_anonymous=is_anonymous,
+ is_private=is_private,
+ group_id=group_id,
+ timestamp=timestamp
)
if follow:
@@ -149,6 +150,59 @@ class AskbotTestCase(TestCase):
return question
+ def edit_question(self,
+ user=None,
+ question=None,
+ title='edited title',
+ body_text='edited body text',
+ revision_comment='edited the question',
+ tags='one two three four',
+ wiki=False,
+ edit_anonymously=False,
+ is_private=False,
+ timestamp=None,
+ force=False,#if True - bypass the assert
+ by_email=False
+ ):
+ """helper editing the question,
+ a bunch of fields are pre-filled for the ease of use
+ """
+ user.edit_question(
+ question=question,
+ title=title,
+ body_text=body_text,
+ revision_comment=revision_comment,
+ tags=tags,
+ wiki=wiki,
+ edit_anonymously=edit_anonymously,
+ is_private=is_private,
+ timestamp=timestamp,
+ force=False,#if True - bypass the assert
+ by_email=False
+ )
+
+ def edit_answer(self,
+ user=None,
+ answer=None,
+ body_text='edited answer body',
+ revision_comment='editing answer',
+ wiki=False,
+ is_private=False,
+ timestamp=None,
+ force=False,#if True - bypass the assert
+ by_email=False
+ ):
+ user.edit_answer(
+ answer=answer,
+ body_text=body_text,
+ revision_comment=revision_comment,
+ wiki=wiki,
+ is_private=is_private,
+ timestamp=timestamp,
+ force=force,
+ by_email=by_email
+ )
+
def reload_object(self, obj):
"""reloads model object from the database
"""
@@ -190,6 +244,12 @@ class AskbotTestCase(TestCase):
tag.save()
return tag
+ def create_group(self, group_name=None, user=None):
+ return models.Tag.group_tags.get_or_create(
+ group_name='private',
+ user=self.u1
+ )
+
def post_comment(
self,
user = None,
diff --git a/askbot/tests/widget_tests.py b/askbot/tests/widget_tests.py
new file mode 100644
index 00000000..40c63e0e
--- /dev/null
+++ b/askbot/tests/widget_tests.py
@@ -0,0 +1,61 @@
+from datetime import datetime
+
+from askbot import models
+from askbot.tests.utils import AskbotTestCase
+
+from django.test.client import Client
+from django.core.urlresolvers import reverse
+
+
+class WidgetViewsTests(AskbotTestCase):
+
+ def setUp(self):
+ self.client = Client()
+ self.user = self.create_user('user1')
+ self.user.set_password('sample')
+ self.user.save()
+ self.good_data = {'title': 'This is a title question',
+ 'ask_anonymously': False}
+
+ def test_post_with_auth(self):
+ self.client.login(username='user1', password='sample')
+ response = self.client.post(reverse('ask_by_widget'), self.good_data)
+ self.assertEquals(response.status_code, 302)
+ self.client.logout()
+
+ def test_post_without_auth(self):
+ response = self.client.post(reverse('ask_by_widget'), self.good_data)
+ self.assertEquals(response.status_code, 302)
+ self.assertTrue('widget_question' in self.client.session)
+ self.assertEquals(self.client.session['widget_question']['title'],
+ self.good_data['title'])
+
+ def test_post_after_login(self):
+ widget_question_data = { 'title': 'testing post after login, does it?',
+ 'author': self.user,
+ 'added_at': datetime.now(),
+ 'wiki': False,
+ 'text': ' ',
+ 'tagnames': '',
+ 'is_anonymous': False
+ }
+
+ self.client.login(username='user1', password='sample')
+
+ session = self.client.session
+ session['widget_question'] = widget_question_data
+ session.save()
+ response = self.client.get(reverse('ask_by_widget'),
+ {'action': 'post-after-login'})
+ self.assertFalse('widget_question' in self.client.session)
+ self.assertEquals(response.status_code, 302)
+ #verify posting question
+
+class WidgetLoginViewTest(AskbotTestCase):
+
+ def test_correct_template_loading(self):
+ client = Client()
+ response = client.get(reverse('widget_signin'))
+ template_name = 'authopenid/widget_signin.html'
+ templates = [template.name for template in response.templates]
+ self.assertTrue(template_name in templates)
diff --git a/askbot/urls.py b/askbot/urls.py
index 726722ff..cee5752a 100644
--- a/askbot/urls.py
+++ b/askbot/urls.py
@@ -31,9 +31,9 @@ APP_PATH = os.path.dirname(__file__)
urlpatterns = patterns('',
url(r'^$', views.readers.index, name='index'),
url(
- r'^sitemap.xml$',
- 'django.contrib.sitemaps.views.sitemap',
- {'sitemaps': sitemaps},
+ r'^sitemap.xml$',
+ 'django.contrib.sitemaps.views.sitemap',
+ {'sitemaps': sitemaps},
name='sitemap'
),
#no translation for this url!!
@@ -43,13 +43,13 @@ urlpatterns = patterns('',
url(r'^%s$' % _('privacy/'), views.meta.privacy, name='privacy'),
url(r'^%s$' % _('help/'), views.meta.help, name='help'),
url(
- r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')),
- views.writers.edit_answer,
+ r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')),
+ views.writers.edit_answer,
name='edit_answer'
),
url(
- r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')),
- views.readers.revisions,
+ r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')),
+ views.readers.revisions,
kwargs = {'post_type': 'answer'},
name='answer_revisions'
),
@@ -67,61 +67,86 @@ urlpatterns = patterns('',
r'(%s)?' % r'/page:(?P<page>\d+)' +
r'/$'),
- views.readers.questions,
+ views.readers.questions,
name='questions'
),
# END main page urls
-
+
url(
r'^api/get_questions/',
views.commands.api_get_questions,
name = 'api_get_questions'
),
url(
- r'^%s%s$' % (_('questions/'), _('ask/')),
- views.writers.ask,
+ r'^save-draft-question/',
+ views.commands.save_draft_question,
+ name = 'save_draft_question'
+ ),
+ url(
+ r'^save-draft-answer/',
+ views.commands.save_draft_answer,
+ name = 'save_draft_answer'
+ ),
+ url(
+ r'^share-question-with-group/',
+ views.commands.share_question_with_group,
+ name='share_question_with_group'
+ ),
+ url(
+ r'^share-question-with-user/',
+ views.commands.share_question_with_user,
+ name='share_question_with_user'
+ ),
+ url(
+ r'^get-users-info/',
+ views.commands.get_users_info,
+ name='get_users_info'
+ ),
+ url(
+ r'^%s%s$' % (_('questions/'), _('ask/')),
+ views.writers.ask,
name='ask'
),
url(
- r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')),
- views.writers.edit_question,
+ r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')),
+ views.writers.edit_question,
name='edit_question'
),
url(#this url is both regular and ajax
- r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('retag/')),
- views.writers.retag_question,
+ r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('retag/')),
+ views.writers.retag_question,
name='retag_question'
),
url(
- r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')),
- views.commands.close,
+ r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')),
+ views.commands.close,
name='close'
),
url(
- r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')),
- views.commands.reopen,
+ r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')),
+ views.commands.reopen,
name='reopen'
),
url(
- r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')),
- views.writers.answer,
+ r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')),
+ views.writers.answer,
name='answer'
),
url(#ajax only
- r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')),
- views.commands.vote,
+ r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')),
+ views.commands.vote,
name='vote'
),
url(
- r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')),
- views.readers.revisions,
+ r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')),
+ views.readers.revisions,
kwargs = {'post_type': 'question'},
name='question_revisions'
),
url(
r'^%s%s$' % (_('widgets/'), _('questions/')),
- views.readers.widget_questions,
+ views.readers.widget_questions,
name='widget_questions'
),
url(#ajax only
@@ -136,7 +161,7 @@ urlpatterns = patterns('',
),
url(#ajax only
r'^post_comments/$',
- views.writers.post_comments,
+ views.writers.post_comments,
name='post_comments'
),
url(#ajax only
@@ -146,17 +171,17 @@ urlpatterns = patterns('',
),
url(#ajax only
r'^comment/delete/$',
- views.writers.delete_comment,
+ views.writers.delete_comment,
name='delete_comment'
),
url(#ajax only
r'^comment/get_text/$',
- views.readers.get_comment,
+ views.readers.get_comment,
name='get_comment'
),
url(
- r'^%s$' % _('tags/'),
- views.readers.tags,
+ r'^%s$' % _('tags/'),
+ views.readers.tags,
name='tags'
),
url(
@@ -271,12 +296,12 @@ urlpatterns = patterns('',
),
url(
r'^%s$' % _('users/'),
- views.users.show_users,
+ views.users.show_users,
name='users'
),
url(
r'^%s%s(?P<group_id>\d+)/(?P<group_slug>.*)/$' % (_('users/'), _('by-group/')),
- views.users.show_users,
+ views.users.show_users,
kwargs = {'by_group': True},
name = 'users_by_group'
),
@@ -355,8 +380,19 @@ urlpatterns = patterns('',
views.commands.join_or_leave_group,
name = 'join_or_leave_group'
),
+ #widgets url!
+ url(
+ r'^widgets/ask/$',
+ views.widgets.ask_widget,
+ name = 'ask_by_widget'
+ ),
+ url(
+ r'^widgets/ask/complete/$',
+ views.widgets.ask_widget_complete,
+ name = 'ask_by_widget_complete'
+ ),
url(
- r'^feeds/(?P<url>.*)/$',
+ r'^feeds/(?P<url>.*)/$',
'django.contrib.syndication.views.feed',
{'feed_dict': feeds},
name='feeds'
@@ -366,7 +402,7 @@ urlpatterns = patterns('',
url(r'^%s$' % _('feedback/'), views.meta.feedback, name='feedback'),
#url(r'^feeds/rss/$', RssLastestQuestionsFeed, name="latest_questions_feed"),
url(
- r'^doc/(?P<path>.*)$',
+ r'^doc/(?P<path>.*)$',
'django.views.static.serve',
{'document_root': os.path.join(APP_PATH,'doc','build','html').replace('\\','/')},
name='askbot_docs',
@@ -401,14 +437,14 @@ urlpatterns = patterns('',
#therefore the stackexchange urls feature won't work
if getattr(settings, 'ASKBOT_USE_STACKEXCHANGE_URLS', False):
urlpatterns += (url(
- r'^%s(?P<id>\d+)/' % _('questions/'),
- views.readers.question,
+ r'^%s(?P<id>\d+)/' % _('questions/'),
+ views.readers.question,
name='question'
),)
else:
urlpatterns += (url(
- r'^%s(?P<id>\d+)/' % _('question/'),
- views.readers.question,
+ r'^%s(?P<id>\d+)/' % _('question/'),
+ views.readers.question,
name='question'
),)
diff --git a/askbot/utils/console.py b/askbot/utils/console.py
index fe6ad29e..a7787325 100644
--- a/askbot/utils/console.py
+++ b/askbot/utils/console.py
@@ -92,14 +92,8 @@ class ProgressBar(object):
def __iter__(self):
return self
- def next(self):
-
- try:
- result = self.iterable.next()
- except StopIteration:
- if self.length > 0:
- sys.stdout.write('\n')
- raise
+ def print_progress_bar(self):
+ """prints the progress bar"""
sys.stdout.write('\b'*len(self.progress))
@@ -112,5 +106,17 @@ class ProgressBar(object):
sys.stdout.write(self.progress)
sys.stdout.flush()
+
+ def next(self):
+
+ try:
+ result = self.iterable.next()
+ except StopIteration:
+ if self.length > 0:
+ self.print_progress_bar()
+ sys.stdout.write('\n')
+ raise
+
+ self.print_progress_bar()
self.counter += 1
return result
diff --git a/askbot/utils/forms.py b/askbot/utils/forms.py
index b8ed253b..ee7adf7e 100644
--- a/askbot/utils/forms.py
+++ b/askbot/utils/forms.py
@@ -108,7 +108,7 @@ class UserNameField(StrippedNonEmptyCharField):
raise forms.ValidationError(self.error_messages['invalid'])
if username in self.RESERVED_NAMES:
raise forms.ValidationError(self.error_messages['forbidden'])
- if slugify(username, force_unidecode = True) == '':
+ if slugify(username) == '':
raise forms.ValidationError(self.error_messages['meaningless'])
try:
user = self.db_model.objects.get(
diff --git a/askbot/utils/slug.py b/askbot/utils/slug.py
index 58f228da..f9e30cbf 100644
--- a/askbot/utils/slug.py
+++ b/askbot/utils/slug.py
@@ -5,30 +5,63 @@ the setting was added just in case - if people actually
want to see unicode characters in the slug. If this is the choice
slug will be simply equal to the input text
"""
+import re
+import unicodedata
from unidecode import unidecode
-from django.template import defaultfilters
+
from django.conf import settings
-import re
+from django.template import defaultfilters
+from django.utils.encoding import smart_unicode
+
+
+# Extra characters outside of alphanumerics that we'll allow.
+SLUG_OK = '-_~'
+
-def slugify(input_text, max_length=50, force_unidecode = False):
+def unicode_slugify(s, ok=SLUG_OK, lower=True, spaces=False):
+ """Function copied from https://github.com/mozilla/unicode-slugify
+ because the author of the package never published it on pypi.
+
+ Copyright notice below applies just to this function
+ Copyright (c) 2011, Mozilla Foundation
+ All rights reserved.
+
+ L and N signify letter/number.
+ http://www.unicode.org/reports/tr44/tr44-4.html#GC_Values_Table
+ """
+ rv = []
+ for c in unicodedata.normalize('NFKC', smart_unicode(s)):
+ cat = unicodedata.category(c)[0]
+ if cat in 'LN' or c in ok:
+ rv.append(c)
+ if cat == 'Z': # space
+ rv.append(' ')
+ new = ''.join(rv).strip()
+ if not spaces:
+ new = re.sub('[-\s]+', '-', new)
+ return new.lower() if lower else new
+
+
+def slugify(input_text, max_length=150):
"""custom slugify function that
removes diacritic modifiers from the characters
"""
+ if input_text == '':
+ return input_text
+
allow_unicode_slugs = getattr(settings, 'ALLOW_UNICODE_SLUGS', False)
- if allow_unicode_slugs == False or force_unidecode == True:
- if input_text == '':
- return input_text
- slug = defaultfilters.slugify(unidecode(input_text))
- while len(slug) > max_length:
- # try to shorten word by word until len(slug) <= max_length
- temp = slug[:slug.rfind('-')]
- if len(temp) > 0:
- slug = temp
- else:
- #we have nothing left, do not apply the last crop,
- #apply the cut-off directly
- slug = slug[:max_length]
- break
- return slug
+ if allow_unicode_slugs:
+ slug = unicode_slugify(input_text)
else:
- return re.sub(r'\s+', '-', input_text.strip().lower())
+ slug = defaultfilters.slugify(unidecode(input_text))
+ while len(slug) > max_length:
+ # try to shorten word by word until len(slug) <= max_length
+ temp = slug[:slug.rfind('-')]
+ if len(temp) > 0:
+ slug = temp
+ else:
+ #we have nothing left, do not apply the last crop,
+ #apply the cut-off directly
+ slug = slug[:max_length]
+ break
+ return slug
diff --git a/askbot/views/__init__.py b/askbot/views/__init__.py
index b9aaf8a9..a3c5e06d 100644
--- a/askbot/views/__init__.py
+++ b/askbot/views/__init__.py
@@ -6,6 +6,7 @@ from askbot.views import writers
from askbot.views import commands
from askbot.views import users
from askbot.views import meta
+from askbot.views import widgets
from django.conf import settings
if 'avatar' in settings.INSTALLED_APPS:
from askbot.views import avatar_views
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index 56848267..1b6b21e8 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -24,6 +24,7 @@ from askbot import models
from askbot import forms
from askbot.conf import should_show_sort_by_relevance
from askbot.conf import settings as askbot_settings
+from askbot.models.tag import get_global_group
from askbot.utils import category_tree
from askbot.utils import decorators
from askbot.utils import url_utils
@@ -614,8 +615,13 @@ def add_tag_category(request):
def get_groups_list(request):
"""returns names of group tags
for the autocomplete function"""
+ global_group = get_global_group()
group_names = models.Tag.group_tags.get_all().filter(
deleted = False
+ ).exclude(
+ name=global_group.name
+ ).exclude(
+ name__startswith='_internal_'
).values_list(
'name', flat = True
)
@@ -1056,3 +1062,169 @@ def moderate_suggested_tag(request):
tag.delete()
else:
raise Exception(forms.format_form_errors(form))
+
+
+@csrf.csrf_exempt
+@decorators.ajax_only
+@decorators.post_only
+def save_draft_question(request):
+ """saves draft questions"""
+ #todo: allow drafts for anonymous users
+ if request.user.is_anonymous():
+ return
+
+ form = forms.DraftQuestionForm(request.POST)
+ if form.is_valid():
+ title = form.cleaned_data.get('title', '')
+ text = form.cleaned_data.get('text', '')
+ tagnames = form.cleaned_data.get('tagnames', '')
+ if title or text or tagnames:
+ try:
+ draft = models.DraftQuestion.objects.get(author=request.user)
+ except models.DraftQuestion.DoesNotExist:
+ draft = models.DraftQuestion()
+
+ draft.title = title
+ draft.text = text
+ draft.tagnames = tagnames
+ draft.author = request.user
+ draft.save()
+
+
+@csrf.csrf_exempt
+@decorators.ajax_only
+@decorators.post_only
+def save_draft_answer(request):
+ """saves draft answers"""
+ #todo: allow drafts for anonymous users
+ if request.user.is_anonymous():
+ return
+
+ form = forms.DraftAnswerForm(request.POST)
+ if form.is_valid():
+ thread_id = form.cleaned_data['thread_id']
+ try:
+ thread = models.Thread.objects.get(id=thread_id)
+ except models.Thread.DoesNotExist:
+ return
+ try:
+ draft = models.DraftAnswer.objects.get(
+ thread=thread,
+ author=request.user
+ )
+ except models.DraftAnswer.DoesNotExist:
+ draft = models.DraftAnswer()
+
+ draft.author = request.user
+ draft.thread = thread
+ draft.text = form.cleaned_data.get('text', '')
+ draft.save()
+
+@decorators.get_only
+@decorators.admins_only
+def get_users_info(request):
+ """retuns list of user names and email addresses
+ of "fake" users - so that admins can post on their
+ behalf"""
+ #user_info_list = models.User.objects.filter(
+ # is_fake=True
+ # ).values_list(
+ # 'username',
+ # 'email'
+ # )
+ user_info_list = models.User.objects.values_list(
+ 'username',
+ 'email'
+ )
+
+ result_list = list()
+ for user_info in user_info_list:
+ username = user_info[0]
+ email = user_info[1]
+ result_list.append('%s|%s' % (username, email))
+
+ output = '\n'.join(result_list)
+ return HttpResponse(output, mimetype = 'text/plain')
+
+@csrf.csrf_protect
+def share_question_with_group(request):
+ form = forms.ShareQuestionForm(request.POST)
+ try:
+ if form.is_valid():
+
+ thread_id = form.cleaned_data['thread_id']
+ group_name = form.cleaned_data['recipient_name']
+
+ thread = models.Thread.objects.get(id=thread_id)
+ question_post = thread._question_post()
+
+ #get notif set before
+ sets1 = question_post.get_notify_sets(
+ mentioned_users=list(),
+ exclude_list=[request.user,]
+ )
+
+ #share the post
+ if group_name == askbot_settings.GLOBAL_GROUP_NAME:
+ thread.make_public(recursive=True)
+ else:
+ group = models.Tag.group_tags.get(name=group_name)
+ thread.add_to_groups((group,), recursive=True)
+
+ #get notif sets after
+ sets2 = question_post.get_notify_sets(
+ mentioned_users=list(),
+ exclude_list=[request.user,]
+ )
+
+ notify_sets = {
+ 'for_mentions': sets2['for_mentions'] - sets1['for_mentions'],
+ 'for_email': sets2['for_email'] - sets1['for_email'],
+ 'for_inbox': sets2['for_inbox'] - sets1['for_inbox']
+ }
+
+ question_post.issue_update_notifications(
+ updated_by=request.user,
+ notify_sets=notify_sets,
+ activity_type=const.TYPE_ACTIVITY_POST_SHARED,
+ timestamp=datetime.datetime.now()
+ )
+
+ return HttpResponseRedirect(thread.get_absolute_url())
+ except Exception:
+ error_message = _('Sorry, looks like sharing request was invalid')
+ request.user.message_set.create(message=error_message)
+ return HttpResponseRedirect(thread.get_absolute_url())
+
+@csrf.csrf_protect
+def share_question_with_user(request):
+ form = forms.ShareQuestionForm(request.POST)
+ try:
+ if form.is_valid():
+
+ thread_id = form.cleaned_data['thread_id']
+ username = form.cleaned_data['recipient_name']
+
+ thread = models.Thread.objects.get(id=thread_id)
+ user = models.User.objects.get(username=username)
+ group = user.get_personal_group()
+ thread.add_to_groups([group], recursive=True)
+ #notify the person
+ #todo: see if user could already see the post - b/f the sharing
+ notify_sets = {
+ 'for_inbox': set([user]),
+ 'for_mentions': set([user]),
+ 'for_email': set([user])
+ }
+ thread._question_post().issue_update_notifications(
+ updated_by=request.user,
+ notify_sets=notify_sets,
+ activity_type=const.TYPE_ACTIVITY_POST_SHARED,
+ timestamp=datetime.datetime.now()
+ )
+
+ return HttpResponseRedirect(thread.get_absolute_url())
+ except Exception:
+ error_message = _('Sorry, looks like sharing request was invalid')
+ request.user.message_set.create(message=error_message)
+ return HttpResponseRedirect(thread.get_absolute_url())
diff --git a/askbot/views/meta.py b/askbot/views/meta.py
index a92aec2b..e4209185 100644
--- a/askbot/views/meta.py
+++ b/askbot/views/meta.py
@@ -26,6 +26,8 @@ from askbot.utils import functions
def generic_view(request, template = None, page_class = None):
"""this may be not necessary, since it is just a rewrite of render_into_skin"""
+ if request is None: # a plug for strange import errors in django startup
+ return render_to_response('django_error.html')
return render_into_skin(template, {'page_class': page_class}, request)
def config_variable(request, variable_name = None, mimetype = None):
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index f1e14cc3..8d63ba51 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -85,7 +85,6 @@ def questions(request, **kwargs):
qs, meta_data = models.Thread.objects.run_advanced_search(
request_user=request.user, search_state=search_state
)
-
if meta_data['non_existing_tags']:
search_state = search_state.remove_tags(meta_data['non_existing_tags'])
@@ -470,7 +469,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
user_post_id_list = [
id for id in post_to_author if post_to_author[id] == request.user.id
]
-
+
#resolve page number and comment number for permalinks
show_comment_position = None
if show_comment:
@@ -535,18 +534,37 @@ def question(request, id):#refactor - long subroutine. display question body, an
is_cacheable = False
elif show_comment_position > askbot_settings.MAX_COMMENTS_TO_SHOW:
is_cacheable = False
-
- answer_form = AnswerForm(
- initial = {
- 'wiki': question_post.wiki and askbot_settings.WIKI_ON,
- 'email_notify': thread.is_followed_by(request.user)
- }
- )
+
+ initial = {
+ 'wiki': question_post.wiki and askbot_settings.WIKI_ON,
+ 'email_notify': thread.is_followed_by(request.user)
+ }
+ #maybe load draft
+ if request.user.is_authenticated():
+ #todo: refactor into methor on thread
+ drafts = models.DraftAnswer.objects.filter(
+ author=request.user,
+ thread=thread
+ )
+ if drafts.count() > 0:
+ initial['text'] = drafts[0].text
+
+ answer_form = AnswerForm(initial)
user_can_post_comment = (
request.user.is_authenticated() and request.user.can_post_comment()
)
+ user_already_gave_answer = False
+ previous_answer = None
+ if request.user.is_authenticated():
+ if askbot_settings.LIMIT_ONE_ANSWER_PER_USER:
+ for answer in answers:
+ if answer.author == request.user:
+ user_already_gave_answer = True
+ previous_answer = answer
+ break
+
data = {
'is_cacheable': False,#is_cacheable, #temporary, until invalidation fix
'long_time': const.LONG_TIME,#"forever" caching
@@ -561,6 +579,8 @@ def question(request, id):#refactor - long subroutine. display question body, an
'user_votes': user_votes,
'user_post_id_list': user_post_id_list,
'user_can_post_comment': user_can_post_comment,#in general
+ 'user_already_gave_answer': user_already_gave_answer,
+ 'previous_answer': previous_answer,
'tab_id' : answer_sort_method,
'favorited' : favorited,
'similar_threads' : thread.get_similar_threads(),
diff --git a/askbot/views/users.py b/askbot/views/users.py
index c15e665b..c0a3a295 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -37,6 +37,7 @@ from askbot.conf import settings as askbot_settings
from askbot import models
from askbot import exceptions
from askbot.models.badges import award_badges_signal
+from askbot.models.tag import get_global_group
from askbot.skins.loaders import render_into_skin
from askbot.templatetags import extra_tags
from askbot.search.state_manager import SearchState
@@ -56,7 +57,7 @@ def owner_or_moderator_required(f):
return f(request, profile_owner, context)
return wrapped_func
-def show_users(request, by_group = False, group_id = None, group_slug = None):
+def show_users(request, by_group=False, group_id=None, group_slug=None):
"""Users view, including listing of users by group"""
users = models.User.objects.exclude(status = 'b')
group = None
@@ -81,7 +82,7 @@ def show_users(request, by_group = False, group_id = None, group_slug = None):
except models.Tag.DoesNotExist:
raise Http404
if group_slug == slugify(group.name):
- users = models.User.objects.filter(
+ users = users.filter(
group_memberships__group__id = group_id
)
if request.user.is_authenticated():
@@ -99,7 +100,6 @@ def show_users(request, by_group = False, group_id = None, group_slug = None):
}
)
return HttpResponseRedirect(group_page_url)
-
is_paginated = True
@@ -443,6 +443,9 @@ def user_stats(request, user, context):
badges.sort(key=operator.itemgetter(1), reverse=True)
user_groups = models.Tag.group_tags.get_for_user(user = user)
+ user_groups = user_groups.exclude(name__startswith='_internal_')
+ global_group = get_global_group()
+ user_groups = user_groups.exclude(name=global_group.name)
if request.user == user:
groups_membership_info = user.get_groups_membership_info(user_groups)
@@ -994,6 +997,8 @@ def groups(request, id = None, slug = None):
user = request.user
)
+ groups = groups.exclude(name__startswith='_internal_')
+
groups = groups.select_related('group_profile')
user_can_add_groups = request.user.is_authenticated() and \
diff --git a/askbot/views/widgets.py b/askbot/views/widgets.py
new file mode 100644
index 00000000..77eb7eb5
--- /dev/null
+++ b/askbot/views/widgets.py
@@ -0,0 +1,74 @@
+from datetime import datetime
+
+from django.core import exceptions
+from django.utils import simplejson
+from django.shortcuts import redirect
+from django.views.decorators import csrf
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+
+from django.contrib.auth.decorators import login_required
+
+from askbot.skins.loaders import render_into_skin
+from askbot import models
+from askbot import forms
+
+@csrf.csrf_protect
+def ask_widget(request):
+
+ def post_question(data, request):
+ thread = models.Thread.objects.create_new(**data_dict)
+ question = thread._question_post()
+ request.session['widget_question_url'] = question.get_absolute_url()
+ return question
+
+
+ if request.method == "POST":
+ form = forms.AskWidgetForm(request.POST)
+ if form.is_valid():
+ ask_anonymously = form.cleaned_data['ask_anonymously']
+ title = form.cleaned_data['title']
+ data_dict = {
+ 'title': title,
+ 'added_at': datetime.now(),
+ 'wiki': False,
+ 'text': ' ',
+ 'tagnames': '',
+ 'is_anonymous': ask_anonymously
+ }
+ if request.user.is_authenticated():
+ data_dict['author'] = request.user
+ question = post_question(data_dict, request)
+ return redirect('ask_by_widget_complete')
+ else:
+ request.session['widget_question'] = data_dict
+ next_url = '%s?next=%s' % (reverse('widget_signin'), reverse('ask_by_widget'))
+ return redirect(next_url)
+ else:
+ if 'widget_question' in request.session and \
+ request.GET.get('action', 'post-after-login'):
+ if request.user.is_authenticated():
+ data_dict = request.session['widget_question']
+ data_dict['author'] = request.user
+ question = post_question(request.session['widget_question'], request)
+ del request.session['widget_question']
+ return redirect('ask_by_widget_complete')
+ else:
+ #FIXME: this redirect is temporal need to create the correct view
+ next_url = '%s?next=%s' % (reverse('widget_signin'), reverse('ask_by_widget'))
+ return redirect(next_url)
+
+ form = forms.AskWidgetForm()
+ data = {'form': form}
+ return render_into_skin('ask_by_widget.html', data, request)
+
+@login_required
+def ask_widget_complete(request):
+ question_url = request.session.get('widget_question_url')
+ if question_url:
+ del request.session['widget_question_url']
+ else:
+ question_url = '#'
+
+ data = {'question_url': question_url}
+ return render_into_skin('ask_widget_complete.html', data, request)
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index 29ad229b..a9f1c4a0 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -15,6 +15,7 @@ import time
import urlparse
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
from django.utils import simplejson
from django.utils.html import strip_tags, escape
@@ -24,6 +25,7 @@ from django.core import exceptions
from django.conf import settings
from django.views.decorators import csrf
+from askbot import exceptions as askbot_exceptions
from askbot import forms
from askbot import models
from askbot.conf import settings as askbot_settings
@@ -217,17 +219,25 @@ def ask(request):#view used to ask a new question
text = form.cleaned_data['text']
ask_anonymously = form.cleaned_data['ask_anonymously']
post_privately = form.cleaned_data['post_privately']
+ group_id = form.cleaned_data.get('group_id', None)
if request.user.is_authenticated():
+ drafts = models.DraftQuestion.objects.filter(
+ author=request.user
+ )
+ drafts.delete()
+
+ user = form.get_post_user(request.user)
try:
- question = request.user.post_question(
+ question = user.post_question(
title = title,
body_text = text,
tags = tagnames,
wiki = wiki,
is_anonymous = ask_anonymously,
is_private = post_privately,
- timestamp = timestamp
+ timestamp = timestamp,
+ group_id = group_id
)
return HttpResponseRedirect(question.get_absolute_url())
except exceptions.PermissionDenied, e:
@@ -254,15 +264,32 @@ def ask(request):#view used to ask a new question
if request.method == 'GET':
form = forms.AskForm()
+ draft_title = ''
+ draft_text = ''
+ draft_tagnames = ''
+ if request.user.is_authenticated():
+ drafts = models.DraftQuestion.objects.filter(author=request.user)
+ if len(drafts) > 0:
+ draft = drafts[0]
+ draft_title = draft.title
+ draft_text = draft.text
+ draft_tagnames = draft.tagnames
+
form.initial = {
- 'title': request.REQUEST.get('title', ''),
- 'text': request.REQUEST.get('text', ''),
- 'tags': request.REQUEST.get('tags', ''),
+ 'title': request.REQUEST.get('title', draft_title),
+ 'text': request.REQUEST.get('text', draft_text),
+ 'tags': request.REQUEST.get('tags', draft_tagnames),
'wiki': request.REQUEST.get('wiki', False),
'ask_anonymously': request.REQUEST.get('ask_anonymousy', False),
'post_privately': request.REQUEST.get('post_privately', False)
}
-
+ if 'group_id' in request.REQUEST:
+ try:
+ group_id = int(request.GET.get('group_id', None))
+ form.initial['group_id'] = group_id
+ except Exception:
+ pass
+
data = {
'active_tab': 'ask',
'page_class': 'ask-page',
@@ -386,7 +413,9 @@ def edit_question(request, id):
is_wiki = form.cleaned_data.get('wiki', question.wiki)
post_privately = form.cleaned_data['post_privately']
- request.user.edit_question(
+ user = form.get_post_user(request.user)
+
+ user.edit_question(
question = question,
title = form.cleaned_data['title'],
body_text = form.cleaned_data['text'],
@@ -462,7 +491,8 @@ def edit_answer(request, id):
if form.is_valid():
if form.has_changed():
- request.user.edit_answer(
+ user = form.get_post_user(request.user)
+ user.edit_answer(
answer = answer,
body_text = form.cleaned_data['text'],
revision_comment = form.cleaned_data['summary'],
@@ -510,10 +540,18 @@ def answer(request, id):#process a new answer
update_time = datetime.datetime.now()
if request.user.is_authenticated():
+ drafts = models.DraftAnswer.objects.filter(
+ author=request.user,
+ thread=question.thread
+ )
+ drafts.delete()
try:
follow = form.cleaned_data['email_notify']
is_private = form.cleaned_data['post_privately']
- answer = request.user.post_answer(
+
+ user = form.get_post_user(request.user)
+
+ answer = user.post_answer(
question = question,
body_text = text,
follow = follow,
@@ -522,6 +560,10 @@ def answer(request, id):#process a new answer
timestamp = update_time,
)
return HttpResponseRedirect(answer.get_absolute_url())
+ except askbot_exceptions.AnswerAlreadyGiven, e:
+ request.user.message_set.create(message = unicode(e))
+ answer = question.thread.get_answers_by_user(request.user)[0]
+ return HttpResponseRedirect(answer.get_absolute_url())
except exceptions.PermissionDenied, e:
request.user.message_set.create(message = unicode(e))
else: