summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.gitignore1
-rw-r--r--askbot/__init__.py4
-rw-r--r--askbot/conf/minimum_reputation.py22
-rw-r--r--askbot/conf/social_sharing.py11
-rw-r--r--askbot/const/message_keys.py5
-rw-r--r--askbot/deployment/__init__.py13
-rw-r--r--askbot/deps/django_authopenid/views.py21
-rw-r--r--askbot/doc/source/changelog.rst9
-rw-r--r--askbot/doc/source/contributors.rst5
-rw-r--r--askbot/feed.py24
-rw-r--r--askbot/forms.py51
-rw-r--r--askbot/importers/stackexchange/management/commands/load_stackexchange.py138
-rw-r--r--askbot/locale/ca/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/de/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/en/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/es/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/fi/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/fr/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/hi/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/ja/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/pt_BR/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/ru/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/tr/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/locale/zh_CN/LC_MESSAGES/djangojs.po2
-rw-r--r--askbot/mail/__init__.py74
-rw-r--r--askbot/mail/lamson_handlers.py17
-rw-r--r--askbot/mail/messages.py4
-rw-r--r--askbot/mail/parsing.py85
-rw-r--r--askbot/management/commands/send_accept_answer_reminders.py4
-rw-r--r--askbot/management/commands/send_unanswered_question_reminders.py4
-rw-r--r--askbot/media/js/post.js3
-rw-r--r--askbot/media/js/tinymce/plugins/preview/preview.html1
-rw-r--r--askbot/media/style/style.css58
-rw-r--r--askbot/media/style/style.less90
-rw-r--r--askbot/media/style/tinymce/content.css76
-rw-r--r--askbot/middleware/forum_mode.py12
-rw-r--r--askbot/migrations/0155_remove_unused_internal_tags.py387
-rw-r--r--askbot/migrations/0156_add_message_model_in_new_django.py386
-rw-r--r--askbot/models/__init__.py79
-rw-r--r--askbot/models/message.py25
-rw-r--r--askbot/models/post.py22
-rw-r--r--askbot/models/question.py49
-rw-r--r--askbot/models/repute.py2
-rw-r--r--askbot/models/tag.py1
-rw-r--r--askbot/patches/__init__.py3
-rw-r--r--askbot/patches/django_patches.py13
-rw-r--r--askbot/setup_templates/settings.py6
-rw-r--r--askbot/setup_templates/settings.py.mustache25
-rw-r--r--askbot/skins/loaders.py52
-rw-r--r--askbot/startup_procedures.py110
-rw-r--r--askbot/tasks.py4
-rw-r--r--askbot/templates/404.html49
-rw-r--r--askbot/templates/404.jinja.html44
-rw-r--r--askbot/templates/500.html (renamed from askbot/templates/500.jinja.html)0
-rw-r--r--askbot/templates/answer_edit.html5
-rw-r--r--askbot/templates/main_page/tab_bar.html2
-rw-r--r--askbot/templates/question/new_answer_form.html5
-rw-r--r--askbot/templates/question/sidebar.html2
-rw-r--r--askbot/templates/question_edit.html5
-rw-r--r--askbot/templates/widgets/ask_form.html5
-rw-r--r--askbot/templatetags/extra_tags.py8
-rw-r--r--askbot/tests/email_alert_tests.py6
-rw-r--r--askbot/tests/email_parsing_tests.py41
-rw-r--r--askbot/tests/form_tests.py56
-rw-r--r--askbot/tests/page_load_tests.py16
-rw-r--r--askbot/tests/post_model_tests.py2
-rw-r--r--askbot/tests/reply_by_email_tests.py4
-rw-r--r--askbot/tests/utils_tests.py52
-rw-r--r--askbot/urls.py13
-rw-r--r--askbot/utils/console.py32
-rw-r--r--askbot/utils/html.py39
-rw-r--r--askbot/views/avatar_views.py14
-rw-r--r--askbot/views/commands.py15
-rw-r--r--askbot/views/meta.py51
-rw-r--r--askbot/views/readers.py52
-rw-r--r--askbot/views/users.py36
-rw-r--r--askbot/views/widgets.py68
-rw-r--r--askbot/views/writers.py57
-rw-r--r--group_messaging/__init__.py3
-rw-r--r--group_messaging/models.py9
-rw-r--r--group_messaging/views.py2
81 files changed, 2090 insertions, 523 deletions
diff --git a/.gitignore b/.gitignore
index 425fd569..2f360b2a 100755
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ nbproject
settings_local.py
settings.py
.idea
+.coverage
*.iml
lint
env
diff --git a/askbot/__init__.py b/askbot/__init__.py
index 3107c689..c77c33d1 100644
--- a/askbot/__init__.py
+++ b/askbot/__init__.py
@@ -7,13 +7,13 @@ basic actions on behalf of the forum application
import os
import platform
-VERSION = (0, 7, 43)
+VERSION = (0, 7, 44)
#keys are module names used by python imports,
#values - the package qualifier to use for pip
REQUIREMENTS = {
'akismet': 'akismet',
- 'django': 'django==1.3.1',
+ 'django': 'django>=1.3.1',
'jinja2': 'Jinja2',
'coffin': 'Coffin>=0.3',
'south': 'South>=0.7.1',
diff --git a/askbot/conf/minimum_reputation.py b/askbot/conf/minimum_reputation.py
index 152a2079..fee880ac 100644
--- a/askbot/conf/minimum_reputation.py
+++ b/askbot/conf/minimum_reputation.py
@@ -107,6 +107,28 @@ settings.register(
settings.register(
livesettings.IntegerValue(
MIN_REP,
+ 'MIN_REP_TO_INSERT_LINK',
+ default=30,
+ description=_('Insert clickable links')
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ MIN_REP,
+ 'MIN_REP_TO_SUGGEST_LINK',
+ default=10,
+ description=_('Insert link suggestions as plain text'),
+ help_text=_(
+ 'This value should be smaller than that for "insert clickable links". '
+ 'This setting should stop link-spamming by newly registered users.'
+ )
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ MIN_REP,
'MIN_REP_TO_CLOSE_OWN_QUESTIONS',
default=25,
description=_('Close own questions'),
diff --git a/askbot/conf/social_sharing.py b/askbot/conf/social_sharing.py
index 9a0f8ad4..c7c29151 100644
--- a/askbot/conf/social_sharing.py
+++ b/askbot/conf/social_sharing.py
@@ -8,13 +8,22 @@ from django.utils.translation import ugettext as _
SOCIAL_SHARING = ConfigurationGroup(
'SOCIAL_SHARING',
- _('Sharing content on social networks'),
+ _('Content sharing'),
super_group = EXTERNAL_SERVICES
)
settings.register(
BooleanValue(
SOCIAL_SHARING,
+ 'RSS_ENABLED',
+ default=True,
+ description=_('Check to enable RSS feeds')
+ )
+)
+
+settings.register(
+ BooleanValue(
+ SOCIAL_SHARING,
'ENABLE_SHARING_TWITTER',
default=True,
description=_('Check to enable sharing of questions on Twitter')
diff --git a/askbot/const/message_keys.py b/askbot/const/message_keys.py
index 74fcbf7f..291381cb 100644
--- a/askbot/const/message_keys.py
+++ b/askbot/const/message_keys.py
@@ -54,7 +54,4 @@ def get_i18n_message(key):
'Please contact the forum administrator to reach a resolution.'
)
}
- if key in messages:
- return messages.get(key)
- else:
- raise KeyError(key)
+ return messages[key]
diff --git a/askbot/deployment/__init__.py b/askbot/deployment/__init__.py
index 8832cd01..59926cf4 100644
--- a/askbot/deployment/__init__.py
+++ b/askbot/deployment/__init__.py
@@ -127,12 +127,23 @@ def deploy_askbot(directory, options):
path_utils.create_path(directory)
- if django.VERSION[0] == 1 and django.VERSION[1] < 3:
+ if django.VERSION[0] > 1:
+ raise Exception(
+ 'Django framework with major version > 1 is not supported'
+ )
+
+ if django.VERSION[1] < 3:
#force people install the django-staticfiles app
context['staticfiles_app'] = ''
else:
context['staticfiles_app'] = "'django.contrib.staticfiles',"
+ if django.VERSION[1] <=3:
+ auth_context_processor = 'django.core.context_processors.auth'
+ else:
+ auth_context_processor = 'django.contrib.auth.context_processors.auth'
+ context['auth_context_processor'] = auth_context_processor
+
path_utils.deploy_into(
directory,
new_project = create_new_project,
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index 05e0cdc5..9e383b49 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -41,6 +41,8 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
from django.forms.util import ErrorList
+from django.shortcuts import render
+from django.template.loader import get_template
from django.views.decorators import csrf
from django.utils.encoding import smart_unicode
from django.utils.html import escape
@@ -48,7 +50,6 @@ from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from askbot.mail import send_mail
from recaptcha_works.decorators import fix_recaptcha_remote_ip
-from askbot.skins.loaders import render_into_skin, get_template
from askbot.deps.django_authopenid.ldap_auth import ldap_create_user
from askbot.deps.django_authopenid.ldap_auth import ldap_authenticate
from askbot.utils.loading import load_module
@@ -169,7 +170,7 @@ def logout_page(request):
'page_class': 'meta',
'have_federated_login_methods': util.have_enabled_federated_login_methods()
}
- return render_into_skin('authopenid/logout.html', data, request)
+ return render(request, 'authopenid/logout.html', data)
def get_url_host(request):
if request.is_secure():
@@ -721,7 +722,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(template_name, data, request)
+ return render(request, template_name, data)
@login_required
def delete_login_method(request):
@@ -993,7 +994,7 @@ def register(request, login_provider_name=None, user_identifier=None):
'login_type':'openid',
'gravatar_faq_url':reverse('faq') + '#gravatar',
}
- return render_into_skin('authopenid/complete.html', data, request)
+ return render(request, 'authopenid/complete.html', data)
def signin_failure(request, message):
"""
@@ -1052,7 +1053,7 @@ def verify_email_and_register(request):
return HttpResponseRedirect(reverse('index'))
else:
data = {'page_class': 'validate-email-page'}
- return render_into_skin('authopenid/verify_email.html', data, request)
+ return render(request, 'authopenid/verify_email.html', data)
@not_authenticated
@decorators.valid_password_login_provider_required
@@ -1138,10 +1139,10 @@ def signup_with_password(request):
'minor_login_providers': minor_login_providers.values(),
'login_form': login_form
}
- return render_into_skin(
+ return render(
+ request,
'authopenid/signup_with_password.html',
- context_data,
- request
+ context_data
)
#what if request is not posted?
@@ -1201,7 +1202,7 @@ def send_email_key(email, key, handler_url_name='user_account_recover'):
'?validation_code=' + key
}
template = get_template('authopenid/email_validation.html')
- message = template.render(data)
+ message = template.render(data)#todo: inject language preference
send_mail(subject, message, django_settings.DEFAULT_FROM_EMAIL, [email])
def send_user_new_email_key(user):
@@ -1273,4 +1274,4 @@ def validation_email_sent(request):
'change_email_url': reverse('user_changeemail'),
'action_type': 'validate'
}
- return render_into_skin('authopenid/changeemail.html', data, request)
+ return render(request, 'authopenid/changeemail.html', data)
diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst
index a971d9d6..ca776770 100644
--- a/askbot/doc/source/changelog.rst
+++ b/askbot/doc/source/changelog.rst
@@ -3,6 +3,15 @@ Changes in Askbot
Development version
-------------------
+* Feedback sender's email is added to the Reply-To header
+ in the feedback form (Evgeny)
+
+0.7.44 (Nov 11, 2012)
+-------------------
+* Support for django 1.4 (Adolfo)
+* Added option to enable/disable rss feeds (Evgeny)
+* Added minimum reputation to insert links and hotlinked images (Evgeny)
+* Added minimum reputation to suggest links as plain text (Evgeny)
* Added support of Haystack for search (Adolfo)
* Added minimum reputation setting to accept any answer as correct (Evgeny)
* Added "VIP" option to groups - if checked, all posts belong to the group and users of that group in the future will be able to moderate those posts. Moderation features for VIP group are in progress (Evgeny)
diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst
index a795d84e..71d942bb 100644
--- a/askbot/doc/source/contributors.rst
+++ b/askbot/doc/source/contributors.rst
@@ -6,8 +6,8 @@ This is the list of contributors to the code of Askbot project.
The list is probably incomplete, apologies for any omissions.
Thanks for all your help
-Programming and documentation
------------------------------
+Programming, bug fixes and documentation
+----------------------------------------
* Mike Chen & Sailing Cai - original authors of CNPROG forum
* Evgeny Fadeev - founder of askbot
* `Adolfo Fitoria <http://fitoria.net>`_
@@ -42,6 +42,7 @@ Programming and documentation
* `Alexandros <https://github.com/alexandros-z>`_
* `Paul Backhouse <https://github.com/powlo>`_
* `jtrain <https://github.com/jtrain>`_
+* Niki Rocco
Translations
------------
diff --git a/askbot/feed.py b/askbot/feed.py
index 285bb452..0e1102b1 100644
--- a/askbot/feed.py
+++ b/askbot/feed.py
@@ -12,12 +12,14 @@
"""
#!/usr/bin/env python
#encoding:utf-8
+from django.contrib.syndication.views import Feed
+
import itertools
-from django.contrib.syndication.feeds import Feed
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist
+from django.http import Http404
from askbot.models import Post
from askbot.conf import settings as askbot_settings
@@ -36,10 +38,12 @@ class RssIndividualQuestionFeed(Feed):
def description(self):
return askbot_settings.APP_DESCRIPTION
- def get_object(self, bits):
- if len(bits) != 1:
- raise ObjectDoesNotExist
- return Post.objects.get_questions().get(id__exact = bits[0])
+ def get_object(self, request, pk):
+ if askbot_settings.RSS_ENABLED is False:
+ raise Http404
+ #hack to get the request object into the Feed class
+ self.request = request
+ return Post.objects.get_questions().get(id__exact = pk)
def item_link(self, item):
"""get full url to the item
@@ -67,6 +71,7 @@ class RssIndividualQuestionFeed(Feed):
)
answers = Post.objects.get_answers().filter(thread = item.thread)
+
for answer in answers:
chain_elements.append([answer,])
chain_elements.append(
@@ -134,7 +139,7 @@ class RssLastestQuestionsFeed(Feed):
"""returns url without the slug
because the slug can change
"""
- return askbot_settings.APP_URL + item.get_absolute_url(no_slug = True)
+ return self.link() + item.get_absolute_url(no_slug = True)
def item_description(self, item):
"""returns the description for the item
@@ -144,6 +149,8 @@ class RssLastestQuestionsFeed(Feed):
def items(self, item):
"""get questions for the feed
"""
+ if askbot_settings.RSS_ENABLED is False:
+ raise Http404
#initial filtering
qs = Post.objects.get_questions().filter(deleted=False)
@@ -164,7 +171,10 @@ class RssLastestQuestionsFeed(Feed):
return qs.order_by('-thread__last_activity_at')[:30]
-
+ #hack to get the request object into the Feed class
+ def get_feed(self, obj, request):
+ self.request = request
+ return super(RssLastestQuestionsFeed, self).get_feed(obj, request)
def main():
"""main function for use as a script
diff --git a/askbot/forms.py b/askbot/forms.py
index 1c5bc3d6..ed47e20e 100644
--- a/askbot/forms.py
+++ b/askbot/forms.py
@@ -4,6 +4,7 @@ import re
from django import forms
from askbot import const
from askbot.const import message_keys
+from django.core.exceptions import PermissionDenied
from django.forms.util import ErrorList
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy, string_concat
@@ -273,6 +274,11 @@ class EditorField(forms.CharField):
min_length = 10 # sentinel default value
def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user', None)
+ if user is None:
+ raise ValueError('user parameter is required')
+ self.user = user
+
editor_attrs = kwargs.pop('editor_attrs', {})
super(EditorField, self).__init__(*args, **kwargs)
self.required = True
@@ -295,6 +301,17 @@ class EditorField(forms.CharField):
self.min_length
) % self.min_length
raise forms.ValidationError(msg)
+
+ if self.user.is_anonymous():
+ #we postpone this validation if user is posting
+ #before logging in, up until publishing the post
+ return value
+
+ try:
+ self.user.assert_can_post_text(value)
+ except PermissionDenied, e:
+ raise forms.ValidationError(unicode(e))
+
return value
@@ -302,7 +319,10 @@ class QuestionEditorField(EditorField):
"""Editor field for the questions"""
def __init__(self, *args, **kwargs):
- super(QuestionEditorField, self).__init__(*args, **kwargs)
+ user = kwargs.pop('user', None)
+ super(QuestionEditorField, self).__init__(
+ user=user, *args, **kwargs
+ )
self.length_error_template_singular = \
'question body must be > %d character'
self.length_error_template_plural = \
@@ -477,10 +497,12 @@ class EditorForm(forms.Form):
the field must be created dynamically, so it's added
in the __init__() function"""
- def __init__(self, editor_attrs=None):
+ def __init__(self, user=None, editor_attrs=None):
super(EditorForm, self).__init__()
editor_attrs = editor_attrs or {}
- self.fields['editor'] = EditorField(editor_attrs=editor_attrs)
+ self.fields['editor'] = EditorField(
+ user=user, editor_attrs=editor_attrs
+ )
class DumpUploadForm(forms.Form):
@@ -775,6 +797,7 @@ class PostPrivatelyForm(forms.Form, FormWithHideableFields):
def allows_post_privately(self):
user = self._user
return (
+ askbot_settings.GROUPS_ENABLED and \
user and user.is_authenticated() and \
user.can_make_group_private_posts()
)
@@ -897,9 +920,10 @@ class AskForm(PostAsSomeoneForm, PostPrivatelyForm):
)
def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user', None)
super(AskForm, self).__init__(*args, **kwargs)
#it's important that this field is set up dynamically
- self.fields['text'] = QuestionEditorField()
+ self.fields['text'] = QuestionEditorField(user=user)
#hide ask_anonymously field
if askbot_settings.ALLOW_ASK_ANONYMOUSLY is False:
self.hide_field('ask_anonymously')
@@ -932,11 +956,12 @@ class AskWidgetForm(forms.Form, FormWithHideableFields):
)
def __init__(self, include_text=True, *args, **kwargs):
+ user = kwargs.pop('user', None)
super(AskWidgetForm, self).__init__(*args, **kwargs)
#hide ask_anonymously field
if not askbot_settings.ALLOW_ASK_ANONYMOUSLY:
self.hide_field('ask_anonymously')
- self.fields['text'] = QuestionEditorField()
+ self.fields['text'] = QuestionEditorField(user=user)
if not include_text:
self.hide_field('text')
#hack to make it validate
@@ -1020,7 +1045,11 @@ class AskByEmailForm(forms.Form):
'required': ASK_BY_EMAIL_SUBJECT_HELP
}
)
- body_text = QuestionEditorField()
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user', None)
+ super(AskByEmailForm, self).__init__(*args, **kwargs)
+ self.fields['body_text'] = QuestionEditorField(user=user)
def clean_sender(self):
"""Cleans the :attr:`~askbot.forms.AskByEmail.sender` attribute
@@ -1074,7 +1103,6 @@ class AskByEmailForm(forms.Form):
class AnswerForm(PostAsSomeoneForm, PostPrivatelyForm):
- text = AnswerEditorField()
wiki = WikiField()
openid = forms.CharField(
required=False, max_length=255,
@@ -1084,7 +1112,7 @@ class AnswerForm(PostAsSomeoneForm, PostPrivatelyForm):
def __init__(self, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
- self.fields['text'] = AnswerEditorField()
+ self.fields['text'] = AnswerEditorField(user=kwargs['user'])
self.fields['email_notify'].widget.attrs['id'] = \
'question-subscribe-updates'
@@ -1169,11 +1197,11 @@ class EditQuestionForm(PostAsSomeoneForm, PostPrivatelyForm):
def __init__(self, *args, **kwargs):
"""populate EditQuestionForm with initial data"""
self.question = kwargs.pop('question')
- self.user = kwargs['user']#preserve for superclass
+ self.user = kwargs.pop('user')#preserve for superclass
revision = kwargs.pop('revision')
super(EditQuestionForm, self).__init__(*args, **kwargs)
#it is important to add this field dynamically
- self.fields['text'] = QuestionEditorField()
+ self.fields['text'] = QuestionEditorField(user=self.user)
self.fields['title'].initial = revision.title
self.fields['text'].initial = revision.text
self.fields['tags'].initial = revision.tagnames
@@ -1275,9 +1303,10 @@ class EditAnswerForm(PostAsSomeoneForm, PostPrivatelyForm):
def __init__(self, answer, revision, *args, **kwargs):
self.answer = answer
+ user = kwargs.pop('user', None)
super(EditAnswerForm, self).__init__(*args, **kwargs)
#it is important to add this field dynamically
- self.fields['text'] = AnswerEditorField()
+ self.fields['text'] = AnswerEditorField(user=user)
self.fields['text'].initial = revision.text
self.fields['wiki'].initial = answer.wiki
diff --git a/askbot/importers/stackexchange/management/commands/load_stackexchange.py b/askbot/importers/stackexchange/management/commands/load_stackexchange.py
index ff17a523..a021b62d 100644
--- a/askbot/importers/stackexchange/management/commands/load_stackexchange.py
+++ b/askbot/importers/stackexchange/management/commands/load_stackexchange.py
@@ -1,11 +1,12 @@
#todo: http://stackoverflow.com/questions/837828/how-to-use-a-slug-in-django
-DEBUGME = False
+DEBUGME = False
import os
import re
import sys
from unidecode import unidecode
import zipfile
from datetime import datetime
+from django.conf import settings as django_settings
from django.core.management.base import BaseCommand, CommandError
import askbot.importers.stackexchange.parse_models as se_parser
from xml.etree import ElementTree as et
@@ -17,11 +18,18 @@ import askbot.deps.django_authopenid.models as askbot_openid
import askbot.importers.stackexchange.models as se
from askbot.forms import EditUserEmailFeedsForm
from askbot.conf import settings as askbot_settings
-from django.contrib.auth.models import Message as DjangoMessage
+
+try:
+ from django.contrib.auth.models import Message as DjangoMessage
+except ImportError:
+ from askbot.models.message import Message as DjangoMessage
+
from django.utils.translation import ugettext as _
+from askbot.utils.console import ProgressBar
from askbot.utils.slug import slugify
from askbot.models.badges import award_badges_signal, award_badges
from askbot.importers.stackexchange.management import is_ready as importer_is_ready
+from optparse import make_option
#from markdown2 import Markdown
#markdowner = Markdown(html4tags=True)
@@ -30,7 +38,8 @@ if DEBUGME == True:
from askbot.utils import dummy_transaction as transaction
HEAP = hpy()
else:
- from django.db import transaction
+ #from django.db import transaction
+ from askbot.utils import dummy_transaction as transaction
xml_read_order = (
'VoteTypes','UserTypes','Users','Users2Votes',
@@ -154,12 +163,12 @@ class X(object):#
#or use database to store these associations
try:
if isinstance(se_post, se.PostComment):
- return askbot.Comment.objects.get(id=COMMENT[se_post.id].id)
+ return askbot.Post.objects.get(id=COMMENT[se_post.id].id)
post_type = se_post.post_type.name
if post_type == 'Question':
- return askbot.Question.objects.get(id=QUESTION[se_post.id].id)
+ return askbot.Post.objects.get(id=QUESTION[se_post.id].id)
elif post_type == 'Answer':
- return askbot.Answer.objects.get(id=ANSWER[se_post.id].id)
+ return askbot.Post.objects.get(id=ANSWER[se_post.id].id)
else:
raise Exception('unknown post type %s' % post_type)
except KeyError:
@@ -280,12 +289,46 @@ class X(object):#
return slugify(cls.badge_exceptions.get(name, name).lower())
class Command(BaseCommand):
- help = 'Loads StackExchange data from unzipped directory of XML files into the ASKBOT database'
+ help = """Loads StackExchange data from SE dump .zip file
+it may be helpful to split this procedure in two:\n
+* read the dump (with option --read-se-dump)
+* transfer data to askbot (with option --process-data)
+"""
args = 'se_dump_dir'
+ option_list = BaseCommand.option_list + (
+ make_option('-r', '--read-dump',
+ action='store_true',
+ dest='read_dump',
+ default=False,
+ help='Only read the the dump'
+ ),
+ make_option('-p', '--process-data',
+ action='store_true',
+ dest='process_data',
+ default=False,
+ help='Only process the data, assuming that the dump is loaded'
+ )
+ )
+
@transaction.commit_manually
def handle(self, *arg, **kwarg):
+ if django_settings.DEBUG:
+ raise CommandError(
+ 'Please set DEBUG to False in the settings.py to reduce '
+ 'RAM usage during the import process'
+ )
+
+ #process the command line arguments, if given
+ if kwarg['read_dump'] is False and kwarg['process_data'] is False:
+ #make them both true as a hack to simulate a condition where
+ #no flags selected means the same as both are indeed selected
+ kwarg['read_dump'] = True
+ kwarg['process_data'] = True
+
+ askbot_settings.update('LIMIT_ONE_ANSWER_PER_USER', False)
+
if not importer_is_ready():
raise CommandError(
"Looks like stackexchange tables are not yet initialized in the database.\n"
@@ -299,16 +342,23 @@ class Command(BaseCommand):
if len(arg) < 1 or not os.path.isfile(arg[0]):
raise CommandError('Error: first argument must be a zip file with the SE forum data')
- self.zipfile = self.open_dump(arg[0])
- #read the data into SE tables
- for item in xml_read_order:
- time_before = datetime.now()
- self.load_xml_file(item)
- transaction.commit()
- time_after = datetime.now()
- if DEBUGME == True:
- print time_after - time_before
- print HEAP.heap()
+ if kwarg['read_dump']:
+ self.zipfile = self.open_dump(arg[0])
+ #read the data into SE tables
+ for item in xml_read_order:
+ time_before = datetime.now()
+ self.load_xml_file(item)
+ transaction.commit()
+ time_after = datetime.now()
+ if DEBUGME == True:
+ print time_after - time_before
+ print HEAP.heap()
+
+ if kwarg['process_data'] is False:
+ #that means we just wanted to load the xml dump to
+ #do the second step in another go in order to have
+ #more ram for the transfer of data from SE to Askbot databases
+ return
#this is important so that when we clean up messages
#automatically generated by the procedures below
@@ -320,36 +370,36 @@ class Command(BaseCommand):
self.save_askbot_message_id_list()
#transfer data into ASKBOT tables
- print 'Transferring users...',
+ print 'Transferring users...'
self.transfer_users()
transaction.commit()
print 'done.'
- print 'Transferring content edits...',
+ print 'Transferring content edits...'
sys.stdout.flush()
self.transfer_question_and_answer_activity()
transaction.commit()
print 'done.'
- print 'Transferring view counts...',
+ print 'Transferring view counts...'
sys.stdout.flush()
self.transfer_question_view_counts()
transaction.commit()
print 'done.'
- print 'Transferring comments...',
+ print 'Transferring comments...'
sys.stdout.flush()
self.transfer_comments()
transaction.commit()
print 'done.'
- print 'Transferring badges and badge awards...',
+ print 'Transferring badges and badge awards...'
sys.stdout.flush()
self.transfer_badges()
transaction.commit()
print 'done.'
- print 'Transferring Q&A votes...',
+ print 'Transferring Q&A votes...'
sys.stdout.flush()
self.transfer_QA_votes()#includes favorites, accepts and flags
transaction.commit()
print 'done.'
- print 'Transferring comment votes...',
+ print 'Transferring comment votes...'
sys.stdout.flush()
self.transfer_comment_votes()
transaction.commit()
@@ -396,7 +446,8 @@ class Command(BaseCommand):
"""transfers some messages from
SE to ASKBOT
"""
- for m in se.Message.objects.all().iterator():
+ messages = se.Message.objects.all()
+ for m in ProgressBar(messages.iterator(), messages.count()):
if m.is_read:
continue
if m.user is None:
@@ -522,10 +573,7 @@ class Command(BaseCommand):
def mark_activity(self,p,u,t):
"""p,u,t - post, user, timestamp
"""
- if isinstance(p, askbot.Question):
- p.thread.set_last_activity(last_activity_by=u, last_activity_at=t)
- elif isinstance(p, askbot.Answer):
- p.question.thread.set_last_activity(last_activity_by=u, last_activity_at=t)
+ p.thread.set_last_activity(last_activity_by=u, last_activity_at=t)
def _process_post_rollback_revision_group(self, rev_group):
#todo: don't know what to do here as there were no
@@ -647,7 +695,9 @@ class Command(BaseCommand):
c_group = []
#this loop groups revisions by revision id, then calls process function
#for the revision grup (elementary revisions posted at once)
- for se_rev in se_revs.iterator():
+ message = 'Processing revisions'
+ count = se_revs.count()
+ for se_rev in ProgressBar(se_revs.iterator(), count, message):
if se_rev.revision_guid == c_guid:
c_group.append(se_rev)
else:
@@ -660,7 +710,8 @@ class Command(BaseCommand):
self._process_post_revision_group(c_group)
def transfer_comments(self):
- for se_c in se.PostComment.objects.all().iterator():
+ comments = se.PostComment.objects.all()
+ for se_c in ProgressBar(comments.iterator(), comments.count()):
if se_c.deletion_date:
print 'Warning deleted comment %d dropped' % se_c.id
sys.stdout.flush()
@@ -683,7 +734,9 @@ class Command(BaseCommand):
def _collect_missing_badges(self):
self._missing_badges = {}
- for se_b in se.Badge.objects.all():
+ badges = se.Badge.objects.all()
+ message = 'Collecting missing badges'
+ for se_b in ProgressBar(badges.iterator(), badges.count(), message):
name = X.get_badge_name(se_b.name)
try:
#todo: query badge from askbot.models.badges
@@ -700,7 +753,9 @@ class Command(BaseCommand):
def _award_badges(self):
#note: SE does not keep information on
#content-related badges like askbot does
- for se_a in se.User2Badge.objects.all().iterator():
+ badges = se.User2Badge.objects.all()
+ message = 'Awarding badges'
+ for se_a in ProgressBar(badges.iterator(), badges.count(), message):
if se_a.user.id == -1:
continue #skip community user
u = USER[se_a.user.id]
@@ -743,7 +798,8 @@ class Command(BaseCommand):
pass
def transfer_question_view_counts(self):
- for se_q in se.Post.objects.filter(post_type__name='Question').iterator():
+ questions = se.Post.objects.filter(post_type__name='Question')
+ for se_q in ProgressBar(questions.iterator(), questions.count()):
q = X.get_post(se_q)
if q is None:
continue
@@ -752,7 +808,8 @@ class Command(BaseCommand):
def transfer_QA_votes(self):
- for v in se.Post2Vote.objects.all().iterator():
+ votes = se.Post2Vote.objects.all()
+ for v in ProgressBar(votes.iterator(), votes.count()):
vote_type = v.vote_type.name
if not vote_type in X.vote_actions:
continue
@@ -779,7 +836,8 @@ class Command(BaseCommand):
transaction.commit()
def transfer_comment_votes(self):
- for v in se.Comment2Vote.objects.all().iterator():
+ votes = se.Comment2Vote.objects.all()
+ for v in ProgressBar(votes.iterator(), votes.count()):
vote_type = v.vote_type.name
if vote_type not in ('UpMod', 'Offensive'):
continue
@@ -822,10 +880,11 @@ class Command(BaseCommand):
xml_data = self.zipfile.read(xml_path)
tree = et.fromstring(xml_data)
- print 'loading from %s to %s' % (xml_path, table_name) ,
+ print 'loading from %s to %s' % (xml_path, table_name)
model = models.get_model('stackexchange', table_name)
i = 0
- for row in tree.findall('.//row'):
+ rows = tree.findall('.//row')
+ for row in ProgressBar(iter(rows), len(rows)):
model_entry = model()
i += 1
for col in row.getchildren():
@@ -849,7 +908,8 @@ class Command(BaseCommand):
return xml_file_basename + '.xml'
def transfer_users(self):
- for se_u in se.User.objects.all().iterator():
+ se_users = se.User.objects.all()
+ for se_u in ProgressBar(se_users.iterator(), se_users.count()):
#if se_u.id == -1:#skip the Community user
# continue
u = askbot.User()
diff --git a/askbot/locale/ca/LC_MESSAGES/djangojs.po b/askbot/locale/ca/LC_MESSAGES/djangojs.po
index f64685b0..e66e382a 100644
--- a/askbot/locale/ca/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/ca/LC_MESSAGES/djangojs.po
@@ -308,7 +308,7 @@ msgstr "pujar arxiu adjunt"
#~ msgid "cancel"
#~ msgstr "anul·lar"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "confirmar abandó del commenari"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/de/LC_MESSAGES/djangojs.po b/askbot/locale/de/LC_MESSAGES/djangojs.po
index 9d962b9e..e7837fe5 100644
--- a/askbot/locale/de/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/de/LC_MESSAGES/djangojs.po
@@ -282,7 +282,7 @@ msgstr "Bitte wähle und lade eine Datei hoch:"
#~ msgid "enter %s more characters"
#~ msgstr "please enter at least %s more characters"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "Are you sure you do not want to post this comment?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/en/LC_MESSAGES/djangojs.po b/askbot/locale/en/LC_MESSAGES/djangojs.po
index fa93edf1..733fa92b 100644
--- a/askbot/locale/en/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/en/LC_MESSAGES/djangojs.po
@@ -289,7 +289,7 @@ msgstr "Please choose and upload a file:"
#~ msgid "enter %s more characters"
#~ msgstr "please enter at least %s more characters"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "Are you sure you do not want to post this comment?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/es/LC_MESSAGES/djangojs.po b/askbot/locale/es/LC_MESSAGES/djangojs.po
index a9528c80..ca14a402 100644
--- a/askbot/locale/es/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/es/LC_MESSAGES/djangojs.po
@@ -317,7 +317,7 @@ msgstr "Por favor, selecciona y sube un archivo:"
#~ msgid "cancel"
#~ msgstr "cancelar"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "seguro que no quieres enviar este comentario?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/fi/LC_MESSAGES/djangojs.po b/askbot/locale/fi/LC_MESSAGES/djangojs.po
index 2c4ad82c..10160a4f 100644
--- a/askbot/locale/fi/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/fi/LC_MESSAGES/djangojs.po
@@ -319,7 +319,7 @@ msgstr "Valitse ja lataa tiedosto:"
#~ msgid "cancel"
#~ msgstr "peruuta"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "Oletko varma, ettet halua julkaista tätä kommenttia?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/fr/LC_MESSAGES/djangojs.po b/askbot/locale/fr/LC_MESSAGES/djangojs.po
index 1c65f20a..b8cb521d 100644
--- a/askbot/locale/fr/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/fr/LC_MESSAGES/djangojs.po
@@ -324,7 +324,7 @@ msgstr "Merci de choisir un fichier et de le télécharger vers le serveur:"
#~ msgid "cancel"
#~ msgstr "annuler"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "Etes-vous sûr de ne pas vouloir publier ce commentaire?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/hi/LC_MESSAGES/djangojs.po b/askbot/locale/hi/LC_MESSAGES/djangojs.po
index d2c85414..dc6957be 100644
--- a/askbot/locale/hi/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/hi/LC_MESSAGES/djangojs.po
@@ -318,7 +318,7 @@ msgstr "कृपया चà¥à¤¨à¥‡à¤‚ और फ़ाइल को अपलà
#~ msgid "cancel"
#~ msgstr "रदà¥à¤¦ करें"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "कà¥à¤¯à¤¾ आप सà¥à¤¨à¤¿à¤¶à¥à¤šà¤¿à¤¤ करेंगे कि आप इस टिपà¥à¤ªà¤£à¥€ को पोसà¥à¤Ÿ नहीं करना चाहते हैं?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/ja/LC_MESSAGES/djangojs.po b/askbot/locale/ja/LC_MESSAGES/djangojs.po
index 88cb4dd0..9d4d0e3a 100644
--- a/askbot/locale/ja/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/ja/LC_MESSAGES/djangojs.po
@@ -315,7 +315,7 @@ msgstr "ファイルをé¸æŠžã—ã¦ã‚¢ãƒƒãƒ—ロードã—ã¦ãã ã•ã„:"
#~ msgid "cancel"
#~ msgstr "キャンセル"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "本当ã«ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã‚’投稿ã—ãŸã„ã§ã™ã‹ï¼Ÿ"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/pt_BR/LC_MESSAGES/djangojs.po b/askbot/locale/pt_BR/LC_MESSAGES/djangojs.po
index cadb90a3..30e84f0b 100644
--- a/askbot/locale/pt_BR/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/pt_BR/LC_MESSAGES/djangojs.po
@@ -377,7 +377,7 @@ msgstr "gravar arquivo anexo"
#~ msgstr "cancelar"
# 100%
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "confirme abandonar comentário"
# 100%
diff --git a/askbot/locale/ru/LC_MESSAGES/djangojs.po b/askbot/locale/ru/LC_MESSAGES/djangojs.po
index 9b25feed..8e3fcb0a 100644
--- a/askbot/locale/ru/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/ru/LC_MESSAGES/djangojs.po
@@ -346,7 +346,7 @@ msgstr "ПожалуйÑта, выберите и загрузите файл:"
#~ msgid "cancel"
#~ msgstr "отмена"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "Ð’Ñ‹ дейÑтвительно уверены, что не хотите добавлÑÑ‚ÑŒ Ñтот комментарий?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/tr/LC_MESSAGES/djangojs.po b/askbot/locale/tr/LC_MESSAGES/djangojs.po
index 22373215..dc5d3765 100644
--- a/askbot/locale/tr/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/tr/LC_MESSAGES/djangojs.po
@@ -321,7 +321,7 @@ msgstr "Yüklenecek dosyayı seçin"
#~ msgid "cancel"
#~ msgstr "iptal"
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "Yorum yazmak istemediÄŸinize emin misiniz?"
#~ msgid "confirm delete comment"
diff --git a/askbot/locale/zh_CN/LC_MESSAGES/djangojs.po b/askbot/locale/zh_CN/LC_MESSAGES/djangojs.po
index c8775c04..23ce1708 100644
--- a/askbot/locale/zh_CN/LC_MESSAGES/djangojs.po
+++ b/askbot/locale/zh_CN/LC_MESSAGES/djangojs.po
@@ -337,7 +337,7 @@ msgstr "上传文件附件"
#~ msgstr "å–消"
# 100%
-#~ msgid "confirm abandon comment"
+#~ msgid "Are you sure you don't want to post this comment?"
#~ msgstr "确认放弃评论"
#~ msgid "confirm delete comment"
diff --git a/askbot/mail/__init__.py b/askbot/mail/__init__.py
index 74aa27e9..6e181b33 100644
--- a/askbot/mail/__init__.py
+++ b/askbot/mail/__init__.py
@@ -2,8 +2,17 @@
these automatically catch email-related exceptions
"""
import os
+import re
import smtplib
-import logging
+import sys
+from askbot import exceptions
+from askbot import const
+from askbot.conf import settings as askbot_settings
+from askbot.mail import parsing
+from askbot.utils import url_utils
+from askbot.utils.file_utils import store_file
+from askbot.utils.html import absolutize_urls
+from bs4 import BeautifulSoup
from django.core import mail
from django.conf import settings as django_settings
from django.core.exceptions import PermissionDenied
@@ -12,14 +21,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import string_concat
from django.template import Context
from django.utils.html import strip_tags
-from askbot import exceptions
-from askbot import const
-from askbot.conf import settings as askbot_settings
-from askbot.utils import url_utils
-from askbot.utils.file_utils import store_file
-from askbot.utils.html import absolutize_urls
-from bs4 import BeautifulSoup
#todo: maybe send_mail functions belong to models
#or the future API
def prefix_the_subject_line(subject):
@@ -82,16 +84,17 @@ def thread_headers(post, orig_post, update):
return headers
def clean_html_email(email_body):
- '''needs more clenup might not work for other email templates
- that does not use table layout'''
-
- remove_linejump = lambda s: s.replace('\n', '')
-
+ """returns the content part from an HTML email.
+ todo: needs more clenup might not work for other email templates
+ that do not use table layout
+ """
soup = BeautifulSoup(email_body)
- table_tds = soup.find('body')
- phrases = map(lambda s: s.strip(),
- filter(bool, table_tds.get_text().split('\n')))
-
+ body_element = soup.find('body')
+ filter_func = lambda s: bool(s.strip())
+ phrases = map(
+ lambda s: s.strip(),
+ filter(filter_func, body_element.get_text().split('\n'))
+ )
return '\n\n'.join(phrases)
def send_mail(
@@ -133,7 +136,7 @@ def send_mail(
if related_object is not None:
assert(activity_type is not None)
except Exception, error:
- logging.critical(unicode(error))
+ sys.stderr.write('\n' + unicode(error).encode('utf-8') + '\n')
if raise_on_failure == True:
raise exceptions.EmailNotSent(unicode(error))
@@ -170,7 +173,7 @@ def mail_moderators(
msg.content_subtype = 'html'
msg.send()
except smtplib.SMTPException, error:
- logging.critical(unicode(error))
+ sys.stderr.write('\n' + error.encode('utf-8') + '\n')
if raise_on_failure == True:
raise exceptions.EmailNotSent(unicode(error))
@@ -266,12 +269,13 @@ def bounce_email(
def extract_reply(text):
"""take the part above the separator
- and discard the last line above the separator"""
- if const.REPLY_SEPARATOR_REGEX.search(text):
- text = const.REPLY_SEPARATOR_REGEX.split(text)[0]
- return '\n'.join(text.splitlines(True)[:-3])
- else:
- return text
+ and discard the last line above the separator
+ ``text`` is the input text
+ """
+ return parsing.extract_reply_contents(
+ text,
+ const.REPLY_SEPARATOR_REGEX
+ )
def process_attachment(attachment):
"""will save a single
@@ -290,11 +294,11 @@ def process_attachment(attachment):
def extract_user_signature(text, reply_code):
"""extracts email signature as text trailing
the reply code"""
- striped_text = strip_tags(text)
- if reply_code in striped_text:
+ stripped_text = strip_tags(text)
+ if reply_code in stripped_text:
#extract the signature
tail = list()
- for line in reversed(striped_text.splitlines()):
+ for line in reversed(stripped_text.splitlines()):
#scan backwards from the end until the magic line
if reply_code in line:
break
@@ -307,13 +311,13 @@ def extract_user_signature(text, reply_code):
return '\n'.join(tail)
else:
- return ''
+ return None
-def process_parts(parts, reply_code = None):
- """Process parts will upload the attachments and parse out the
- body, if body is multipart. Secondly - links to attachments
- will be added to the body of the question.
+def process_parts(parts, reply_code=None):
+ """Uploads the attachments and parses out the
+ body, if body is multipart.
+ Links to attachments will be added to the body of the question.
Returns ready to post body of the message and the list
of uploaded files.
"""
@@ -362,10 +366,10 @@ def process_emailed_question(
'subject': subject,
'body_text': body_text
}
- form = AskByEmailForm(data)
+ user = User.objects.get(email__iexact=from_address)
+ form = AskByEmailForm(data, user=user)
if form.is_valid():
email_address = form.cleaned_data['email']
- user = User.objects.get(email__iexact = email_address)
if user.can_post_by_email() is False:
raise PermissionDenied(messages.insufficient_reputation(user))
diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py
index da09eec2..8bfa86f7 100644
--- a/askbot/mail/lamson_handlers.py
+++ b/askbot/mail/lamson_handlers.py
@@ -3,13 +3,13 @@ import functools
from django.core.files.uploadedfile import SimpleUploadedFile
from django.conf import settings as django_settings
from django.template import Context
+from django.template.loader import get_template
from django.utils.translation import ugettext as _
from lamson.routing import route, stateless
from lamson.server import Relay
from askbot.models import ReplyAddress, Group, Tag
from askbot import mail
from askbot.conf import settings as askbot_settings
-from askbot.skins.loaders import get_template
#we might end up needing to use something like this
#to distinguish the reply text from the quoted original message
@@ -147,13 +147,6 @@ def process_reply(func):
#here is the business part of this function
parts = get_parts(message)
- for part_type, content in parts:
- if part_type == 'body':
- print '==============================='
- print 'message :', content
- break
- else:
- continue
func(
from_address = message.From,
subject_line = message['Subject'],
@@ -173,7 +166,7 @@ def process_reply(func):
if error is not None:
template = get_template('email/reply_by_email_error.html')
- body_text = template.render(Context({'error':error}))
+ body_text = template.render(Context({'error':error}))#todo: set lang
mail.send_mail(
subject_line = "Error posting your reply",
body_text = body_text,
@@ -250,7 +243,7 @@ def VALIDATE_EMAIL(
mail.send_mail(
subject_line = _('Re: Welcome to %(site_name)s') % data,
- body_text = template.render(Context(data)),
+ body_text = template.render(Context(data)),#todo: set lang
recipient_list = [from_address,]
)
except ValueError:
@@ -281,7 +274,7 @@ def PROCESS(
#2) process body text and email signature
user = reply_address_object.user
- if signature:#if there, then it was stripped
+ if signature is not None:#if there, then it was stripped
if signature != user.email_signature:
user.email_signature = signature
else:#try to strip signature
@@ -319,6 +312,6 @@ def PROCESS(
mail.send_mail(
subject_line = _('Re: %s') % subject_line,
- body_text = template.render(Context(data)),
+ body_text = template.render(Context(data)),#todo: set lang
recipient_list = [from_address,]
)
diff --git a/askbot/mail/messages.py b/askbot/mail/messages.py
index 3ab3ff2f..0e888545 100644
--- a/askbot/mail/messages.py
+++ b/askbot/mail/messages.py
@@ -3,8 +3,8 @@ of email messages for various occasions
"""
import functools
from django.template import Context
+from django.template.loader import get_template
from askbot.conf import settings as askbot_settings
-from askbot.skins.loaders import get_template
from askbot.utils import html as html_utils
def message(template = None):
@@ -16,7 +16,7 @@ def message(template = None):
def wrapped(*args, **kwargs):
template_object = get_template(template)
data = func(*args, **kwargs)
- return template_object.render(Context(data))
+ return template_object.render(Context(data))#todo: set lang
return wrapped
return decorate
diff --git a/askbot/mail/parsing.py b/askbot/mail/parsing.py
new file mode 100644
index 00000000..31be6abb
--- /dev/null
+++ b/askbot/mail/parsing.py
@@ -0,0 +1,85 @@
+"""a module for parsing email response text
+this file is a candidate for publishing as an independent module
+"""
+import re
+import sys
+
+#Regexes for quote separators
+#add more via variables ending with _QUOTE_RE
+#These regexes do not contain any trailing:
+#* newline chars,
+#* lines starting with | or >
+#* lines consisting entirely of empty space
+#expressions are stripped of month and day names
+#to keep them simpler and make the additions of language variants
+#easier.
+GMAIL_QUOTE_RE = r'\nOn [^\n]* wrote:\Z'
+GMAIL_SECOND_QUOTE_RE = r'\n\d{4}/\d{1,2}/\d{1,2} [^\n]*\Z'
+YAHOO_QUOTE_RE = r'\n_+\n\s*From: [^\n]+\nTo: [^\n]+\nSent: [^\n]+\nSubject: [^\n]+\Z'
+KMAIL_QUOTE_RE = r'\AOn [^\n]+ you wrote:\s*\n\n'
+OUTLOOK_RTF_QUOTE_RE = r'\nSubject: [^\n]+\nFrom: [^\n]+\nTo: [^\n]+\nDate: [^\n]+\Z'
+OUTLOOK_TEXT_QUOTE_RE = r'\n_+\Z'
+
+def compile_quote_regexes():
+ regex_names = filter(
+ lambda v: v.endswith('_QUOTE_RE'),
+ globals().keys()
+ )
+ compiled_regexes = list()
+ for regex_name in regex_names:
+ regex = globals()[regex_name]
+ compiled_regexes.append(
+ re.compile(
+ regex,
+ re.MULTILINE | re.IGNORECASE
+ )
+ )
+ return compiled_regexes
+
+CLIENT_SPECIFIC_QUOTE_REGEXES = compile_quote_regexes()
+
+def strip_trailing_empties_and_quotes(text):
+ #strip empty lines and quote lines starting with | and >
+ return re.sub(r'(([\n\s\xa0])|(\n[\|>][^\n]*))*\Z', '', text)
+
+def strip_leading_empties(text):
+ return re.sub(r'\A[\n\s\xa0]*', '', text)
+
+def strip_email_client_quote_separator(text):
+ """strips email client quote separator from the responses,
+ e.g. (on such date XYZ wrote)
+
+ if one client-specific separator matches, then result
+ is immediately returned
+ """
+ for regex in CLIENT_SPECIFIC_QUOTE_REGEXES:
+ if regex.search(text):
+ return regex.sub('', text)
+ #did not find a quote separator!!! log it
+ log_message = u'\nno matching quote separator: %s\n' % text
+ sys.stderr.write(log_message.encode('utf-8'))
+ text_lines = text.splitlines(False)
+ return ''.join(text_lines[:-3])#strip 3 lines as a guess
+
+def extract_reply_contents(text, reply_separator=None):
+ """If reply_separator is given,
+ take the part above the separator.
+ After, strip the email-client-specific text
+
+ ``text`` is the input text
+ ``reply_separator`` is either a string or a regex object
+ """
+ if reply_separator:
+ if isinstance(reply_separator, basestring):
+ text = text.split(reply_separator)[0]
+ else:
+ testre = re.compile('test')
+ if type(testre) == type(reply_separator):
+ text = reply_separator.split(text)[0]
+ else:
+ raise ValueError('reply_separator must be a string or a compiled regex')
+
+ text = strip_trailing_empties_and_quotes(text)
+ text = strip_email_client_quote_separator(text)
+ text = strip_trailing_empties_and_quotes(text)
+ return strip_leading_empties(text)
diff --git a/askbot/management/commands/send_accept_answer_reminders.py b/askbot/management/commands/send_accept_answer_reminders.py
index 119d7611..2b8b0851 100644
--- a/askbot/management/commands/send_accept_answer_reminders.py
+++ b/askbot/management/commands/send_accept_answer_reminders.py
@@ -1,6 +1,7 @@
import datetime
from django.core.management.base import NoArgsCommand
from django.conf import settings as django_settings
+from django.template.loader import get_template
from askbot import models
from askbot import const
from askbot.conf import settings as askbot_settings
@@ -8,7 +9,6 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from askbot import mail
from askbot.utils.classes import ReminderSchedule
-from askbot.skins.loaders import get_template
from django.template import Context
DEBUG_THIS_COMMAND = False
@@ -73,7 +73,7 @@ class Command(NoArgsCommand):
}
template = get_template('email/accept_answer_reminder.html')
- body_text = template.render(Context(data))
+ body_text = template.render(Context(data))#todo: set lang
if DEBUG_THIS_COMMAND:
print "User: %s<br>\nSubject:%s<br>\nText: %s<br>\n" % \
diff --git a/askbot/management/commands/send_unanswered_question_reminders.py b/askbot/management/commands/send_unanswered_question_reminders.py
index 3fa390ad..982dbafb 100644
--- a/askbot/management/commands/send_unanswered_question_reminders.py
+++ b/askbot/management/commands/send_unanswered_question_reminders.py
@@ -1,4 +1,5 @@
from django.core.management.base import NoArgsCommand
+from django.template.loader import get_template
from askbot import models
from askbot import const
from askbot.conf import settings as askbot_settings
@@ -6,7 +7,6 @@ from django.utils.translation import ungettext
from askbot import mail
from askbot.utils.classes import ReminderSchedule
from askbot.models.question import Thread
-from askbot.skins.loaders import get_template
from django.template import Context
DEBUG_THIS_COMMAND = False
@@ -78,7 +78,7 @@ class Command(NoArgsCommand):
}
template = get_template('email/unanswered_question_reminder.html')
- body_text = template.render(Context(data))
+ body_text = template.render(Context(data))#todo: set lang
if DEBUG_THIS_COMMAND:
diff --git a/askbot/media/js/post.js b/askbot/media/js/post.js
index 3ed61182..fb6f6ef0 100644
--- a/askbot/media/js/post.js
+++ b/askbot/media/js/post.js
@@ -1591,7 +1591,7 @@ EditCommentForm.prototype.reset = function(){
EditCommentForm.prototype.confirmAbandon = function(){
this.focus(true);
this._textarea.addClass('highlight');
- var answer = confirm(gettext('confirm abandon comment'));
+ var answer = confirm(gettext("Are you sure you don't want to post this comment?"));
this._textarea.removeClass('highlight');
return answer;
};
@@ -1635,6 +1635,7 @@ EditCommentForm.prototype.getSaveHandler = function(){
dataType: "json",
data: post_data,
success: function(json) {
+ //type is 'edit' or 'add'
if (me._type == 'add'){
me._comment.dispose();
me._comment.getContainerWidget().reRenderComments(json);
diff --git a/askbot/media/js/tinymce/plugins/preview/preview.html b/askbot/media/js/tinymce/plugins/preview/preview.html
index 67e7b142..16693edb 100644
--- a/askbot/media/js/tinymce/plugins/preview/preview.html
+++ b/askbot/media/js/tinymce/plugins/preview/preview.html
@@ -1,6 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
+<link type="text/css" href="/m/default/media/style/style.css" rel="stylesheet" />
<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
<script type="text/javascript" src="jscripts/embed.js"></script>
<script type="text/javascript"><!--
diff --git a/askbot/media/style/style.css b/askbot/media/style/style.css
index 79bc422b..ea81746f 100644
--- a/askbot/media/style/style.css
+++ b/askbot/media/style/style.css
@@ -120,13 +120,9 @@ pre {
font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
font-size: 100%;
margin-bottom: 10px;
- /*overflow: auto;*/
-
background-color: #F5F5F5;
padding-left: 5px;
padding-top: 5px;
- /*width: 671px;*/
-
padding-bottom: 20px ! ie7;
}
code {
@@ -1797,6 +1793,7 @@ ul#related-tags li {
width: 100%;
}
.wmd-preview {
+ color: #525252;
margin: 0;
padding: 5px;
background-color: #F5F5F5;
@@ -1819,9 +1816,16 @@ ul#related-tags li {
.wmd-preview blockquote {
background-color: #eee;
}
-.wmd-preview IMG {
+.wmd-preview img {
max-width: 600px;
}
+.wmd-preview a {
+ color: #1b79bd;
+}
+.wmd-preview li {
+ margin-bottom: 7px;
+ font-size: 14px;
+}
.defaultSkin table.mceLayout,
.defaultSkin table.mceLayout tr.mceFirst td {
border: none;
@@ -3748,14 +3752,6 @@ p.signup_p {
.simple-subscribe-options input {
display: inline;
}
-/* a workaround to set link colors correctly */
-.wmd-preview a {
- color: #1b79bd;
-}
-.wmd-preview li {
- margin-bottom: 7px;
- font-size: 14px;
-}
.search-result-summary {
font-weight: bold;
font-size: 18px;
@@ -4146,3 +4142,39 @@ textarea.tipped-input {
width: 515px;
margin-bottom: 0px;
}
+/* modifications for small screens */
+@media screen and (max-width: 960px) {
+ /* content margins touch viewport */
+}
+@media screen and (max-width: 480px) {
+ .content-wrapper {
+ width: 100%;
+ }
+ #ContentRight {
+ display: none;
+ }
+ #ContentLeft {
+ width: 100%;
+ }
+ #secondaryHeader #scopeWrapper {
+ display: none;
+ }
+ #askButton {
+ display: block;
+ float: none;
+ margin: auto;
+ margin-top: 5px;
+ }
+ .main-page h1,
+ .main-page .counts .views,
+ .main-page .counts .votes,
+ .main-page .rss,
+ .main-page .tabBar,
+ .main-page .tags,
+ .main-page .userinfo {
+ display: none;
+ }
+ .short-summary {
+ width: 100%;
+ }
+}
diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less
index 9e2e2a25..69f4daf7 100644
--- a/askbot/media/style/style.less
+++ b/askbot/media/style/style.less
@@ -102,18 +102,15 @@ pre {
font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
font-size: 100%;
margin-bottom: 10px;
- /*overflow: auto;*/
background-color: #F5F5F5;
padding-left: 5px;
padding-top: 5px;
- /*width: 671px;*/
padding-bottom: 20px ! ie7;
}
code {
font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
font-size: 100%;
-
}
blockquote {
@@ -407,7 +404,7 @@ body.user-messages {
display:block;
float:left;
}
-
+
#homeButton:hover{
.sprites(-6px-45,-36px);
}
@@ -428,6 +425,7 @@ body.user-messages {
line-height:55px;
margin-left:16px
}
+
.on{
background:url(../images/scopearrow.png) no-repeat center bottom;
}
@@ -533,7 +531,7 @@ body.anon {
#searchBar {
width: 500px;
.searchInput {
- width: 440px;
+ width: 435px;
}
.searchInputCancelable {
@@ -1678,15 +1676,16 @@ ul#related-tags li {
}
.wmd-preview {
+ color: #525252;
margin: 0;
padding: 5px;
background-color: #F5F5F5;
min-height: 20px;
overflow: auto;
font-size:13px;
- font-family:@body-font;
+ font-family: @body-font;
- p{
+ p {
margin-bottom: 14px;
line-height: 1.4;
font-size: 14px;
@@ -1695,19 +1694,27 @@ ul#related-tags li {
p:last-child{
margin-bottom: 0;
}
-}
-.wmd-preview pre {
- background-color: #E7F1F8;
+ pre {
+ background-color: #E7F1F8;
+ }
-}
+ blockquote {
+ background-color: #eee;
+ }
-.wmd-preview blockquote {
- background-color: #eee;
-}
+ img {
+ max-width: 600px;
+ }
+
+ a {
+ color: @link;
+ }
-.wmd-preview IMG {
- max-width: 600px;
+ li {
+ margin-bottom:7px;
+ font-size:14px;
+ }
}
.defaultSkin table.mceLayout,
@@ -3605,17 +3612,6 @@ p.signup_p {
}
}
-/* a workaround to set link colors correctly */
-
-.wmd-preview a {
- color:@link;
-}
-
-.wmd-preview li {
- margin-bottom:7px;
- font-size:14px;
-}
-
.search-result-summary {
font-weight: bold;
font-size:18px;
@@ -4008,3 +4004,43 @@ textarea.tipped-input {
width: 515px;
margin-bottom: 0px;
}
+
+/* modifications for small screens */
+@media screen and (max-width: 960px) {/* content margins touch viewport */
+}
+@media screen and (max-width: 480px) {
+ .content-wrapper {
+ width: 100%;
+ }
+ #ContentRight {
+ display: none;
+ }
+ #ContentLeft {
+ width: 100%;
+ }
+ #secondaryHeader #scopeWrapper {
+ display: none;
+ }
+ #askButton {
+ display: block;
+ float: none;
+ margin: auto;
+ margin-top: 5px;
+ }
+ #homeButton {
+ }
+ .main-page {
+ h1,
+ .counts .views,
+ .counts .votes,
+ .rss,
+ .tabBar,
+ .tags,
+ .userinfo {
+ display: none;
+ }
+ }
+ .short-summary {
+ width: 100%;
+ }
+}
diff --git a/askbot/media/style/tinymce/content.css b/askbot/media/style/tinymce/content.css
index 41c45d1e..7a81caaf 100644
--- a/askbot/media/style/tinymce/content.css
+++ b/askbot/media/style/tinymce/content.css
@@ -51,8 +51,78 @@ font[face=mceinline] {font-family:inherit !important}
/* overrides on top of default */
body.mceContentBody {
- font-size: 13px;
- background-color:#fff;
- padding: 0;
+ font-size: 14px;
+ background-color: white;
+ padding: 5px;
+ margin: 0;
line-height:135%;
+ color: #525252;
+ font-family: Arial;
+}
+
+body.mceContentBody p {
+ margin-bottom: 14px;
+ line-height: 1.4;
+ font-size: 14px;
+}
+
+body.mceContentBody p:last-child {
+ margin-bottom: 0;
+}
+
+body.mceContentBody pre {
+ background-color: #E7F1F8;
+}
+
+body.mceContentBody blockquote {
+ background-color: #eee;
+}
+
+body.mceContentBody img {
+ max-width: 600px;
+}
+
+body.mceContentBody a {
+ color: #1b79bd;
+}
+
+body.mceContentBody li {
+ margin-bottom:7px;
+ font-size:14px;
+}
+
+body.mceContentBody ul {
+ list-style: disc;
+ margin-left: 20px;
+ padding-left: 0px;
+ margin-bottom: 1em;
+}
+
+body.mceContentBody ol {
+ list-style: decimal;
+ margin-left: 30px;
+ margin-bottom: 1em;
+ padding-left: 0px;
+}
+
+body.mceContentBody pre {
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size: 100%;
+ margin-bottom: 10px;
+ background-color: #F5F5F5;
+ padding-left: 5px;
+ padding-top: 5px;
+ padding-bottom: 20px ! ie7;
+}
+
+body.mceContentBody code {
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size: 100%;
+}
+
+body.mceContentBody blockquote {
+ margin-bottom: 10px;
+ margin-right: 15px;
+ padding: 10px 0px 1px 10px;
+ background-color: #F5F5F5;
}
diff --git a/askbot/middleware/forum_mode.py b/askbot/middleware/forum_mode.py
index d593a6f2..331fe85b 100644
--- a/askbot/middleware/forum_mode.py
+++ b/askbot/middleware/forum_mode.py
@@ -11,7 +11,7 @@ from askbot.conf import settings as askbot_settings
PROTECTED_VIEW_MODULES = (
'askbot.views',
- 'django.contrib.syndication.views',
+ 'askbot.feed',
)
ALLOWED_VIEWS = (
'askbot.views.meta.media',
@@ -30,7 +30,13 @@ def is_view_allowed(func):
"""True, if view is allowed to access
by the special rule
"""
- view_path = func.__module__ + '.' + func.__name__
+ if hasattr(func, '__name__'):
+ view_path = func.__module__ + '.' + func.__name__
+ elif hasattr(func, '__class__'):
+ view_path = func.__module__ + '.' + func.__class__.__name__
+ else:
+ view_path = ''
+
return view_path in ALLOWED_VIEWS
class ForumModeMiddleware(object):
@@ -51,7 +57,7 @@ class ForumModeMiddleware(object):
if is_view_allowed(resolver_match.func):
return
-
+
if is_view_protected(resolver_match.func):
request.user.message_set.create(
_('Please log in to use %s') % \
diff --git a/askbot/migrations/0155_remove_unused_internal_tags.py b/askbot/migrations/0155_remove_unused_internal_tags.py
new file mode 100644
index 00000000..d4ef662f
--- /dev/null
+++ b/askbot/migrations/0155_remove_unused_internal_tags.py
@@ -0,0 +1,387 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+ def forwards(self, orm):
+ "Write your forwards methods here."
+ print 'Deleting unused tags added for internal bookkeeping...'
+ tags = orm['askbot.Tag'].objects.filter(name__startswith='_internal_')
+ count = tags.count()
+ if count > 0:
+ tags.delete()
+ print '%d tags formatted _internal_X' % count
+ else:
+ print 'None found'
+
+
+ def backwards(self, orm):
+ "Write your backwards methods here."
+
+
+ models = {
+ 'askbot.activity': {
+ 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"},
+ 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True'}),
+ 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'symmetrical': 'False', 'through': "orm['askbot.ActivityAuditStatus']", 'to': "orm['auth.User']"}),
+ 'summary': ('django.db.models.fields.TextField', [], {'default': "''"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.activityauditstatus': {
+ 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'},
+ 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.anonymousanswer': {
+ 'Meta': {'object_name': 'AnonymousAnswer'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Post']"}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'askbot.anonymousquestion': {
+ 'Meta': {'object_name': 'AnonymousQuestion'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),
+ 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'askbot.askwidget': {
+ 'Meta': {'object_name': 'AskWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'include_text_field': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'inner_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'outer_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']", 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'askbot.award': {
+ 'Meta': {'object_name': 'Award', 'db_table': "u'award'"},
+ 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.badgedata': {
+ 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'},
+ 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'})
+ },
+ 'askbot.draftanswer': {
+ 'Meta': {'object_name': 'DraftAnswer'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['askbot.Thread']"})
+ },
+ 'askbot.draftquestion': {
+ 'Meta': {'object_name': 'DraftQuestion'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125', 'null': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'})
+ },
+ 'askbot.emailfeedsetting': {
+ 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.favoritequestion': {
+ 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.group': {
+ 'Meta': {'object_name': 'Group', '_ormbases': ['auth.Group']},
+ 'description': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'described_group'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
+ 'is_vip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_answers_to_enquirers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'openness': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}),
+ 'preapproved_email_domains': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+ 'preapproved_emails': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
+ },
+ 'askbot.groupmembership': {
+ 'Meta': {'object_name': 'GroupMembership', '_ormbases': ['auth.AuthUserGroups']},
+ 'authusergroups_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.AuthUserGroups']", 'unique': 'True', 'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+ },
+ 'askbot.markedtag': {
+ 'Meta': {'object_name': 'MarkedTag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.post': {
+ 'Meta': {'object_name': 'Post'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}),
+ 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_posts'", 'symmetrical': 'False', 'through': "orm['askbot.PostToGroup']", 'to': "orm['askbot.Group']"}),
+ 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}),
+ 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'summary': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'posts'", 'null': 'True', 'blank': 'True', 'to': "orm['askbot.Thread']"}),
+ 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'askbot.postflagreason': {
+ 'Meta': {'object_name': 'PostFlagReason'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'details': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_reject_reasons'", 'to': "orm['askbot.Post']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'askbot.postrevision': {
+ 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'},
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'approved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'approved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}),
+ 'by_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'revised_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'})
+ },
+ 'askbot.posttogroup': {
+ 'Meta': {'unique_together': "(('post', 'group'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"})
+ },
+ 'askbot.questionview': {
+ 'Meta': {'object_name': 'QuestionView'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {}),
+ 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.questionwidget': {
+ 'Meta': {'object_name': 'QuestionWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order_by': ('django.db.models.fields.CharField', [], {'default': "'-added_at'", 'max_length': '18'}),
+ 'question_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '7'}),
+ 'search_query': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'style': ('django.db.models.fields.TextField', [], {'default': '"\\n@import url(\'http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700\');\\nbody {\\n overflow: hidden;\\n}\\n\\n#container {\\n width: 200px;\\n height: 350px;\\n}\\nul {\\n list-style: none;\\n padding: 5px;\\n margin: 5px;\\n}\\nli {\\n border-bottom: #CCC 1px solid;\\n padding-bottom: 5px;\\n padding-top: 5px;\\n}\\nli:last-child {\\n border: none;\\n}\\na {\\n text-decoration: none;\\n color: #464646;\\n font-family: \'Yanone Kaffeesatz\', sans-serif;\\n font-size: 15px;\\n}\\n"', 'blank': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'askbot.replyaddress': {
+ 'Meta': {'object_name': 'ReplyAddress'},
+ 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}),
+ 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'reply_action': ('django.db.models.fields.CharField', [], {'default': "'auto_answer_or_comment'", 'max_length': '32'}),
+ 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.repute': {
+ 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"},
+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.tag': {
+ 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"},
+ 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'suggested_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'suggested_tags'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'tag_wiki': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'described_tag'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+ },
+ 'askbot.thread': {
+ 'Meta': {'object_name': 'Thread'},
+ 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}),
+ 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_threads'", 'symmetrical': 'False', 'through': "orm['askbot.ThreadToGroup']", 'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}),
+ 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),
+ 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+ },
+ 'askbot.threadtogroup': {
+ 'Meta': {'unique_together': "(('thread', 'group'),)", 'object_name': 'ThreadToGroup', 'db_table': "'askbot_thread_groups'"},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}),
+ 'visibility': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+ },
+ 'askbot.vote': {
+ 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}),
+ 'vote': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"})
+ },
+ 'auth.authusergroups': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'AuthUserGroups', 'db_table': "'auth_user_groups'", 'managed': 'False'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
+ 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+ 'email_signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['askbot']
+ symmetrical = True
diff --git a/askbot/migrations/0156_add_message_model_in_new_django.py b/askbot/migrations/0156_add_message_model_in_new_django.py
new file mode 100644
index 00000000..950196fa
--- /dev/null
+++ b/askbot/migrations/0156_add_message_model_in_new_django.py
@@ -0,0 +1,386 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models, connection
+import django
+
+def db_table_exists(table_name):
+ return table_name in connection.introspection.table_names()
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ if django.get_version() > '1.3.1' and not db_table_exists('auth_message'):
+ db.create_table('auth_message', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='_message_set', to=orm['auth.User'])),
+ ('message', self.gf('django.db.models.fields.TextField')())
+ ))
+ db.send_create_signal('askbot', ['Message'])
+
+ def backwards(self, orm):
+ 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.askwidget': {
+ 'Meta': {'object_name': 'AskWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'include_text_field': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'inner_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'outer_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']", 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'askbot.award': {
+ 'Meta': {'object_name': 'Award', 'db_table': "u'award'"},
+ 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.badgedata': {
+ 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'},
+ 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
+ },
+ 'askbot.draftanswer': {
+ 'Meta': {'object_name': 'DraftAnswer'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['askbot.Thread']"})
+ },
+ 'askbot.draftquestion': {
+ 'Meta': {'object_name': 'DraftQuestion'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125', 'null': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'})
+ },
+ 'askbot.emailfeedsetting': {
+ 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.favoritequestion': {
+ 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.group': {
+ 'Meta': {'object_name': 'Group', '_ormbases': ['auth.Group']},
+ 'description': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'described_group'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}),
+ 'is_vip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+ 'moderate_answers_to_enquirers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'openness': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}),
+ 'preapproved_email_domains': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+ 'preapproved_emails': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
+ },
+ 'askbot.groupmembership': {
+ 'Meta': {'object_name': 'GroupMembership', '_ormbases': ['auth.AuthUserGroups']},
+ 'authusergroups_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.AuthUserGroups']", 'unique': 'True', 'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+ },
+ 'askbot.markedtag': {
+ 'Meta': {'object_name': 'MarkedTag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+ 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.post': {
+ 'Meta': {'object_name': 'Post'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}),
+ 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_posts'", 'symmetrical': 'False', 'through': "orm['askbot.PostToGroup']", 'to': "orm['askbot.Group']"}),
+ 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}),
+ 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'summary': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'posts'", 'null': 'True', 'blank': 'True', 'to': "orm['askbot.Thread']"}),
+ 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'askbot.postflagreason': {
+ 'Meta': {'object_name': 'PostFlagReason'},
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'details': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_reject_reasons'", 'to': "orm['askbot.Post']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+ },
+ 'askbot.postrevision': {
+ 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'},
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+ 'approved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'approved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}),
+ 'by_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'revised_at': ('django.db.models.fields.DateTimeField', [], {}),
+ 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}),
+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}),
+ 'text': ('django.db.models.fields.TextField', [], {}),
+ 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'})
+ },
+ 'askbot.posttogroup': {
+ 'Meta': {'unique_together': "(('post', 'group'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"})
+ },
+ 'askbot.questionview': {
+ 'Meta': {'object_name': 'QuestionView'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}),
+ 'when': ('django.db.models.fields.DateTimeField', [], {}),
+ 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"})
+ },
+ 'askbot.questionwidget': {
+ 'Meta': {'object_name': 'QuestionWidget'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'order_by': ('django.db.models.fields.CharField', [], {'default': "'-added_at'", 'max_length': '18'}),
+ 'question_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '7'}),
+ 'search_query': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'style': ('django.db.models.fields.TextField', [], {'default': '"\\n@import url(\'http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700\');\\nbody {\\n overflow: hidden;\\n}\\n\\n#container {\\n width: 200px;\\n height: 350px;\\n}\\nul {\\n list-style: none;\\n padding: 5px;\\n margin: 5px;\\n}\\nli {\\n border-bottom: #CCC 1px solid;\\n padding-bottom: 5px;\\n padding-top: 5px;\\n}\\nli:last-child {\\n border: none;\\n}\\na {\\n text-decoration: none;\\n color: #464646;\\n font-family: \'Yanone Kaffeesatz\', sans-serif;\\n font-size: 15px;\\n}\\n"', 'blank': 'True'}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'askbot.replyaddress': {
+ 'Meta': {'object_name': 'ReplyAddress'},
+ 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}),
+ 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'reply_action': ('django.db.models.fields.CharField', [], {'default': "'auto_answer_or_comment'", 'max_length': '32'}),
+ 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.repute': {
+ 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"},
+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'askbot.tag': {
+ 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"},
+ 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'suggested_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'suggested_tags'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'tag_wiki': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'described_tag'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+ },
+ 'askbot.thread': {
+ 'Meta': {'object_name': 'Thread'},
+ 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}),
+ 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+ 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+ 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}),
+ 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_threads'", 'symmetrical': 'False', 'through': "orm['askbot.ThreadToGroup']", 'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}),
+ 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}),
+ 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}),
+ 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
+ },
+ 'askbot.threadtogroup': {
+ 'Meta': {'unique_together': "(('thread', 'group'),)", 'object_name': 'ThreadToGroup', 'db_table': "'askbot_thread_groups'"},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}),
+ 'visibility': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'})
+ },
+ 'askbot.vote': {
+ 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}),
+ 'vote': ('django.db.models.fields.SmallIntegerField', [], {}),
+ 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"})
+ },
+ 'auth.authusergroups': {
+ 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'AuthUserGroups', 'db_table': "'auth_user_groups'", 'managed': 'False'},
+ 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+ },
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
+ 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+ 'email_signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
+ 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
+ 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
+ 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
+ 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ }
+ }
+
+ complete_apps = ['askbot']
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index e89b2cda..3a46f119 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -13,6 +13,7 @@ import collections
import datetime
import hashlib
import logging
+import re
import urllib
import uuid
from celery import states
@@ -20,6 +21,7 @@ from celery.task import task
from django.core.urlresolvers import reverse, NoReverseMatch
from django.db.models import signals as django_signals
from django.template import Context
+from django.template.loader import get_template
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.utils.safestring import mark_safe
@@ -56,12 +58,19 @@ from askbot.models.repute import Award, Repute, Vote
from askbot.models.widgets import AskWidget, QuestionWidget
from askbot import auth
from askbot.utils.decorators import auto_now_timestamp
+from askbot.utils.markup import URL_RE
from askbot.utils.slug import slugify
+from askbot.utils.html import replace_links_with_text
from askbot.utils.html import sanitize_html
from askbot.utils.diff import textDiff as htmldiff
from askbot.utils.url_utils import strip_path
from askbot import mail
+from django import get_version as django_version
+
+if django_version() > '1.3.1':
+ from askbot.models.message import Message
+
def get_model(model_name):
"""a shortcut for getting model for an askbot app"""
return models.get_model('askbot', model_name)
@@ -113,6 +122,38 @@ def get_users_by_text_query(search_query, users_query_set = None):
# models.Q(about__search = search_query)
# )
+class RelatedObjectSimulator(object):
+ '''Objects that simulates the "messages_set" related field
+ somehow django does not creates it automatically in django1.4.1'''
+
+ def __init__(self, user, model_class):
+ self.user = user
+ self.model_class = model_class
+
+ def create(self, **kwargs):
+ return self.model_class.objects.create(user=self.user, **kwargs)
+
+ def filter(self, *args, **kwargs):
+ return self.model_class.objects.filter(*args, **kwargs)
+
+
+#django 1.4.1 only
+@property
+def user_message_set(self):
+ return RelatedObjectSimulator(self, Message)
+
+#django 1.4.1 only
+def user_get_and_delete_messages(self):
+ messages = []
+ for message in Message.objects.filter(user=self):
+ messages.append(message)
+ message.delete()
+ return messages
+
+if django_version() > '1.3.1':
+ User.add_to_class('message_set', user_message_set)
+ User.add_to_class('get_and_delete_messages', user_get_and_delete_messages)
+
User.add_to_class(
'status',
models.CharField(
@@ -633,6 +674,19 @@ def user_assert_can_upload_file(request_user):
)
+def user_assert_can_post_text(self, text):
+ """Raises exceptions.PermissionDenied, if user does not have
+ privilege to post given text, depending on the contents
+ """
+ if re.search(URL_RE, text):
+ min_rep = askbot_settings.MIN_REP_TO_SUGGEST_LINK
+ if self.is_authenticated() and self.reputation < min_rep:
+ message = _(
+ 'Could not post, because your karma is insufficient to publish links'
+ )
+ raise django_exceptions.PermissionDenied(message)
+
+
def user_assert_can_post_question(self):
"""raises exceptions.PermissionDenied with
text that has the reason for the denial
@@ -2478,6 +2532,25 @@ def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None):
)
return vote
+def user_fix_html_links(self, text):
+ """depending on the user's privilege, allow links
+ and hotlinked images or replace them with plain text
+ url
+ """
+ is_simple_user = not self.is_administrator_or_moderator()
+ has_low_rep = self.reputation < askbot_settings.MIN_REP_TO_INSERT_LINK
+ if is_simple_user and has_low_rep:
+ result = replace_links_with_text(text)
+ if result != text:
+ message = ungettext(
+ 'At least %d karma point is required to post links',
+ 'At least %d karma points is required to post links',
+ askbot_settings.MIN_REP_TO_INSERT_LINK
+ ) % askbot_settings.MIN_REP_TO_INSERT_LINK
+ self.message_set.create(message=message)
+ return result
+ return text
+
def user_unfollow_question(self, question = None):
self.followed_threads.remove(question.thread)
@@ -2790,6 +2863,7 @@ User.add_to_class('get_tag_filtered_questions', user_get_tag_filtered_questions)
User.add_to_class('get_messages', get_messages)
User.add_to_class('delete_messages', delete_messages)
User.add_to_class('toggle_favorite_question', toggle_favorite_question)
+User.add_to_class('fix_html_links', user_fix_html_links)
User.add_to_class('follow_question', user_follow_question)
User.add_to_class('unfollow_question', user_unfollow_question)
User.add_to_class('is_following_question', user_is_following_question)
@@ -2846,6 +2920,7 @@ User.add_to_class('assert_can_upload_file', user_assert_can_upload_file)
User.add_to_class('assert_can_post_question', user_assert_can_post_question)
User.add_to_class('assert_can_post_answer', user_assert_can_post_answer)
User.add_to_class('assert_can_post_comment', user_assert_can_post_comment)
+User.add_to_class('assert_can_post_text', user_assert_can_post_text)
User.add_to_class('assert_can_edit_post', user_assert_can_edit_post)
User.add_to_class('assert_can_edit_deleted_post', user_assert_can_edit_deleted_post)
User.add_to_class('assert_can_see_deleted_post', user_assert_can_see_deleted_post)
@@ -3365,9 +3440,8 @@ def send_respondable_email_validation_message(
)
data['email_code'] = reply_address.address
- from askbot.skins.loaders import get_template
template = get_template(template_name)
- body_text = template.render(Context(data))
+ body_text = template.render(Context(data))#todo: set lang
reply_to_address = 'welcome-%s@%s' % (
reply_address.address,
@@ -3509,7 +3583,6 @@ def moderate_group_joining(sender, instance=None, created=False, **kwargs):
content_object = group
)
-
#signal for User model save changes
django_signals.pre_save.connect(make_admin_if_first_user, sender=User)
django_signals.pre_save.connect(calculate_gravatar_hash, sender=User)
diff --git a/askbot/models/message.py b/askbot/models/message.py
new file mode 100644
index 00000000..5086f515
--- /dev/null
+++ b/askbot/models/message.py
@@ -0,0 +1,25 @@
+'''Copied from Django 1.3.1 source code, it will use this model to'''
+from django.db import models
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext as _
+
+class Message(models.Model):
+ """
+ The message system is a lightweight way to queue messages for given
+ users. A message is associated with a User instance (so it is only
+ applicable for registered users). There's no concept of expiration or
+ timestamps. Messages are created by the Django admin after successful
+ actions. For example, "The poll Foo was created successfully." is a
+ message.
+ """
+ user = models.ForeignKey(User, related_name='_message_set')
+ message = models.TextField(_('message'))
+
+ class Meta:
+ '''Added for backwards compatibility with databases
+ migrated from django 1.3'''
+ app_label = 'auth'
+ db_table = 'auth_message'
+
+ def __unicode__(self):
+ return self.message
diff --git a/askbot/models/post.py b/askbot/models/post.py
index daf9c93f..ce7b249b 100644
--- a/askbot/models/post.py
+++ b/askbot/models/post.py
@@ -503,8 +503,8 @@ class Post(models.Model):
last_revision = self.html
data = self.parse_post_text()
+ self.html = author.fix_html_links(data['html'])
- self.html = data['html']
newly_mentioned_users = set(data['newly_mentioned_users']) - set([author])
removed_mentions = data['removed_mentions']
@@ -862,8 +862,8 @@ class Post(models.Model):
if quote_level > 0, the post will be indented that number of times
todo: move to views?
"""
- from askbot.skins.loaders import get_template
from django.template import Context
+ from django.template.loader import get_template
template = get_template('email/quoted_post.html')
data = {
'post': self,
@@ -871,7 +871,7 @@ class Post(models.Model):
'is_leaf_post': is_leaf_post,
'format': format
}
- return template.render(Context(data))
+ return template.render(Context(data))#todo: set lang
def format_for_email_as_parent_thread_summary(self):
"""format for email as summary of parent posts
@@ -884,17 +884,6 @@ class Post(models.Model):
if parent_post is None:
break
quote_level += 1
- """
- output += '<p>'
- output += _(
- 'In reply to %(user)s %(post)s of %(date)s'
- ) % {
- 'user': parent_post.author.username,
- 'post': _(parent_post.post_type),
- 'date': parent_post.added_at.strftime(const.DATETIME_FORMAT)
- }
- output += '</p>'
- """
output += parent_post.format_for_email(
quote_level = quote_level,
format = 'parent_subthread'
@@ -906,10 +895,10 @@ class Post(models.Model):
"""outputs question or answer and all it's comments
returns empty string for all other post types
"""
- from askbot.skins.loaders import get_template
from django.template import Context
+ from django.template.loader import get_template
template = get_template('email/post_as_subthread.html')
- return template.render(Context({'post': self}))
+ return template.render(Context({'post': self}))#todo: set lang
def set_cached_comments(self, comments):
"""caches comments in the lifetime of the object
@@ -2212,4 +2201,5 @@ class AnonymousAnswer(DraftContent):
wiki=self.wiki,
text=self.text
)
+ self.question.thread.invalidate_cached_data()
self.delete()
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 7faf589b..5c0d5732 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -8,9 +8,11 @@ from django.contrib.auth.models import User
from django.core import cache # import cache, not from cache import cache, to be able to monkey-patch cache.cache in test cases
from django.core import exceptions as django_exceptions
from django.core.urlresolvers import reverse
+from django.template.loader import get_template
from django.utils.hashcompat import md5_constructor
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
+from django.utils.translation import string_concat
import askbot
from askbot.conf import settings as askbot_settings
@@ -29,7 +31,6 @@ from askbot import const
from askbot.utils.lists import LazyList
from askbot.search import mysql
from askbot.utils.slug import slugify
-from askbot.skins.loaders import get_template #jinja2 template loading enviroment
from askbot.search.state_manager import DummySearchState
class ThreadQuerySet(models.query.QuerySet):
@@ -702,7 +703,10 @@ class Thread(models.Model):
def format_for_email(self, user=None):
"""experimental function: output entire thread for email"""
- question, answers, junk, published_ans_ids = self.get_cached_post_data(user=user)
+
+ question, answers, junk, published_ans_ids = \
+ self.get_cached_post_data(user=user)
+
output = question.format_for_email_as_subthread()
if answers:
answer_heading = ungettext(
@@ -1415,16 +1419,35 @@ class AnonymousQuestion(DraftContent):
tagnames = models.CharField(max_length=125)
is_anonymous = models.BooleanField(default=False)
- def publish(self,user):
+ 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,
- author = user,
- wiki = self.wiki,
- is_anonymous = self.is_anonymous,
- tagnames = self.tagnames,
- text = self.text,
- )
- self.delete()
+ try:
+ user.assert_can_post_text(self.text)
+ Thread.objects.create_new(
+ title = self.title,
+ added_at = added_at,
+ author = user,
+ wiki = self.wiki,
+ is_anonymous = self.is_anonymous,
+ tagnames = self.tagnames,
+ text = self.text,
+ )
+ self.delete()
+ except django_exceptions.PermissionDenied, error:
+ #delete previous draft questions (only one is allowed anyway)
+ prev_drafts = DraftQuestion.objects.filter(author=user)
+ prev_drafts.delete()
+ #convert this question to draft
+ DraftQuestion.objects.create(
+ author=user,
+ title=self.title,
+ text=self.text,
+ tagnames=self.tagnames
+ )
+ #add message with a link to the ask page
+ extra_message = _(
+ 'Please, <a href="%s">review your question</a>.'
+ ) % reverse('ask')
+ message = string_concat(unicode(error), u' ', extra_message)
+ user.message_set.create(message=unicode(message))
diff --git a/askbot/models/repute.py b/askbot/models/repute.py
index a6e9d7d1..e48773e6 100644
--- a/askbot/models/repute.py
+++ b/askbot/models/repute.py
@@ -223,7 +223,7 @@ class Repute(models.Model):
return _('<em>Changed by moderator. Reason:</em> %(reason)s') \
% {'reason':self.comment}
else:
- delta = self.positive - self.negative
+ delta = self.positive + self.negative#.negative is < 0 so we add!
link_title_data = {
'points': abs(delta),
'username': self.user.username,
diff --git a/askbot/models/tag.py b/askbot/models/tag.py
index d7e91eb5..e4c4540a 100644
--- a/askbot/models/tag.py
+++ b/askbot/models/tag.py
@@ -1,5 +1,4 @@
import re
-import logging
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
diff --git a/askbot/patches/__init__.py b/askbot/patches/__init__.py
index ebaeabac..6145097c 100644
--- a/askbot/patches/__init__.py
+++ b/askbot/patches/__init__.py
@@ -17,6 +17,9 @@ def patch_django():
django_patches.add_csrf_protection()
django_patches.add_available_attrs_decorator()
+ if major == 1 and minor <=2:
+ django_patches.add_render_shortcut()
+
def patch_coffin():
"""coffin before version 0.3.4
does not have csrf_token template tag.
diff --git a/askbot/patches/django_patches.py b/askbot/patches/django_patches.py
index e28c88ba..fe0e2fe7 100644
--- a/askbot/patches/django_patches.py
+++ b/askbot/patches/django_patches.py
@@ -339,3 +339,16 @@ def add_available_attrs_decorator():
return tuple(a for a in WRAPPER_ASSIGNMENTS if hasattr(fn, a))
import django.utils.decorators
django.utils.decorators.available_attrs = available_attrs
+
+def add_render_shortcut():
+ """adds `render` shortcut, introduced with django 1.3"""
+ try:
+ from django.shortcuts import render
+ except ImportError:
+ def render(request, template, data=None):
+ from django.shortcuts import render_to_response
+ from django.template import RequestContext
+ return render_to_response(template, RequestContext(request, data))
+
+ import django.shortcuts
+ django.shortcuts.render = render
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index b43b2b50..237a1280 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -87,8 +87,8 @@ SECRET_KEY = 'sdljdfjkldsflsdjkhsjkldgjlsdgfs s '
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
#below is askbot stuff for this tuple
#'askbot.skins.loaders.load_template_source', #changed due to bug 97
'askbot.skins.loaders.filesystem_load_template_source',
@@ -144,7 +144,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'askbot.context.application_settings',
#'django.core.context_processors.i18n',
'askbot.user_messages.context_processors.user_messages',#must be before auth
- 'django.core.context_processors.auth', #this is required for admin
+ 'django.contrib.auth.context_processors.auth', #this is required for admin
'django.core.context_processors.csrf', #necessary for csrf protection
)
diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache
index aa153435..1f3dabd2 100644
--- a/askbot/setup_templates/settings.py.mustache
+++ b/askbot/setup_templates/settings.py.mustache
@@ -19,12 +19,16 @@ ADMINS = (
MANAGERS = ADMINS
-DATABASE_ENGINE = 'postgresql_psycopg2' # only postgres (>8.3) and mysql are supported so far others have not been tested yet
-DATABASE_NAME = '{{database_name}}' # Or path to database file if using sqlite3.
-DATABASE_USER = '{{database_user}}' # Not used with sqlite3.
-DATABASE_PASSWORD = '{{database_password}}' # Not used with sqlite3.
-DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
-DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': '{{database_name}}', # Or path to database file if using sqlite3.
+ 'USER': '{{database_user}}', # Not used with sqlite3.
+ 'PASSWORD': '{{database_password}}', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
#outgoing mail server settings
SERVER_EMAIL = ''
@@ -87,10 +91,9 @@ SECRET_KEY = 'sdljdfjkldsflsdjkhsjkldgjlsdgfs s '
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
- #below is askbot stuff for this tuple
- 'askbot.skins.loaders.filesystem_load_template_source',
+ 'askbot.skins.loaders.Loader',
+ 'django.template.loaders.app_directories.Loader',
+ 'django.template.loaders.filesystem.Loader',
#'django.template.loaders.eggs.load_template_source',
)
@@ -143,7 +146,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'askbot.context.application_settings',
#'django.core.context_processors.i18n',
'askbot.user_messages.context_processors.user_messages',#must be before auth
- 'django.core.context_processors.auth', #this is required for admin
+ '{{ auth_context_processor }}', #this is required for the admin app
'django.core.context_processors.csrf', #necessary for csrf protection
)
diff --git a/askbot/skins/loaders.py b/askbot/skins/loaders.py
index fc680f00..50276f34 100644
--- a/askbot/skins/loaders.py
+++ b/askbot/skins/loaders.py
@@ -1,6 +1,7 @@
import os.path
-from django.template.loaders import filesystem
+from django.template.loader import BaseLoader
from django.template import RequestContext
+from django.template import TemplateDoesNotExist
from django.http import HttpResponse
from django.utils import translation
from django.conf import settings as django_settings
@@ -22,27 +23,6 @@ template.add_to_builtins('askbot.templatetags.extra_filters_jinja')
#here it is ignored because it is assumed that we won't use unicode paths
ASKBOT_SKIN_COLLECTION_DIR = os.path.dirname(__file__)
-#changed the name from load_template_source
-def filesystem_load_template_source(name, dirs=None):
- """Django template loader
- """
-
- if dirs is None:
- dirs = (ASKBOT_SKIN_COLLECTION_DIR, )
- else:
- dirs += (ASKBOT_SKIN_COLLECTION_DIR, )
-
- try:
- #todo: move this to top after splitting out get_skin_dirs()
- tname = os.path.join(askbot_settings.ASKBOT_DEFAULT_SKIN, 'templates', name)
- return filesystem.load_template_source(tname, dirs)
- except:
- tname = os.path.join('default', 'templates', name)
- return filesystem.load_template_source(tname, dirs)
-filesystem_load_template_source.is_usable = True
-#added this for backward compatbility
-load_template_source = filesystem_load_template_source
-
class SkinEnvironment(CoffinEnvironment):
"""Jinja template environment
that loads templates from askbot skins
@@ -103,8 +83,9 @@ def get_skin(request = None):
for a given request (request var is not used at this time)"""
return SKINS[askbot_settings.ASKBOT_DEFAULT_SKIN]
-def get_template(template, request = None):
- """retreives template for the skin
+def get_askbot_template(template, request = None):
+ """
+ retreives template for the skin
request variable will be used in the future to set
template according to the user preference or admins preference
@@ -117,21 +98,22 @@ def get_template(template, request = None):
def render_into_skin_as_string(template, data, request):
context = RequestContext(request, data)
- template = get_template(template, request)
+ template = get_askbot_template(template, request)
return template.render(context)
-def render_into_skin(template, data, request, mimetype = 'text/html'):
- """in the future this function will be able to
- switch skin depending on the site administrator/user selection
- right now only admins can switch
- """
- return HttpResponse(
- render_into_skin_as_string(template, data, request),
- mimetype=mimetype
- )
-
def render_text_into_skin(text, data, request):
context = RequestContext(request, data)
skin = get_skin(request)
template = skin.from_string(text)
return template.render(context)
+
+class Loader(BaseLoader):
+ """skins template loader for Django > 1.2
+ todo: verify that this actually follows django's convention correctly
+ """
+ is_usable = True
+ def load_template(self, template_name, template_dirs = None):
+ try:
+ return get_askbot_template(template_name), template_name
+ except TemplateNotFound:
+ raise TemplateDoesNotExist
diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py
index 6b28f688..1fa1b3e7 100644
--- a/askbot/startup_procedures.py
+++ b/askbot/startup_procedures.py
@@ -7,12 +7,13 @@ question: why not run these from askbot/__init__.py?
the main function is run_startup_tests
"""
-import sys
+import askbot
+import django
import os
import re
-import urllib
-import askbot
import south
+import sys
+import urllib
from django.db import transaction, connection
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
@@ -44,7 +45,7 @@ class AskbotConfigError(ImproperlyConfigured):
def askbot_warning(line):
"""prints a warning with the nice header, but does not quit"""
- print >> sys.stderr, PREAMBLE + '\n' + line
+ print >> sys.stderr, line
def print_errors(error_messages, header = None, footer = None):
"""if there is one or more error messages,
@@ -210,14 +211,28 @@ def test_encoding():
def test_template_loader():
"""Sends a warning if you have an old style template
loader that used to send a warning"""
- old_template_loader = 'askbot.skins.loaders.load_template_source'
- if old_template_loader in django_settings.TEMPLATE_LOADERS:
- raise AskbotConfigError(
- "\nPlease change: \n"
- "'askbot.skins.loaders.load_template_source', to\n"
- "'askbot.skins.loaders.filesystem_load_template_source',\n"
- "in the TEMPLATE_LOADERS of your settings.py file"
+ old_loaders = (
+ 'askbot.skins.loaders.load_template_source',
+ 'askbot.skins.loaders.filesystem_load_template_source',
+ )
+ errors = list()
+ for loader in old_loaders:
+ if loader in django_settings.TEMPLATE_LOADERS:
+ errors.append(
+ 'remove "%s" from the TEMPLATE_LOADERS setting' % loader
+ )
+
+ current_loader = 'askbot.skins.loaders.Loader'
+ if current_loader not in django_settings.TEMPLATE_LOADERS:
+ errors.append(
+ 'add "%s" to the beginning of the TEMPLATE_LOADERS' % current_loader
)
+ elif django_settings.TEMPLATE_LOADERS[0] != current_loader:
+ errors.append(
+ '"%s" must be the first element of TEMPLATE_LOADERS' % current_loader
+ )
+
+ print_errors(errors)
def test_celery():
"""Tests celery settings
@@ -348,7 +363,6 @@ def test_new_skins():
def test_staticfiles():
"""tests configuration of the staticfiles app"""
errors = list()
- import django
django_version = django.VERSION
if django_version[0] == 1 and django_version[1] < 3:
staticfiles_app_name = 'staticfiles'
@@ -458,7 +472,6 @@ def test_staticfiles():
' python manage.py collectstatic\n'
)
-
print_errors(errors)
if django_settings.STATICFILES_STORAGE == \
'django.contrib.staticfiles.storage.StaticFilesStorage':
@@ -686,6 +699,74 @@ def test_longerusername():
errors.append('run "python manage.py migrate longerusername"')
print_errors(errors)
+def test_template_context_processors():
+ """makes sure that all necessary template context processors
+ are in the settings.py"""
+
+ required_processors = [
+ 'django.core.context_processors.request',
+ 'askbot.context.application_settings',
+ 'askbot.user_messages.context_processors.user_messages',
+ 'django.core.context_processors.csrf',
+ ]
+ old_auth_processor = 'django.core.context_processors.auth'
+ new_auth_processor = 'django.contrib.auth.context_processors.auth'
+
+ invalid_processors = list()
+ if django.VERSION[1] <= 3:
+ required_processors.append(old_auth_processor)
+ if new_auth_processor in django_settings.TEMPLATE_CONTEXT_PROCESSORS:
+ invalid_processors.append(new_auth_processor)
+ elif django.VERSION[1] > 3:
+ required_processors.append(new_auth_processor)
+ if old_auth_processor in django_settings.TEMPLATE_CONTEXT_PROCESSORS:
+ invalid_processors.append(old_auth_processor)
+
+ missing_processors = list()
+ for processor in required_processors:
+ if processor not in django_settings.TEMPLATE_CONTEXT_PROCESSORS:
+ missing_processors.append(processor)
+
+ errors = list()
+ if invalid_processors:
+ message = 'remove from TEMPLATE_CONTEXT_PROCESSORS in settings.py:\n'
+ message += format_as_text_tuple_entries(invalid_processors)
+ errors.append(message)
+
+ if missing_processors:
+ message = 'add to TEMPLATE_CONTEXT_PROCESSORS in settings.py:\n'
+ message += format_as_text_tuple_entries(missing_processors)
+ errors.append(message)
+
+ print_errors(errors)
+
+
+def test_cache_backend():
+ """prints a warning if cache backend is disabled or per-process"""
+ if django.VERSION[1] > 2:
+ backend = django_settings.CACHES['default']['BACKEND']
+ else:
+ backend = django_settings.CACHE_BACKEND
+
+ errors = list()
+ if backend.strip() == '' or 'dummy' in backend:
+ message = """Please enable at least a "locmem" cache (for a single process server).
+If you need to run > 1 server process, set up some production caching system,
+such as redis or memcached"""
+ errors.append(message)
+
+ if 'locmem' in backend:
+ message = """WARNING!!! You are using a 'locmem' (local memory) caching backend,
+which is OK for a low volume site running on a single-process server.
+For a multi-process configuration it is neccessary to have a production
+cache system, such as redis or memcached.
+
+With local memory caching and multi-process setup you might intermittently
+see outdated content on your site.
+"""
+ askbot_warning(message)
+
+
def test_group_messaging():
"""tests correctness of the "group_messaging" app configuration"""
errors = list()
@@ -717,6 +798,7 @@ def test_group_messaging():
if errors:
print_errors(errors)
+
def run_startup_tests():
"""function that runs
all startup tests, mainly checking settings config so far
@@ -731,6 +813,7 @@ def run_startup_tests():
test_middleware()
test_celery()
#test_csrf_cookie_domain()
+ test_template_context_processors()
test_tinymce()
test_staticfiles()
test_new_skins()
@@ -738,6 +821,7 @@ def run_startup_tests():
test_avatar()
test_group_messaging()
test_haystack()
+ test_cache_backend()
settings_tester = SettingsTester({
'CACHE_MIDDLEWARE_ANONYMOUS_ONLY': {
'value': True,
diff --git a/askbot/tasks.py b/askbot/tasks.py
index 505597d2..0ae96c09 100644
--- a/askbot/tasks.py
+++ b/askbot/tasks.py
@@ -24,6 +24,7 @@ import uuid
from django.contrib.contenttypes.models import ContentType
from django.template import Context
+from django.template.loader import get_template
from django.utils.translation import ugettext as _
from celery.decorators import task
from askbot.conf import settings as askbot_settings
@@ -80,7 +81,6 @@ def notify_author_of_published_revision_celery_task(revision):
}
#load the template
- from askbot.skins.loaders import get_template
template = get_template('email/notify_author_about_approved_post.html')
#todo: possibly add headers to organize messages in threads
headers = {'Reply-To': append_content_address}
@@ -191,7 +191,6 @@ def send_instant_notifications_about_activity_in_post(
return
#calculate some variables used in the loop below
- from askbot.skins.loaders import get_template
update_type_map = const.RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES
update_type = update_type_map[update_activity.activity_type]
origin_post = post.get_origin_post()
@@ -243,4 +242,3 @@ def send_instant_notifications_about_activity_in_post(
)
else:
logger.debug('success %s, logId=%s' % (user.email, log_id))
-
diff --git a/askbot/templates/404.html b/askbot/templates/404.html
index 158bfb94..2da99646 100644
--- a/askbot/templates/404.html
+++ b/askbot/templates/404.html
@@ -1,5 +1,44 @@
-{% load extra_tags %}
-{% include_jinja "404.jinja.html" request %}
-{% comment %}
-this one has to be a django template because of use of default hander404
-{% endcomment %}
+{% extends "one_column_body.html" %}
+<!-- template 404.jinja.html -->
+{% block title %}{% spaceless %}{% trans %}Page not found{% endtrans %}{% endspaceless %}{% endblock %}
+{% block forestyle%}
+<style type="text/css">
+ form input { margin-right: 5px; }
+</style>
+{% endblock %}
+{% block content %}
+<h1>{% trans %}Page not found{% endtrans %}</h1>
+<div id="main-body">
+ <div style="padding:5px 0px 10px 0;line-height:25px;">
+ <h2>{% trans %}Sorry, could not find the page you requested.{% endtrans %}</h2>
+ <div style="margin-top:5px">
+ {% trans %}This might have happened for the following reasons:{% endtrans %}<br/>
+ <ul>
+ <li>{% trans %}this question or answer has been deleted;{% endtrans %}</li>
+ <li>{% trans %}url has error - please check it;{% endtrans %}</li>
+ <li>{% trans %}the page you tried to visit is protected or you don't have sufficient points, see{% endtrans %} <a href="{% url faq %}">{% trans %}faq{% endtrans %}</a>;</li>
+ <li>{% trans %}if you believe this error 404 should not have occured, please{% endtrans %}
+ <a href="{{feedback_site_url}}" target="_blank">{% trans %}report this problem{% endtrans %}</a></li>
+ </u>
+ </div>
+ <script type="text/javascript">
+ var GOOG_FIXURL_LANG = '{{settings.LANGUAGE_CODE}}';
+ var GOOG_FIXURL_SITE = '{{site_url}}';
+ </script>
+ <script type="text/javascript" src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
+ <ul>
+ <li><a href="#" id="linkPrevious">{% trans %}back to previous page{% endtrans %} »</li>
+ <li><a href="{% url questions %}">{% trans %}see all questions{% endtrans %} »</a></li>
+ <li><a href="{% url tags %}">{% trans %}see all tags{% endtrans %} »</a></li>
+ </u>
+ </div>
+</div>
+{% endblock %}
+{% block endjs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#linkPrevious").bind("click", back=function(){history.go(-1);})
+ });
+ </script>
+{% endblock %}
+<!-- end template 404.jinja.html -->
diff --git a/askbot/templates/404.jinja.html b/askbot/templates/404.jinja.html
deleted file mode 100644
index 2da99646..00000000
--- a/askbot/templates/404.jinja.html
+++ /dev/null
@@ -1,44 +0,0 @@
-{% extends "one_column_body.html" %}
-<!-- template 404.jinja.html -->
-{% block title %}{% spaceless %}{% trans %}Page not found{% endtrans %}{% endspaceless %}{% endblock %}
-{% block forestyle%}
-<style type="text/css">
- form input { margin-right: 5px; }
-</style>
-{% endblock %}
-{% block content %}
-<h1>{% trans %}Page not found{% endtrans %}</h1>
-<div id="main-body">
- <div style="padding:5px 0px 10px 0;line-height:25px;">
- <h2>{% trans %}Sorry, could not find the page you requested.{% endtrans %}</h2>
- <div style="margin-top:5px">
- {% trans %}This might have happened for the following reasons:{% endtrans %}<br/>
- <ul>
- <li>{% trans %}this question or answer has been deleted;{% endtrans %}</li>
- <li>{% trans %}url has error - please check it;{% endtrans %}</li>
- <li>{% trans %}the page you tried to visit is protected or you don't have sufficient points, see{% endtrans %} <a href="{% url faq %}">{% trans %}faq{% endtrans %}</a>;</li>
- <li>{% trans %}if you believe this error 404 should not have occured, please{% endtrans %}
- <a href="{{feedback_site_url}}" target="_blank">{% trans %}report this problem{% endtrans %}</a></li>
- </u>
- </div>
- <script type="text/javascript">
- var GOOG_FIXURL_LANG = '{{settings.LANGUAGE_CODE}}';
- var GOOG_FIXURL_SITE = '{{site_url}}';
- </script>
- <script type="text/javascript" src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
- <ul>
- <li><a href="#" id="linkPrevious">{% trans %}back to previous page{% endtrans %} »</li>
- <li><a href="{% url questions %}">{% trans %}see all questions{% endtrans %} »</a></li>
- <li><a href="{% url tags %}">{% trans %}see all tags{% endtrans %} »</a></li>
- </u>
- </div>
-</div>
-{% endblock %}
-{% block endjs %}
- <script type="text/javascript">
- $().ready(function(){
- $("#linkPrevious").bind("click", back=function(){history.go(-1);})
- });
- </script>
-{% endblock %}
-<!-- end template 404.jinja.html -->
diff --git a/askbot/templates/500.jinja.html b/askbot/templates/500.html
index 297ae736..297ae736 100644
--- a/askbot/templates/500.jinja.html
+++ b/askbot/templates/500.html
diff --git a/askbot/templates/answer_edit.html b/askbot/templates/answer_edit.html
index 8c3687f1..20c0684d 100644
--- a/askbot/templates/answer_edit.html
+++ b/askbot/templates/answer_edit.html
@@ -28,7 +28,10 @@
{% if settings.WIKI_ON and answer.wiki == False %}
{{ macros.checkbox_in_div(form.wiki) }}
{% endif %}
- {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {% if settings.GROUPS_ENABLED and
+ request.user.is_authenticated() and
+ request.user.can_make_group_private_posts()
+ %}
{{ macros.checkbox_in_div(form.post_privately) }}
{% endif %}
<div class="after-editor">
diff --git a/askbot/templates/main_page/tab_bar.html b/askbot/templates/main_page/tab_bar.html
index 17ab810e..37f63012 100644
--- a/askbot/templates/main_page/tab_bar.html
+++ b/askbot/templates/main_page/tab_bar.html
@@ -1,6 +1,7 @@
{% import "macros.html" as macros %}
{% load extra_filters_jinja %}
{% cache 0 "scope_sort_tabs" search_tags request.user author_name scope sort query context.page language_code %}
+{% if settings.RSS_ENABLED %}
<a class="rss"
{% if feed_url %}
href="{{feed_url}}"
@@ -10,6 +11,7 @@
title="{% trans %}subscribe to the questions feed{% endtrans %}"
>{% trans %}RSS{% endtrans %}
</a>
+{% endif %}
<div class="tabBar">
<div id="sort_tabs" class="tabsA">
<span class="label">{% trans %}Sort by &raquo;{% endtrans %}</span>
diff --git a/askbot/templates/question/new_answer_form.html b/askbot/templates/question/new_answer_form.html
index 76772abf..bc51f44a 100644
--- a/askbot/templates/question/new_answer_form.html
+++ b/askbot/templates/question/new_answer_form.html
@@ -51,7 +51,10 @@
{% if settings.WIKI_ON %}
{{ macros.checkbox_in_div(answer.wiki) }}
{% endif %}
- {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {% if settings.GROUPS_ENABLED and
+ request.user.is_authenticated() and
+ request.user.can_make_group_private_posts()
+ %}
{{ macros.checkbox_in_div(answer.post_privately) }}
{% endif %}
{% endif %}
diff --git a/askbot/templates/question/sidebar.html b/askbot/templates/question/sidebar.html
index 4d431ef2..905ce781 100644
--- a/askbot/templates/question/sidebar.html
+++ b/askbot/templates/question/sidebar.html
@@ -39,12 +39,14 @@
<input type="checkbox" id="question-subscribe-sidebar"/>
<label for="question-subscribe-sidebar">{% trans %}<strong>Here</strong> (once you log in) you will be able to sign up for the periodic email updates about this question.{% endtrans %}</label>
{%endif%}
+ {% if settings.RSS_ENABLED %}
<p class="rss">
<a
href="{{settings.APP_URL}}/feeds/question/{{ question.id }}"
title="{% trans %}subscribe to this question rss feed{% endtrans %}"
>{% trans %}subscribe to rss feed{% endtrans %}</a>
</p>
+ {% endif %}
</div>
</div>
diff --git a/askbot/templates/question_edit.html b/askbot/templates/question_edit.html
index f176b11d..8b049e55 100644
--- a/askbot/templates/question_edit.html
+++ b/askbot/templates/question_edit.html
@@ -38,7 +38,10 @@
{% if form.can_stay_anonymous() %}
{{ macros.checkbox_in_div(form.reveal_identity) }}
{% endif %}
- {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {% if settings.GROUPS_ENABLED and
+ request.user.is_authenticated() and
+ request.user.can_make_group_private_posts()
+ %}
{{ macros.checkbox_in_div(form.post_privately) }}
{% endif %}
</div>
diff --git a/askbot/templates/widgets/ask_form.html b/askbot/templates/widgets/ask_form.html
index d528609f..0f851fee 100644
--- a/askbot/templates/widgets/ask_form.html
+++ b/askbot/templates/widgets/ask_form.html
@@ -39,7 +39,10 @@
{% if settings.ALLOW_ASK_ANONYMOUSLY %}
{{ macros.checkbox_in_div(form.ask_anonymously) }}
{% endif %}
- {% if request.user.is_authenticated() and request.user.can_make_group_private_posts() %}
+ {% if settings.GROUPS_ENABLED and
+ request.user.is_authenticated() and
+ request.user.can_make_group_private_posts()
+ %}
{{ macros.checkbox_in_div(form.post_privately) }}
{% endif %}
</div>
diff --git a/askbot/templatetags/extra_tags.py b/askbot/templatetags/extra_tags.py
index dc9da5fc..6b148b4b 100644
--- a/askbot/templatetags/extra_tags.py
+++ b/askbot/templatetags/extra_tags.py
@@ -1,11 +1,12 @@
import math
from django import template
+from django.template import RequestContext
+from django.template.loader import get_template
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from askbot.utils import functions
from askbot.utils.slug import slugify
-from askbot.skins.loaders import get_template
from askbot.conf import settings as askbot_settings
register = template.Library()
@@ -90,8 +91,8 @@ class IncludeJinja(template.Node):
self.request_var = template.Variable(request_var)
def render(self, context):
request = self.request_var.resolve(context)
- jinja_template = get_template(self.filename, request)
- return jinja_template.render(context)
+ jinja_template = get_template(self.filename)
+ return jinja_template.render(RequestContext(request, context))
@register.tag
def include_jinja(parser, token):
@@ -112,4 +113,3 @@ def include_jinja(parser, token):
raise template.TemplateSyntaxError('file name must be quoted')
return IncludeJinja(filename, request_var)
-
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index f4ae052b..bdb361e5 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -1037,7 +1037,11 @@ class PostApprovalTests(utils.AskbotTestCase):
class AbsolutizeUrlsInEmailsTests(utils.AskbotTestCase):
- @with_settings(MIN_REP_TO_TRIGGER_EMAIL=1, APP_URL='http://example.com/')
+ @with_settings(
+ MIN_REP_TO_TRIGGER_EMAIL=1,
+ APP_URL='http://example.com/',
+ MIN_REP_TO_INSERT_LINK=1
+ )
def test_urls_are_absolute(self):
u1 = self.create_user('u1')
max_email = models.EmailFeedSetting.MAX_EMAIL_SCHEDULE
diff --git a/askbot/tests/email_parsing_tests.py b/askbot/tests/email_parsing_tests.py
index 905bff0a..3ed0908a 100644
--- a/askbot/tests/email_parsing_tests.py
+++ b/askbot/tests/email_parsing_tests.py
@@ -1,11 +1,12 @@
+# -*- coding: utf-8 -*-
from django.conf import settings as django_settings
-from askbot.skins.loaders import get_template
from django.template import Context
+from django.template.loader import get_template
from askbot import mail
from askbot import models
from askbot.tests import utils
-class EmailParseTests(utils.AskbotTestCase):
+class EmailParsingTests(utils.AskbotTestCase):
def setUp(self):
self.template_name = 'email/welcome_lamson_on.html'
@@ -22,4 +23,38 @@ class EmailParseTests(utils.AskbotTestCase):
print '=================================================='
print cleaned_body
print "CLEANED BODY"
- self.assertEquals(cleaned_body, self.expected_output)
+ self.assertEqual(cleaned_body, self.expected_output)
+
+ def test_gmail_rich_text_response_stripped(self):
+ text = u'\n\nthis is my reply!\n\nOn Wed, Oct 31, 2012 at 1:45 AM, <kp@kp-dev.askbot.com> wrote:\n\n> **\n> '
+ self.assertEqual(mail.extract_reply(text), 'this is my reply!')
+
+ def test_gmail_plain_text_response_stripped(self):
+ text = u'\n\nthis is my another reply!\n\nOn Wed, Oct 31, 2012 at 1:45 AM, <kp@kp-dev.askbot.com> wrote:\n>\n> '
+ self.assertEqual(mail.extract_reply(text), 'this is my another reply!')
+
+ def test_yahoo_mail_response_stripped(self):
+ text = u'\n\nthis is my reply!\n\n\n\n________________________________\n From: "kp@kp-dev.askbot.com" <kp@kp-dev.askbot.com>\nTo: fadeev@rocketmail.com \nSent: Wednesday, October 31, 2012 2:41 AM\nSubject: "This is my test question"\n \n\n \n \n \n'
+ self.assertEqual(mail.extract_reply(text), 'this is my reply!')
+
+ def test_kmail_plain_text_response_stripped(self):
+ text = u'On Monday 01 October 2012 21:22:44 you wrote: \n\nthis is my reply!'
+ self.assertEqual(mail.extract_reply(text), 'this is my reply!')
+
+ def test_outlook_com_with_rtf_response_stripped(self):
+ text = u'outlook.com (new hotmail) with RTF on \n\nSubject: "Posting a question by email." \nFrom: kp@kp-dev.askbot.com \nTo: aj_fitoria@hotmail.com \nDate: Thu, 1 Nov 2012 16:30:27 +0000'
+ self.assertEqual(
+ mail.extract_reply(text),
+ 'outlook.com (new hotmail) with RTF on'
+ )
+ self.assertEqual(
+ mail.extract_reply(text),
+ 'outlook.com (new hotmail) with RTF on'
+ )
+
+ def test_outlook_com_plain_text_response_stripped(self):
+ text = u'reply from hotmail without RTF \n________________________________ \n> Subject: "test with recovered signature" \n> From: kp@kp-dev.askbot.com \n> To: aj_fitoria@hotmail.com \n> Date: Thu, 1 Nov 2012 16:44:35 +0000'
+ self.assertEqual(
+ mail.extract_reply(text),
+ u'reply from hotmail without RTF'
+ )
diff --git a/askbot/tests/form_tests.py b/askbot/tests/form_tests.py
index 90f4f4f2..fed38f87 100644
--- a/askbot/tests/form_tests.py
+++ b/askbot/tests/form_tests.py
@@ -1,5 +1,7 @@
from django import forms as django_forms
+from django.contrib.auth.models import AnonymousUser
from askbot.tests.utils import AskbotTestCase
+from askbot.tests.utils import with_settings
from askbot.conf import settings as askbot_settings
from askbot import forms
from askbot.utils import forms as util_forms
@@ -53,9 +55,10 @@ class AskByEmailFormTests(AskbotTestCase):
and makes sure that tags and title are parsed out"""
setting_backup = askbot_settings.TAGS_ARE_REQUIRED
askbot_settings.update('TAGS_ARE_REQUIRED', True)
+ user = AnonymousUser()
for test_case in SUBJECT_LINE_CASES:
self.data['subject'] = test_case[0]
- form = forms.AskByEmailForm(self.data)
+ form = forms.AskByEmailForm(self.data, user=user)
output = test_case[1]
if output is None:
self.assertFalse(form.is_valid())
@@ -75,10 +78,11 @@ class AskByEmailFormTests(AskbotTestCase):
"""loops through variants of the from field
in the emails and tests the email address
extractor"""
+ user = AnonymousUser()
for test_case in EMAIL_CASES:
self.data['sender'] = test_case[0]
expected_result = test_case[1]
- form = forms.AskByEmailForm(self.data)
+ form = forms.AskByEmailForm(self.data, user=user)
if expected_result is None:
self.assertFalse(form.is_valid())
else:
@@ -196,9 +200,9 @@ class EditQuestionAnonymouslyFormTests(AskbotTestCase):
data['reveal_identity'] = 'on'
self.form = forms.EditQuestionForm(
data,
- question = question,
- user = editor,
- revision = question.get_latest_revision(),
+ question=question,
+ user=editor,
+ revision=question.get_latest_revision(),
)
def test_reveal_identity_field(self):
@@ -230,7 +234,7 @@ class AskFormTests(AskbotTestCase):
}
if ask_anonymously == True:
data['ask_anonymously'] = 'on'
- self.form = forms.AskForm(data)
+ self.form = forms.AskForm(data, user=AnonymousUser())
self.form.full_clean()
def assert_anon_is(self, value):
@@ -317,7 +321,7 @@ class AnswerEditorFieldTests(AskbotTestCase):
def setUp(self):
self.old_min_length = askbot_settings.MIN_ANSWER_BODY_LENGTH
askbot_settings.update('MIN_ANSWER_BODY_LENGTH', 10)
- self.field = forms.AnswerEditorField()
+ self.field = forms.AnswerEditorField(user=AnonymousUser())
def tearDown(self):
askbot_settings.update('MIN_ANSWER_BODY_LENGTH', self.old_min_length)
@@ -364,8 +368,6 @@ class PostAsSomeoneFormTests(AskbotTestCase):
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?",
@@ -374,12 +376,40 @@ class AskWidgetFormTests(AskbotTestCase):
self.bad_data = {'title': ''}
def test_valid_input(self):
- form_object = self.form(include_text=False, data=self.good_data)
- print form_object.errors
+ form_object = forms.AskWidgetForm(
+ include_text=False, data=self.good_data, user=AnonymousUser()
+ )
self.assertTrue(form_object.is_valid())
- form_object = self.form(include_text=False, data=self.good_data_anon)
+ form_object = forms.AskWidgetForm(
+ include_text=False, data=self.good_data_anon, user=AnonymousUser()
+ )
self.assertTrue(form_object.is_valid())
def test_invalid_input(self):
- form_object = self.form(False, data=self.bad_data)
+ form_object = forms.AskWidgetForm(
+ include_text=False, data=self.bad_data, user=AnonymousUser()
+ )
self.assertFalse(form_object.is_valid())
+
+
+TEXT_WITH_LINK = 'blah blah http://example.com/url/image.png'
+class EditorFieldTests(AskbotTestCase):
+
+ def setUp(self):
+ self.user = self.create_user('user')
+ self.user.reputation = 5
+ self.user.save()
+
+ @with_settings(EDITOR_TYPE='markdown', MIN_REP_TO_SUGGEST_LINK=10)
+ def test_low_rep_user_cannot_post_links_markdown(self):
+ field = forms.EditorField(user=self.user)
+ self.assertRaises(
+ django_forms.ValidationError, field.clean, TEXT_WITH_LINK
+ )
+
+ @with_settings(EDITOR_TYPE='tinymce', MIN_REP_TO_SUGGEST_LINK=10)
+ def test_low_rep_user_cannot_post_links_tinymce(self):
+ field = forms.EditorField(user=self.user)
+ self.assertRaises(
+ django_forms.ValidationError, field.clean, TEXT_WITH_LINK
+ )
diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py
index 4efac0f0..6c820fef 100644
--- a/askbot/tests/page_load_tests.py
+++ b/askbot/tests/page_load_tests.py
@@ -194,19 +194,17 @@ class PageLoadTestCase(AskbotTestCase):
'get_groups_list',
status_code=status_code
)
+ #self.try_url(
+ # 'individual_question_feed',
+ # kwargs={'pk':'one-tag'},
+ # status_code=status_code)
self.try_url(
- 'feeds',
- status_code=status_code,
- kwargs={'url':'rss'})
+ 'latest_questions_feed',
+ status_code=status_code)
self.try_url(
- 'feeds',
- kwargs={'url':'rss'},
+ 'latest_questions_feed',
data={'tags':'one-tag'},
status_code=status_code)
- #self.try_url(
- # 'feeds',
- # kwargs={'url':'question'},
- # status_code=status_code)
self.try_url(
'about',
status_code=status_code,
diff --git a/askbot/tests/post_model_tests.py b/askbot/tests/post_model_tests.py
index e61fcd2d..031909fb 100644
--- a/askbot/tests/post_model_tests.py
+++ b/askbot/tests/post_model_tests.py
@@ -3,13 +3,13 @@ import datetime
from operator import attrgetter
import time
from askbot.search.state_manager import SearchState
-from askbot.skins.loaders import get_template
from django.contrib.auth.models import User
from django.core import cache, urlresolvers
from django.core.cache.backends.dummy import DummyCache
from django.core.cache.backends.locmem import LocMemCache
from django.core.exceptions import ValidationError
+from django.template.loader import get_template
from askbot.tests.utils import AskbotTestCase
from askbot.models import Post
from askbot.models import PostRevision
diff --git a/askbot/tests/reply_by_email_tests.py b/askbot/tests/reply_by_email_tests.py
index 30cb48be..5353586c 100644
--- a/askbot/tests/reply_by_email_tests.py
+++ b/askbot/tests/reply_by_email_tests.py
@@ -84,8 +84,8 @@ class ReplyAddressModelTests(AskbotTestCase):
'instruction': 'reply above this line'
}
msg = MockMessage(
- "This is a test reply \n\nOn such and such someone"
- "wrote something \n\n%s\nlorem ipsum " % (reply_separator),
+ "This is a test reply \n\nOn such and such someone "
+ "wrote: \n\n%s\nlorem ipsum " % (reply_separator),
"user1@domain.com"
)
msg['Subject'] = 'test subject'
diff --git a/askbot/tests/utils_tests.py b/askbot/tests/utils_tests.py
index c8526ad1..ed845f48 100644
--- a/askbot/tests/utils_tests.py
+++ b/askbot/tests/utils_tests.py
@@ -2,6 +2,7 @@ from django.test import TestCase
from askbot.tests.utils import with_settings
from askbot.utils.url_utils import urls_equal
from askbot.utils.html import absolutize_urls
+from askbot.utils.html import replace_links_with_text
from askbot.conf import settings as askbot_settings
class UrlUtilsTests(TestCase):
@@ -17,11 +18,58 @@ class UrlUtilsTests(TestCase):
self.assertTrue(e('http://cnn.com/path', 'http://cnn.com/path/', True))
self.assertFalse(e('http://cnn.com/path', 'http://cnn.com/path/'))
-
+
+
+class ReplaceLinksWithTextTests(TestCase):
+ """testing correctness of `askbot.utils.html.replace_links_with_text"""
+
+ def test_local_link_not_replaced(self):
+ text = '<a href="/some-link">some link</a>'
+ self.assertEqual(replace_links_with_text(text), text)
+
+ def test_link_without_url_replaced(self):
+ text = '<a>some link</a>'
+ self.assertEqual(replace_links_with_text(text), 'some link')
+
+ def test_external_link_without_text_replaced(self):
+ text = '<a href="https://example.com/"></a>'
+ #in this case we delete the link
+ self.assertEqual(replace_links_with_text(text), '')
+
+ def test_external_link_with_text_replaced(self):
+ text = '<a href="https://example.com/">some link</a>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ 'https://example.com/ (some link)'
+ )
+
+ def test_local_image_not_replaced(self):
+ text = '<img src="/some-image.gif"/>'
+ self.assertEqual(replace_links_with_text(text), text)
+
+ def test_local_url_with_hotlinked_image_replaced(self):
+ text = '<a href="/some-link"><img src="http://example.com/img.png" alt="picture""> some text</a>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ '<a href="/some-link">http://example.com/img.png (picture) some text</a>'
+ )
+
+ def test_hotlinked_image_without_alt_replaced(self):
+ text = '<img src="https://example.com/some-image.gif"/>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ 'https://example.com/some-image.gif'
+ )
+
+ def test_hotlinked_image_with_alt_replaced(self):
+ text = '<img src="https://example.com/some-image.gif" alt="picture"/>'
+ self.assertEqual(
+ replace_links_with_text(text),
+ 'https://example.com/some-image.gif (picture)'
+ )
class HTMLUtilsTests(TestCase):
"""tests for :mod:`askbot.utils.html` module"""
-
@with_settings(APP_URL='http://example.com')
def test_absolutize_urls(self):
text = """<img class="junk" src="/some.gif"> <img class="junk" src="/cat.gif"> <IMG SRC='/some.png'>"""
diff --git a/askbot/urls.py b/askbot/urls.py
index 362a16ee..4694b38c 100644
--- a/askbot/urls.py
+++ b/askbot/urls.py
@@ -2,6 +2,7 @@
askbot askbot url configuraion file
"""
import os.path
+import django
from django.conf import settings
from django.conf.urls.defaults import url, patterns, include
from django.conf.urls.defaults import handler500, handler404
@@ -218,6 +219,11 @@ urlpatterns = patterns('',
views.meta.list_suggested_tags,
name = 'list_suggested_tags'
),
+
+ #feeds
+ url(r'^feeds/rss/$', RssLastestQuestionsFeed(), name="latest_questions_feed"),
+ url(r'^feeds/question/(?P<pk>\d+)/$', RssIndividualQuestionFeed(), name="individual_question_feed"),
+
url(#ajax only
r'^%s$' % 'moderate-suggested-tag',
views.commands.moderate_suggested_tag,
@@ -473,16 +479,9 @@ urlpatterns = patterns('',
views.widgets.question_widget,
name = 'question_widget'
),
- url(
- r'^feeds/(?P<url>.*)/$',
- 'django.contrib.syndication.views.feed',
- {'feed_dict': feeds},
- name='feeds'
- ),
#upload url is ajax only
url( r'^%s$' % _('upload/'), views.writers.upload, name='upload'),
url(r'^%s$' % _('feedback/'), views.meta.feedback, name='feedback'),
- #url(r'^feeds/rss/$', RssLastestQuestionsFeed, name="latest_questions_feed"),
url(
r'^doc/(?P<path>.*)$',
'django.views.static.serve',
diff --git a/askbot/utils/console.py b/askbot/utils/console.py
index a691d961..0c27cd23 100644
--- a/askbot/utils/console.py
+++ b/askbot/utils/console.py
@@ -91,7 +91,8 @@ class ProgressBar(object):
self.iterable = iterable
self.length = length
self.counter = float(0)
- self.barlen = 60
+ self.max_barlen = 60
+ self.curr_barlen = 0
self.progress = ''
if message and length > 0:
print message
@@ -103,17 +104,33 @@ class ProgressBar(object):
def print_progress_bar(self):
"""prints the progress bar"""
- sys.stdout.write('\b'*len(self.progress))
+ self.backspace_progress_percent()
+
+ tics_to_write = 0
+ if self.length < self.max_barlen:
+ tics_to_write = self.max_barlen/self.length
+ elif int(self.counter) % (self.length/self.max_barlen) == 0:
+ tics_to_write = 1
+
+ if self.curr_barlen + tics_to_write <= self.max_barlen:
+ sys.stdout.write('-' * tics_to_write)
+ self.curr_barlen += tics_to_write
- if self.length < self.barlen:
- sys.stdout.write('-'*(self.barlen/self.length))
- elif int(self.counter) % (self.length / self.barlen) == 0:
- sys.stdout.write('-')
+ self.print_progress_percent()
+
+ def backspace_progress_percent(self):
+ sys.stdout.write('\b'*len(self.progress))
+ def print_progress_percent(self):
+ """prints percent of achieved progress"""
self.progress = ' %.2f%%' % (100 * (self.counter/self.length))
sys.stdout.write(self.progress)
sys.stdout.flush()
+ def finish_progress_bar(self):
+ """brint the last bars, to make all bars equal length"""
+ self.backspace_progress_percent()
+ sys.stdout.write('-' * (self.max_barlen - self.curr_barlen))
def next(self):
@@ -121,7 +138,8 @@ class ProgressBar(object):
result = self.iterable.next()
except StopIteration:
if self.length > 0:
- self.print_progress_bar()
+ self.finish_progress_bar()
+ self.print_progress_percent()
sys.stdout.write('\n')
raise
diff --git a/askbot/utils/html.py b/askbot/utils/html.py
index 49eddee2..1d76fdb7 100644
--- a/askbot/utils/html.py
+++ b/askbot/utils/html.py
@@ -1,11 +1,11 @@
"""Utilities for working with HTML."""
+from bs4 import BeautifulSoup
import html5lib
from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers
import re
import htmlentitydefs
from urlparse import urlparse
from django.core.urlresolvers import reverse
-from django.utils.html import escape
from askbot.conf import settings as askbot_settings
class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
@@ -55,12 +55,47 @@ def absolutize_urls(html):
img_replacement = '\g<prefix>"%s/\g<url>" style="max-width:500px;"' % askbot_settings.APP_URL
replacement = '\g<prefix>"%s\g<url>"' % askbot_settings.APP_URL
html = url_re1.sub(img_replacement, html)
- html= url_re2.sub(img_replacement, html)
+ html = url_re2.sub(img_replacement, html)
html = url_re3.sub(replacement, html)
#temporal fix for bad regex with wysiwyg editor
return url_re4.sub(replacement, html).replace('%s//' % askbot_settings.APP_URL,
'%s/' % askbot_settings.APP_URL)
+def replace_links_with_text(html):
+ """any absolute links will be replaced with the
+ url in plain text, same with any img tags
+ """
+ def format_url_replacement(url, text):
+ url = url.strip()
+ text = text.strip()
+ url_domain = urlparse(url).netloc
+ if url and text and url_domain != text and url != text:
+ return '%s (%s)' % (url, text)
+ return url or text or ''
+
+ soup = BeautifulSoup(html)
+ abs_url_re = r'^http(s)?://'
+
+ images = soup.find_all('img')
+ for image in images:
+ url = image.get('src', '')
+ text = image.get('alt', '')
+ if url == '' or re.match(abs_url_re, url):
+ image.replaceWith(format_url_replacement(url, text))
+
+ links = soup.find_all('a')
+ for link in links:
+ url = link.get('href', '')
+ text = ''.join(link.text) or ''
+
+ if text == '':#this is due to an issue with url inlining in comments
+ link.replaceWith('')
+ elif url == '' or re.match(abs_url_re, url):
+ link.replaceWith(format_url_replacement(url, text))
+
+ return unicode(soup.find('body').renderContents(), 'utf-8')
+
+
def sanitize_html(html):
"""Sanitizes an HTML fragment."""
p = html5lib.HTMLParser(tokenizer=HTMLSanitizer,
diff --git a/askbot/views/avatar_views.py b/askbot/views/avatar_views.py
index 42d0b38a..94252860 100644
--- a/askbot/views/avatar_views.py
+++ b/askbot/views/avatar_views.py
@@ -1,10 +1,15 @@
-"""this is an unfortunate copy-paste (mostly)
+"""
+todo: remove this module - not needed any more
+
+this is an unfortunate copy-paste (mostly)
from the avatar app - the reason is that django-avatar app
does not support jinja templates
"""
import urllib
from django.http import HttpResponseRedirect
from django.template import RequestContext
+from django.template.loader import get_template
+from django.shortcuts import render
from django.utils.translation import ugettext as _
from django.views.decorators import csrf
from django.conf import settings
@@ -17,7 +22,6 @@ from avatar.settings import AVATAR_MAX_AVATARS_PER_USER, AVATAR_DEFAULT_SIZE
from avatar.util import get_primary_avatar, get_default_avatar_url
from avatar.views import render_primary as django_avatar_render_primary
-from askbot.skins.loaders import render_into_skin
from askbot import models
notification = False
@@ -111,7 +115,7 @@ def add(request, extra_context=None, next_override=None,
if extra_context:
data.update(extra_context)
- return render_into_skin('avatar/add.html', data, request)
+ return render(request, 'avatar/add.html', data)
@login_required
@csrf.csrf_protect
@@ -153,7 +157,7 @@ def change(request, extra_context=None, next_override=None,
if extra_context:
data.update(extra_context)
- return render_into_skin('avatar/change.html', data, request)
+ return render(request, 'avatar/change.html', data)
@login_required
@csrf.csrf_protect
@@ -190,7 +194,7 @@ def delete(request, extra_context=None, next_override=None, *args, **kwargs):
if extra_context:
data.update(extra_context)
- return render_into_skin('avatar/confirm_delete.html', data, request)
+ return render(request, 'avatar/confirm_delete.html', data)
def render_primary(request, user_id = None, *args, **kwargs):
user = models.User.objects.get(id = user_id)
diff --git a/askbot/views/commands.py b/askbot/views/commands.py
index 698bf2db..de5bb12b 100644
--- a/askbot/views/commands.py
+++ b/askbot/views/commands.py
@@ -20,6 +20,8 @@ from django.http import HttpResponseRedirect
from django.http import HttpResponseForbidden
from django.forms import ValidationError, IntegerField, CharField
from django.shortcuts import get_object_or_404
+from django.shortcuts import render
+from django.template.loader import get_template
from django.views.decorators import csrf
from django.utils import simplejson
from django.utils.html import escape
@@ -35,8 +37,7 @@ from askbot.utils import decorators
from askbot.utils import url_utils
from askbot.utils.forms import get_db_object_or_404
from askbot import mail
-from django.template import Context
-from askbot.skins.loaders import render_into_skin, get_template
+from django.template import RequestContext
from askbot.skins.loaders import render_into_skin_as_string
from askbot.skins.loaders import render_text_into_skin
from askbot import const
@@ -115,7 +116,7 @@ def manage_inbox(request):
'post': post.html,
'reject_reason': reject_reason.details.html
}
- body_text = template.render(Context(data))
+ body_text = template.render(RequestContext(request, data))
mail.send_mail(
subject_line = _('your post was not accepted'),
body_text = unicode(body_text),
@@ -684,7 +685,7 @@ def subscribe_for_tags(request):
return HttpResponseRedirect(reverse('index'))
else:
data = {'tags': tag_names}
- return render_into_skin('subscribe_for_tags.html', data, request)
+ return render(request, 'subscribe_for_tags.html', data)
else:
all_tag_names = pure_tag_names + wildcards
message = _('Please sign in to subscribe for: %(tags)s') \
@@ -769,7 +770,7 @@ def close(request, id):#close question
'question': question,
'form': form,
}
- return render_into_skin('close.html', data, request)
+ return render(request, 'close.html', data)
except exceptions.PermissionDenied, e:
request.user.message_set.create(message = unicode(e))
return HttpResponseRedirect(question.get_absolute_url())
@@ -798,7 +799,7 @@ def reopen(request, id):#re-open question
'closed_by_profile_url': closed_by_profile_url,
'closed_by_username': closed_by_username,
}
- return render_into_skin('reopen.html', data, request)
+ return render(request, 'reopen.html', data)
except exceptions.PermissionDenied, e:
request.user.message_set.create(message = unicode(e))
@@ -1348,7 +1349,7 @@ def get_editor(request):
if 'config' not in request.GET:
return HttpResponseForbidden()
config = simplejson.loads(request.GET['config'])
- form = forms.EditorForm(editor_attrs=config)
+ form = forms.EditorForm(editor_attrs=config, user=request.user)
editor_html = render_text_into_skin(
'{{ form.media }} {{ form.editor }}',
{'form': form},
diff --git a/askbot/views/meta.py b/askbot/views/meta.py
index 7b271219..bb152d5b 100644
--- a/askbot/views/meta.py
+++ b/askbot/views/meta.py
@@ -6,7 +6,9 @@ This module contains a collection of views displaying all sorts of secondary and
from django.shortcuts import render_to_response, get_object_or_404
from django.core.urlresolvers import reverse
from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.shortcuts import render
from django.template import RequestContext, Template
+from django.template.loader import get_template
from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
@@ -21,16 +23,16 @@ from askbot.utils.forms import get_next_url
from askbot.mail import mail_moderators
from askbot.models import BadgeData, Award, User, Tag
from askbot.models import badges as badge_data
-from askbot.skins.loaders import get_template, render_into_skin, render_text_into_skin
+from askbot.skins.loaders import render_text_into_skin
from askbot.utils.decorators import admins_only
from askbot.utils.forms import get_next_url
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"""
+ """this may be not necessary, since it is just a rewrite of render"""
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)
+ return render(request, template, {'page_class': page_class})
def config_variable(request, variable_name = None, mimetype = None):
"""Print value from the configuration settings
@@ -47,7 +49,7 @@ def about(request, template='about.html'):
'page_class': 'meta',
'content': askbot_settings.FORUM_ABOUT
}
- return render_into_skin('static_page.html', data, request)
+ return render(request, 'static_page.html', data)
def page_not_found(request, template='404.html'):
return generic_view(request, template)
@@ -60,7 +62,7 @@ def help(request):
'app_name': askbot_settings.APP_SHORT_NAME,
'page_class': 'meta'
}
- return render_into_skin('help.html', data, request)
+ return render(render, 'help.html', data)
def faq(request):
if askbot_settings.FORUM_FAQ.strip() != '':
@@ -69,18 +71,14 @@ def faq(request):
'content': askbot_settings.FORUM_FAQ,
'page_class': 'meta',
}
- return render_into_skin(
- 'static_page.html',
- data,
- request
- )
+ return render(request, 'static_page.html', data)
else:
data = {
'gravatar_faq_url': reverse('faq') + '#gravatar',
'ask_question_url': reverse('ask'),
'page_class': 'meta',
}
- return render_into_skin('faq_static.html', data, request)
+ return render(request, 'faq_static.html', data)
@csrf.csrf_protect
def feedback(request):
@@ -100,13 +98,26 @@ def feedback(request):
data=request.POST
)
if form.is_valid():
+
if not request.user.is_authenticated():
- data['email'] = form.cleaned_data.get('email',None)
+ data['email'] = form.cleaned_data.get('email', None)
+ else:
+ data['email'] = request.user.email
+
data['message'] = form.cleaned_data['message']
- data['name'] = form.cleaned_data.get('name',None)
- template = get_template('email/feedback_email.txt', request)
+ data['name'] = form.cleaned_data.get('name', None)
+ template = get_template('email/feedback_email.txt')
message = template.render(RequestContext(request, data))
- mail_moderators(_('Q&A forum feedback'), message)
+
+ headers = {}
+ if data['email']:
+ headers = {'Reply-To': data['email']}
+
+ mail_moderators(
+ _('Q&A forum feedback'),
+ message,
+ headers=headers
+ )
msg = _('Thanks for the feedback!')
request.user.message_set.create(message=msg)
return HttpResponseRedirect(get_next_url(request))
@@ -115,7 +126,7 @@ def feedback(request):
initial={'next':get_next_url(request)})
data['form'] = form
- return render_into_skin('feedback.html', data, request)
+ return render(request, 'feedback.html', data)
feedback.CANCEL_MESSAGE=_('We look forward to hearing your feedback! Please, give it next time :)')
def privacy(request):
@@ -124,7 +135,7 @@ def privacy(request):
'page_class': 'meta',
'content': askbot_settings.FORUM_PRIVACY
}
- return render_into_skin('static_page.html', data, request)
+ return render(request, 'static_page.html', data)
def badges(request):#user status/reputation system
#todo: supplement database data with the stuff from badges.py
@@ -148,7 +159,7 @@ def badges(request):#user status/reputation system
'mybadges' : my_badges,
'feedback_faq_url' : reverse('feedback'),
}
- return render_into_skin('badges.html', data, request)
+ return render(request, 'badges.html', data)
def badge(request, id):
#todo: supplement database data with the stuff from badges.py
@@ -169,7 +180,7 @@ def badge(request, id):
'badge' : badge,
'page_class': 'meta',
}
- return render_into_skin('badge.html', data, request)
+ return render(request, 'badge.html', data)
@admins_only
def list_suggested_tags(request):
@@ -209,4 +220,4 @@ def list_suggested_tags(request):
'page_title': _('Suggested tags'),
'paginator_context' : paginator_context,
}
- return render_into_skin('list_suggested_tags.html', data, request)
+ return render(request, 'list_suggested_tags.html', data)
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index 5a3e378b..f1b31b32 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -11,9 +11,11 @@ import logging
import urllib
import operator
from django.shortcuts import get_object_or_404
+from django.shortcuts import render
from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpResponseNotAllowed
from django.core.paginator import Paginator, EmptyPage, InvalidPage
-from django.template import Context
+from django.template.loader import get_template
+from django.template import RequestContext
from django.utils import simplejson
from django.utils.html import escape
from django.utils.translation import ugettext as _
@@ -41,7 +43,6 @@ from askbot.utils.decorators import anonymous_forbidden, ajax_only, get_only
from askbot.search.state_manager import SearchState, DummySearchState
from askbot.templatetags import extra_tags
from askbot.conf import settings as askbot_settings
-from askbot.skins.loaders import render_into_skin, get_template #jinja2 template loading enviroment
from askbot.views import context
# used in index page
@@ -151,23 +152,31 @@ def questions(request, **kwargs):
question_counter = question_counter % {'q_num': humanize.intcomma(q_count),}
if q_count > page_size:
- paginator_tpl = get_template('main_page/paginator.html', request)
- paginator_html = paginator_tpl.render(Context({
- 'context': functions.setup_paginator(paginator_context),
- 'questions_count': q_count,
- 'page_size' : page_size,
- 'search_state': search_state,
- }))
+ paginator_tpl = get_template('main_page/paginator.html')
+ paginator_html = paginator_tpl.render(
+ RequestContext(
+ request, {
+ 'context': functions.setup_paginator(paginator_context),
+ 'questions_count': q_count,
+ 'page_size' : page_size,
+ 'search_state': search_state,
+ }
+ )
+ )
else:
paginator_html = ''
- questions_tpl = get_template('main_page/questions_loop.html', request)
- questions_html = questions_tpl.render(Context({
- 'threads': page,
- 'search_state': search_state,
- 'reset_method_count': reset_method_count,
- 'request': request
- }))
+ questions_tpl = get_template('main_page/questions_loop.html')
+ questions_html = questions_tpl.render(
+ RequestContext(
+ request, {
+ 'threads': page,
+ 'search_state': search_state,
+ 'reset_method_count': reset_method_count,
+ 'request': request
+ }
+ )
+ )
ajax_data = {
'query_data': {
@@ -226,7 +235,7 @@ def questions(request, **kwargs):
'feed_url': context_feed_url,
}
- return render_into_skin('main_page.html', template_data, request)
+ return render(request, 'main_page.html', template_data)
def tags(request):#view showing a listing of available tags - plain list
@@ -316,10 +325,9 @@ def tags(request):#view showing a listing of available tags - plain list
'search_state': SearchState(*[None for x in range(7)])
}
- return render_into_skin('tags.html', data, request)
+ return render(request, 'tags.html', data)
@csrf.csrf_protect
-#@cache_page(60 * 5)
def question(request, id):#refactor - long subroutine. display question body, answers and comments
"""view that displays body of the question and
all answers to it
@@ -549,7 +557,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
if drafts.count() > 0:
initial['text'] = drafts[0].text
- answer_form = AnswerForm(initial)
+ answer_form = AnswerForm(initial, user=request.user)
user_can_post_comment = (
request.user.is_authenticated() and request.user.can_post_comment()
@@ -599,7 +607,7 @@ def question(request, id):#refactor - long subroutine. display question body, an
data.update(context.get_for_tag_editor())
- return render_into_skin('question.html', data, request)
+ return render(request, 'question.html', data)
def revisions(request, id, post_type = None):
assert post_type in ('question', 'answer')
@@ -622,7 +630,7 @@ def revisions(request, id, post_type = None):
'post': post,
'revisions': revisions,
}
- return render_into_skin('revisions.html', data, request)
+ return render(request, 'revisions.html', data)
@csrf.csrf_exempt
@ajax_only
diff --git a/askbot/views/users.py b/askbot/views/users.py
index 16857d3f..414ac8b0 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -21,6 +21,7 @@ from django.core.paginator import Paginator, EmptyPage, InvalidPage
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404
+from django.shortcuts import render
from django.http import HttpResponse, HttpResponseForbidden
from django.http import HttpResponseRedirect, Http404
from django.utils.translation import ugettext as _
@@ -40,7 +41,6 @@ from askbot import models
from askbot import exceptions
from askbot.models.badges import award_badges_signal
from askbot.models.tag import format_personal_group_name
-from askbot.skins.loaders import render_into_skin
from askbot.search.state_manager import SearchState
from askbot.utils import url_utils
from askbot.utils.loading import load_module
@@ -200,7 +200,7 @@ def show_users(request, by_group=False, group_id=None, group_slug=None):
'group_openness_choices': group_openness_choices
}
- return render_into_skin('users.html', data, request)
+ return render(request, 'users.html', data)
@csrf.csrf_protect
def user_moderate(request, subject, context):
@@ -294,7 +294,7 @@ def user_moderate(request, subject, context):
'user_status_changed': user_status_changed
}
context.update(data)
- return render_into_skin('user_profile/user_moderate.html', context, request)
+ return render(request, 'user_profile/user_moderate.html', context)
#non-view function
def set_new_email(user, new_email, nomessage=False):
@@ -356,7 +356,7 @@ def edit_user(request, id):
'support_custom_avatars': ('avatar' in django_settings.INSTALLED_APPS),
'view_user': user,
}
- return render_into_skin('user_profile/user_edit.html', data, request)
+ return render(request, 'user_profile/user_edit.html', data)
def user_stats(request, user, context):
question_filter = {}
@@ -528,7 +528,7 @@ def user_stats(request, user, context):
}
context.update(data)
- return render_into_skin('user_profile/user_stats.html', context, request)
+ return render(request, 'user_profile/user_stats.html', context)
def user_recent(request, user, context):
@@ -682,7 +682,7 @@ def user_recent(request, user, context):
'activities' : activities[:const.USER_VIEW_DATA_SIZE]
}
context.update(data)
- return render_into_skin('user_profile/user_recent.html', context, request)
+ return render(request, 'user_profile/user_recent.html', context)
#not a view - no direct url route here, called by `user_responses`
@csrf.csrf_protect
@@ -714,7 +714,7 @@ def show_group_join_requests(request, user, context):
'join_requests': join_requests
}
context.update(data)
- return render_into_skin('user_inbox/group_join_requests.html', context, request)
+ return render(request, 'user_inbox/group_join_requests.html', context)
@owner_or_moderator_required
@@ -783,7 +783,7 @@ def user_responses(request, user, context):
context['group_messaging_template_name'] = 'group_messaging/home.html'
#here we take shortcut, because we don't care about
#all the extra context loaded below
- return render_into_skin('user_inbox/messages.html', context, request)
+ return render(request, 'user_inbox/messages.html', context)
else:
raise Http404
@@ -851,7 +851,7 @@ def user_responses(request, user, context):
'responses' : filtered_response_list,
}
context.update(data)
- return render_into_skin('user_inbox/responses_and_flags.html', context, request)
+ return render(request, 'user_inbox/responses_and_flags.html', context)
def user_network(request, user, context):
if 'followit' not in django_settings.INSTALLED_APPS:
@@ -862,7 +862,7 @@ def user_network(request, user, context):
'followers': user.get_followers(),
}
context.update(data)
- return render_into_skin('user_profile/user_network.html', context, request)
+ return render(request, 'user_profile/user_network.html', context)
@owner_or_moderator_required
def user_votes(request, user, context):
@@ -892,7 +892,7 @@ def user_votes(request, user, context):
'votes' : votes[:const.USER_VIEW_DATA_SIZE]
}
context.update(data)
- return render_into_skin('user_profile/user_votes.html', context, request)
+ return render(request, 'user_profile/user_votes.html', context)
def user_reputation(request, user, context):
@@ -915,7 +915,7 @@ def user_reputation(request, user, context):
'reps': reps
}
context.update(data)
- return render_into_skin('user_profile/user_reputation.html', context, request)
+ return render(request, 'user_profile/user_reputation.html', context)
def user_favorites(request, user, context):
@@ -933,7 +933,7 @@ def user_favorites(request, user, context):
'questions' : questions,
}
context.update(data)
- return render_into_skin('user_profile/user_favorites.html', context, request)
+ return render(request, 'user_profile/user_favorites.html', context)
@owner_or_moderator_required
@@ -984,10 +984,10 @@ def user_email_subscriptions(request, user, context):
'action_status': action_status,
}
context.update(data)
- return render_into_skin(
+ return render(
+ request,
'user_profile/user_email_subscriptions.html',
- context,
- request
+ context
)
@csrf.csrf_protect
@@ -1006,7 +1006,7 @@ def user_custom_tab(request, user, context):
'tab_name': tab_settings['SLUG'],
'page_title': page_title
})
- return render_into_skin('user_profile/custom_tab.html', context, request)
+ return render(request, 'user_profile/custom_tab.html', context)
USER_VIEW_CALL_TABLE = {
'stats': user_stats,
@@ -1130,4 +1130,4 @@ def groups(request, id = None, slug = None):
'tab_name': scope,
'page_class': 'groups-page'
}
- return render_into_skin('groups.html', data, request)
+ return render(request, 'groups.html', data)
diff --git a/askbot/views/widgets.py b/askbot/views/widgets.py
index 8699cdf1..f607411d 100644
--- a/askbot/views/widgets.py
+++ b/askbot/views/widgets.py
@@ -1,16 +1,15 @@
from datetime import datetime
-from django.core import exceptions
-from django.template import Context
+from django.template import RequestContext
+from django.template.loader import get_template
+from django.shortcuts import render
from django.http import HttpResponse, Http404
from django.views.decorators import csrf
from django.core.urlresolvers import reverse
from django.shortcuts import redirect, get_object_or_404
-from django.views.decorators.cache import cache_page
from django.contrib.auth.decorators import login_required
-from askbot.skins.loaders import render_into_skin, get_template
from askbot.conf import settings as askbot_settings
from askbot.utils import decorators
from askbot import models
@@ -47,7 +46,7 @@ def widgets(request):
'question_widgets': models.QuestionWidget.objects.all().count(),
'page_class': 'widgets'
}
- return render_into_skin('embed/widgets.html', data, request)
+ return render(request, 'embed/widgets.html', data)
@csrf.csrf_protect
def ask_widget(request, widget_id):
@@ -61,8 +60,11 @@ def ask_widget(request, widget_id):
widget = get_object_or_404(models.AskWidget, id=widget_id)
if request.method == "POST":
- form = forms.AskWidgetForm(include_text=widget.include_text_field,
- data=request.POST)
+ form = forms.AskWidgetForm(
+ include_text=widget.include_text_field,
+ data=request.POST,
+ user=request.user
+ )
if form.is_valid():
ask_anonymously = form.cleaned_data['ask_anonymously']
title = form.cleaned_data['title']
@@ -93,7 +95,7 @@ def ask_widget(request, widget_id):
}
if request.user.is_authenticated():
data_dict['author'] = request.user
- question = post_question(data_dict, request)
+ #question = post_question(data_dict, request)
return redirect('ask_by_widget_complete')
else:
request.session['widget_question'] = data_dict
@@ -116,14 +118,17 @@ def ask_widget(request, widget_id):
next_url = '%s?next=%s' % (reverse('widget_signin'), reverse('ask_by_widget'))
return redirect(next_url)
- form = forms.AskWidgetForm(include_text=widget.include_text_field)
+ form = forms.AskWidgetForm(
+ include_text=widget.include_text_field,
+ user=request.user
+ )
data = {
'form': form,
'widget': widget,
'editor_type': askbot_settings.EDITOR_TYPE
}
- return render_into_skin('embed/ask_by_widget.html', data, request)
+ return render(request, 'embed/ask_by_widget.html', data)
@login_required
def ask_widget_complete(request):
@@ -138,7 +143,7 @@ def ask_widget_complete(request):
del request.session['widget_css']
data = {'question_url': question_url, 'custom_css': custom_css}
- return render_into_skin('embed/ask_widget_complete.html', data, request)
+ return render(request, 'embed/ask_widget_complete.html', data)
@decorators.admins_only
@@ -149,7 +154,7 @@ def list_widgets(request, model):
'widgets': widgets,
'widget_name': model
}
- return render_into_skin('embed/list_widgets.html', data, request)
+ return render(request, 'embed/list_widgets.html', data)
@decorators.admins_only
def create_widget(request, model):
@@ -167,7 +172,7 @@ def create_widget(request, model):
data = {'form': form,
'action': 'edit',
'widget_name': model}
- return render_into_skin('embed/widget_form.html', data, request)
+ return render(request, 'embed/widget_form.html', data)
@decorators.admins_only
def edit_widget(request, model, widget_id):
@@ -206,7 +211,7 @@ def edit_widget(request, model, widget_id):
data = {'form': form,
'action': 'edit',
'widget_name': model}
- return render_into_skin('embed/widget_form.html', data, request)
+ return render(request, 'embed/widget_form.html', data)
@decorators.admins_only
def delete_widget(request, model, widget_id):
@@ -216,28 +221,35 @@ def delete_widget(request, model, widget_id):
widget.delete()
return redirect('list_widgets', model=model)
else:
- return render_into_skin('embed/delete_widget.html',
- {'widget': widget, 'widget_name': model}, request)
+ return render(
+ request,
+ 'embed/delete_widget.html',
+ {'widget': widget, 'widget_name': model}
+ )
def render_ask_widget_js(request, widget_id):
widget = get_object_or_404(models.AskWidget, pk=widget_id)
variable_name = "AskbotAskWidget%d" % widget.id
- content_tpl = get_template('embed/askbot_widget.js', request)
- context_dict = {'widget': widget,
- 'host': request.get_host(),
- 'variable_name': variable_name}
- content = content_tpl.render(Context(context_dict))
+ content_tpl = get_template('embed/askbot_widget.js')
+ context_dict = {
+ 'widget': widget,
+ 'host': request.get_host(),
+ 'variable_name': variable_name
+ }
+ content = content_tpl.render(RequestContext(request, context_dict))
return HttpResponse(content, mimetype='text/javascript')
def render_ask_widget_css(request, widget_id):
widget = get_object_or_404(models.AskWidget, pk=widget_id)
variable_name = "AskbotAskWidget%d" % widget.id
- content_tpl = get_template('embed/askbot_widget.css', request)
- context_dict = {'widget': widget,
- 'host': request.get_host(),
- 'editor_type': askbot_settings.EDITOR_TYPE,
- 'variable_name': variable_name}
- content = content_tpl.render(Context(context_dict))
+ content_tpl = get_template('embed/askbot_widget.css')
+ context_dict = {
+ 'widget': widget,
+ 'host': request.get_host(),
+ 'editor_type': askbot_settings.EDITOR_TYPE,
+ 'variable_name': variable_name
+ }
+ content = content_tpl.render(RequestContext(request, context_dict))
return HttpResponse(content, mimetype='text/css')
def question_widget(request, widget_id):
@@ -271,4 +283,4 @@ def question_widget(request, widget_id):
'widget': widget
}
- return render_into_skin('embed/question_widget.html', data, request)
+ return render(request, 'embed/question_widget.html', data)
diff --git a/askbot/views/writers.py b/askbot/views/writers.py
index db7a24d2..da4ce6a1 100644
--- a/askbot/views/writers.py
+++ b/askbot/views/writers.py
@@ -14,6 +14,7 @@ import tempfile
import time
import urlparse
from django.shortcuts import get_object_or_404
+from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
@@ -29,7 +30,6 @@ from askbot import exceptions as askbot_exceptions
from askbot import forms
from askbot import models
from askbot.conf import settings as askbot_settings
-from askbot.skins.loaders import render_into_skin
from askbot.utils import decorators
from askbot.utils.forms import format_errors
from askbot.utils.functions import diff_date
@@ -192,7 +192,7 @@ def import_data(request):
'dump_upload_form': form,
'need_configuration': (not stackexchange.is_ready())
}
- return render_into_skin('import_data.html', data, request)
+ return render(request, 'import_data.html', data)
#@login_required #actually you can post anonymously, but then must register
@csrf.csrf_protect
@@ -211,7 +211,7 @@ def ask(request):#view used to ask a new question
user can start posting a question anonymously but then
must login/register in order for the question go be shown
"""
- form = forms.AskForm(request.REQUEST)
+ form = forms.AskForm(request.REQUEST, user=request.user)
if request.method == 'POST':
if form.is_valid():
timestamp = datetime.datetime.now()
@@ -264,7 +264,7 @@ def ask(request):#view used to ask a new question
return HttpResponseRedirect(url_utils.get_login_url())
if request.method == 'GET':
- form = forms.AskForm()
+ form = forms.AskForm(user=request.user)
draft_title = ''
draft_text = ''
@@ -302,7 +302,7 @@ def ask(request):#view used to ask a new question
'tag_names': list()#need to keep context in sync with edit_question for tag editor
}
data.update(context.get_for_tag_editor())
- return render_into_skin('ask.html', data, request)
+ return render(request, 'ask.html', data)
@login_required
@csrf.csrf_exempt
@@ -349,7 +349,7 @@ def retag_question(request, id):
'question': question,
'form' : form,
}
- return render_into_skin('question_retag.html', data, request)
+ return render(request, 'question_retag.html', data)
except exceptions.PermissionDenied, e:
if request.is_ajax():
response_data = {
@@ -386,24 +386,24 @@ def edit_question(request, id):
rev_id = revision_form.cleaned_data['revision']
revision = question.revisions.get(revision = rev_id)
form = forms.EditQuestionForm(
- question = question,
- user = request.user,
- revision = revision
+ question=question,
+ user=request.user,
+ revision=revision
)
else:
form = forms.EditQuestionForm(
request.POST,
- question = question,
- user = request.user,
- revision = revision
+ question=question,
+ user=question.user,
+ revision=revision
)
else:#new content edit
# Always check modifications against the latest revision
form = forms.EditQuestionForm(
request.POST,
- question = question,
- revision = revision,
- user = request.user,
+ question=question,
+ revision=revision,
+ user=request.user,
)
revision_form = forms.RevisionForm(question, revision)
if form.is_valid():
@@ -437,10 +437,10 @@ def edit_question(request, id):
'wiki': question.wiki
}
form = forms.EditQuestionForm(
- question = question,
- revision = revision,
- user = request.user,
- initial = initial
+ question=question,
+ revision=revision,
+ user=request.user,
+ initial=initial
)
data = {
@@ -455,7 +455,7 @@ def edit_question(request, id):
'category_tree_data': askbot_settings.CATEGORY_TREE
}
data.update(context.get_for_tag_editor())
- return render_into_skin('question_edit.html', data, request)
+ return render(request, 'question_edit.html', data)
except exceptions.PermissionDenied, e:
request.user.message_set.create(message = unicode(e))
@@ -481,15 +481,20 @@ def edit_answer(request, id):
# Replace with those from the selected revision
rev = revision_form.cleaned_data['revision']
revision = answer.revisions.get(revision = rev)
- form = forms.EditAnswerForm(answer, revision)
+ form = forms.EditAnswerForm(
+ answer, revision, user=request.user
+ )
else:
form = forms.EditAnswerForm(
answer,
revision,
- request.POST
+ request.POST,
+ user=request.user
)
else:
- form = forms.EditAnswerForm(answer, revision, request.POST)
+ form = forms.EditAnswerForm(
+ answer, revision, request.POST, user=request.user
+ )
revision_form = forms.RevisionForm(answer, revision)
if form.is_valid():
@@ -506,7 +511,7 @@ def edit_answer(request, id):
return HttpResponseRedirect(answer.get_absolute_url())
else:
revision_form = forms.RevisionForm(answer, revision)
- form = forms.EditAnswerForm(answer, revision)
+ form = forms.EditAnswerForm(answer, revision, user=request.user)
if request.user.can_make_group_private_posts():
form.initial['post_privately'] = answer.is_private()
data = {
@@ -517,7 +522,7 @@ def edit_answer(request, id):
'revision_form': revision_form,
'form': form,
}
- return render_into_skin('answer_edit.html', data, request)
+ return render(request, 'answer_edit.html', data)
except exceptions.PermissionDenied, e:
request.user.message_set.create(message = unicode(e))
@@ -536,7 +541,7 @@ def answer(request, id):#process a new answer
"""
question = get_object_or_404(models.Post, post_type='question', id=id)
if request.method == "POST":
- form = forms.AnswerForm(request.POST)
+ form = forms.AnswerForm(request.POST, user=request.user)
if form.is_valid():
wiki = form.cleaned_data['wiki']
text = form.cleaned_data['text']
diff --git a/group_messaging/__init__.py b/group_messaging/__init__.py
index 642ad5c8..ed3d73ff 100644
--- a/group_messaging/__init__.py
+++ b/group_messaging/__init__.py
@@ -11,4 +11,7 @@ the group should be named `'_personal_1'`.
Only one person must be a member of a personal group and
each user must have such group.
+
+TODO: decouple this application
+first step is to package send_mail separately
"""
diff --git a/group_messaging/models.py b/group_messaging/models.py
index 9cc12786..5d43baf8 100644
--- a/group_messaging/models.py
+++ b/group_messaging/models.py
@@ -4,7 +4,7 @@ import copy
import datetime
import urllib
from askbot.mail import send_mail #todo: remove dependency?
-from coffin.template.loader import get_template
+from django.template.loader import get_template
from django.db import models
from django.db.models import signals
from django.conf import settings as django_settings
@@ -395,9 +395,10 @@ class Message(models.Model):
#in the template we have a placeholder to be replaced like this:
body_text = body_text.replace('THREAD_URL_HOLE', thread_url)
send_mail(
- recipient_list=[user.email,],
- subject_line=subject,
- body_text=body_text
+ subject,
+ body_text,
+ django_settings.DEFAULT_FROM_EMAIL,
+ [user.email,],
)
diff --git a/group_messaging/views.py b/group_messaging/views.py
index d4d646ec..244762d1 100644
--- a/group_messaging/views.py
+++ b/group_messaging/views.py
@@ -10,7 +10,7 @@ and turns them into complete views
"""
import copy
import datetime
-from coffin.template.loader import get_template
+from django.template.loader import get_template
from django.contrib.auth.models import User
from django.db import models
from django.forms import IntegerField