diff options
author | Byron <byroncorrales@gmail.com> | 2011-09-19 11:34:44 -0600 |
---|---|---|
committer | Byron <byroncorrales@gmail.com> | 2011-09-19 11:34:44 -0600 |
commit | 498bd123c080bed9935a6ccec048a1d15caf7ee6 (patch) | |
tree | f9c8fe28d5fd28e6c95c98fb2565db1c3aab5e04 | |
parent | adc55299eef92b42f125475b97c91d96ba41f5cc (diff) | |
parent | fdaabe9a5d03493394bacac35f03694cbe781325 (diff) | |
download | askbot-498bd123c080bed9935a6ccec048a1d15caf7ee6.tar.gz askbot-498bd123c080bed9935a6ccec048a1d15caf7ee6.tar.bz2 askbot-498bd123c080bed9935a6ccec048a1d15caf7ee6.zip |
Merge branch 'master' of https://github.com/ASKBOT/askbot-devel
65 files changed, 1966 insertions, 245 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 484f6b67..7d503fb7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ include LICENSE include AUTHORS include COPYING include README.rst +include askbot_requirements.txt recursive-include askbot * recursive-exclude askbot *.pyc recursive-exclude .git diff --git a/askbot/__init__.py b/askbot/__init__.py index d56557d9..8133c74f 100644 --- a/askbot/__init__.py +++ b/askbot/__init__.py @@ -9,7 +9,7 @@ import smtplib import sys import logging -VERSION = (0, 7, 20) +VERSION = (0, 7, 22) #necessary for interoperability of django and coffin try: diff --git a/askbot/conf/__init__.py b/askbot/conf/__init__.py index b19aeb60..1d2d7240 100644 --- a/askbot/conf/__init__.py +++ b/askbot/conf/__init__.py @@ -7,11 +7,13 @@ import askbot.conf.email import askbot.conf.forum_data_rules import askbot.conf.flatpages import askbot.conf.site_settings +import askbot.conf.license import askbot.conf.external_keys import askbot.conf.skin_general_settings import askbot.conf.sidebar_main import askbot.conf.sidebar_question import askbot.conf.sidebar_profile +import askbot.conf.spam_and_moderation import askbot.conf.user_settings import askbot.conf.markup import askbot.conf.social_sharing diff --git a/askbot/conf/external_keys.py b/askbot/conf/external_keys.py index d20bf689..8912b0ff 100644 --- a/askbot/conf/external_keys.py +++ b/askbot/conf/external_keys.py @@ -1,6 +1,5 @@ -""" -External service key settings -""" +"""External service key settings""" +from askbot import const from askbot.conf.settings_wrapper import settings from askbot.deps import livesettings from django.utils.translation import ugettext as _ @@ -8,7 +7,8 @@ from django.conf import settings as django_settings EXTERNAL_KEYS = livesettings.ConfigurationGroup( 'EXTERNAL_KEYS', - _('Keys to connect the site with external services like Facebook, etc.') + _('Keys to connect the site with external ' + 'services like Facebook, etc.') ) settings.register( @@ -19,11 +19,12 @@ settings.register( help_text=_( 'This key helps google index your site ' 'please obtain is at ' - '<a href="%(google_webmasters_tools_url)s">' + '<a href="%(url)s?hl=%(lang)s">' 'google webmasters tools site</a>' - ) % {'google_webmasters_tools_url': - 'https://www.google.com/webmasters/tools/home?hl=' \ - + django_settings.LANGUAGE_CODE} + ) % { + 'url': const.DEPENDENCY_URLS['google-webmaster-tools'], + 'lang': django_settings.LANGUAGE_CODE, + } ) ) @@ -33,12 +34,12 @@ settings.register( 'GOOGLE_ANALYTICS_KEY', description=_('Google Analytics key'), help_text=_( - 'Obtain is at <a href="%(ga_site)s">' - 'Google Analytics</a> site, if you ' - 'wish to use Google Analytics to monitor ' - 'your site' - ) % {'ga_site':'http://www.google.com/intl/%s/analytics/' \ - % django_settings.LANGUAGE_CODE } + 'Obtain is at <a href="%(url)s">' + 'Google Analytics</a> site, if you ' + 'wish to use Google Analytics to monitor ' + 'your site' + ) % {'url': 'http://www.google.com/intl/%s/analytics/' \ + % django_settings.LANGUAGE_CODE } ) ) @@ -68,8 +69,8 @@ settings.register( 'Recaptcha is a tool that helps distinguish ' 'real people from annoying spam robots. ' 'Please get this and a public key at ' - 'the <a href="http://recaptcha.net">recaptcha.net</a>' - ) + 'the <a href="%(url)s">%(url)s</a>' + ) % {'url': const.DEPENDENCY_URLS['recaptcha']} ) ) @@ -82,9 +83,9 @@ settings.register( 'Facebook API key and Facebook secret ' 'allow to use Facebook Connect login method ' 'at your site. Please obtain these keys ' - 'at <a href="http://www.facebook.com/developers/createapp.php">' + 'at <a href="%(url)s">' 'facebook create app</a> site' - ) + ) % {'url': const.DEPENDENCY_URLS['facebook-apps']} ) ) @@ -102,9 +103,9 @@ settings.register( 'TWITTER_KEY', description=_('Twitter consumer key'), help_text=_( - 'Please register your forum at <a href="http://dev.twitter.com/apps/">' + 'Please register your forum at <a href="%(url)s">' 'twitter applications site</a>' - ), + ) % {'url': const.DEPENDENCY_URLS['twitter-apps']}, ) ) @@ -123,9 +124,9 @@ settings.register( 'LINKEDIN_KEY', description=_('LinkedIn consumer key'), help_text=_( - 'Please register your forum at <a href="http://dev.twitter.com/apps/">' - 'twitter applications site</a>' - ), + 'Please register your forum at <a href="%(url)s">' + 'LinkedIn developer site</a>' + ) % {'url': const.DEPENDENCY_URLS['linkedin-apps']}, ) ) @@ -144,9 +145,9 @@ settings.register( 'IDENTICA_KEY', description=_('ident.ca consumer key'), help_text=_( - 'Please register your forum at <a href="http://identi.ca/settings/oauthapps">' + 'Please register your forum at <a href="%(url)s">' 'Identi.ca applications site</a>' - ), + ) % {'url': const.DEPENDENCY_URLS['identica-apps']}, ) ) diff --git a/askbot/conf/license.py b/askbot/conf/license.py new file mode 100644 index 00000000..73001c2d --- /dev/null +++ b/askbot/conf/license.py @@ -0,0 +1,82 @@ +"""settings that allow changing of the license +clause used in askbot instances""" +from askbot import const +from askbot.conf.settings_wrapper import settings +from askbot.deps import livesettings +from askbot.skins import utils as skin_utils +from django.utils.translation import ugettext as _ +from django.conf import settings as django_settings + +LICENSE_SETTINGS = livesettings.ConfigurationGroup( + 'LICENSE_SETTINGS', + _('License settings') + ) + +settings.register( + livesettings.BooleanValue( + LICENSE_SETTINGS, + 'USE_LICENSE', + description = _('Show license clause in the site footer'), + default = True + ) +) + +settings.register( + livesettings.StringValue( + LICENSE_SETTINGS, + 'LICENSE_ACRONYM', + description = _('Short name for the license'), + default = 'cc-by-sa' + ) +) + +settings.register( + livesettings.StringValue( + LICENSE_SETTINGS, + 'LICENSE_TITLE', + description = _('Full name of the license'), + default = _('Creative Commons Attribution Share Alike 3.0'), + ) +) + +settings.register( + livesettings.BooleanValue( + LICENSE_SETTINGS, + 'LICENSE_USE_URL', + description = _('Add link to the license page'), + default = True + ) +) + +settings.register( + livesettings.URLValue( + LICENSE_SETTINGS, + 'LICENSE_URL', + description = _('License homepage'), + help_text = _( + 'URL of the official page with all the license legal clauses' + ), + default = const.DEPENDENCY_URLS['cc-by-sa'] + ) +) + +settings.register( + livesettings.BooleanValue( + LICENSE_SETTINGS, + 'LICENSE_USE_LOGO', + description = _('Use license logo'), + default = True + ) +) + +settings.register( + livesettings.ImageValue( + LICENSE_SETTINGS, + 'LICENSE_LOGO_URL', + description = _('License logo image'), + upload_directory = django_settings.ASKBOT_FILE_UPLOAD_DIR, + upload_url = '/' + django_settings.ASKBOT_UPLOADED_FILES_URL, + default = '/images/cc-by-sa.png', + url_resolver = skin_utils.get_media_url + ) +) diff --git a/askbot/conf/sidebar_main.py b/askbot/conf/sidebar_main.py index 012d8ace..57f9848d 100644 --- a/askbot/conf/sidebar_main.py +++ b/askbot/conf/sidebar_main.py @@ -41,6 +41,16 @@ settings.register( ) settings.register( + values.IntegerValue( + SIDEBAR_MAIN, + 'SIDEBAR_MAIN_AVATAR_LIMIT', + description = _('Limit how many avatars will be displayed on the sidebar'), + default = 16 + ) +) + + +settings.register( values.BooleanValue( SIDEBAR_MAIN, 'SIDEBAR_MAIN_SHOW_TAG_SELECTOR', diff --git a/askbot/conf/site_settings.py b/askbot/conf/site_settings.py index 804f457c..376c65c0 100644 --- a/askbot/conf/site_settings.py +++ b/askbot/conf/site_settings.py @@ -17,7 +17,7 @@ settings.register( livesettings.StringValue( QA_SITE_SETTINGS, 'APP_TITLE', - default=u'ASKBOT: Open Source Q&A Forum', + default=u'Askbot: Open Source Q&A Forum', description=_('Site title for the Q&A forum') ) ) @@ -26,7 +26,7 @@ settings.register( livesettings.StringValue( QA_SITE_SETTINGS, 'APP_KEYWORDS', - default=u'ASKBOT,CNPROG,forum,community', + default=u'Askbot,CNPROG,forum,community', description=_('Comma separated list of Q&A site keywords') ) ) @@ -35,8 +35,7 @@ settings.register( livesettings.StringValue( QA_SITE_SETTINGS, 'APP_COPYRIGHT', - default='Copyright ASKBOT, 2010. Some rights reserved ' + \ - 'under creative commons license.', + default='Copyright Askbot, 2010-2011.', description=_('Copyright message to show in the footer') ) ) diff --git a/askbot/conf/skin_general_settings.py b/askbot/conf/skin_general_settings.py index b90d3de5..8521bf87 100644 --- a/askbot/conf/skin_general_settings.py +++ b/askbot/conf/skin_general_settings.py @@ -106,20 +106,7 @@ settings.register( ) ) -settings.register( - values.IntegerValue( - GENERAL_SKIN_SETTINGS, - 'MEDIA_RESOURCE_REVISION', - default = 1, - description = _('Skin media revision number'), - help_text = _( - 'Increment this number when you change ' - 'image in skin media or stylesheet. ' - 'This helps avoid showing your users ' - 'outdated images from their browser cache.' - ) - ) -) + settings.register( values.BooleanValue( @@ -271,3 +258,30 @@ settings.register( ) ) ) + +settings.register( + values.IntegerValue( + GENERAL_SKIN_SETTINGS, + 'MEDIA_RESOURCE_REVISION', + default = 1, + description = _('Skin media revision number'), + help_text = _( + 'Will be set automatically ' + 'but you can modify it if necessary.' + ) + ) +) + +settings.register( + values.StringValue( + GENERAL_SKIN_SETTINGS, + 'MEDIA_RESOURCE_REVISION_HASH', + description = _( + 'Hash to update the media revision number automatically.' + ), + default='', + help_text = _( + 'Will be set automatically, it is not necesary to modify manually.' + ) + ) +) diff --git a/askbot/conf/spam_and_moderation.py b/askbot/conf/spam_and_moderation.py new file mode 100644 index 00000000..375fbdd5 --- /dev/null +++ b/askbot/conf/spam_and_moderation.py @@ -0,0 +1,32 @@ +"""Settings for content moderation and spam control""" +from django.utils.translation import ugettext as _ +from askbot import const +from askbot.deps import livesettings +from askbot.conf.settings_wrapper import settings + +SPAM_AND_MODERATION = livesettings.ConfigurationGroup( + 'SPAM_AND_MODERATION', + _('Spam control and content moderation') + ) + +settings.register( + livesettings.BooleanValue( + SPAM_AND_MODERATION, + 'USE_AKISMET', + description=_('Enable Akismet spam detection(keys below are required)'), + default=False, + help_text = _( + 'To get an Akismet key please visit ' + '<a href="%(url)s">Akismet site</a>' + ) % {'url': const.DEPENDENCY_URLS['akismet']} + ) +) + +settings.register( + livesettings.StringValue( + SPAM_AND_MODERATION, + 'AKISMET_API_KEY', + description=_('Akismet key for spam detection') + ) +) + diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py index 15233ee1..73c71800 100644 --- a/askbot/const/__init__.py +++ b/askbot/const/__init__.py @@ -1,10 +1,11 @@ # encoding:utf-8 -from django.utils.translation import ugettext as _ -import re """ All constants could be used in other modules -For reasons that models, views can't have unicode text in this project, all unicode text go here. +For reasons that models, views can't have unicode +text in this project, all unicode text go here. """ +from django.utils.translation import ugettext as _ +import re CLOSE_REASONS = ( (1, _('duplicate question')), (2, _('question is off-topic or not relevant')), @@ -48,7 +49,8 @@ POST_SORT_METHODS = ( ('relevance-desc', _('relevance')), ) #todo: add assertion here that all sort methods are unique -#because they are keys to the hash used in implementations of Q.run_advanced_search +#because they are keys to the hash used in implementations +#of Q.run_advanced_search DEFAULT_POST_SORT_METHOD = 'activity-desc' POST_SCOPE_LIST = ( @@ -91,28 +93,28 @@ TAG_REGEX = r'^[%s]+$' % TAG_CHARS TAG_SPLIT_REGEX = r'[ ,]+' EMAIL_REGEX = re.compile(r'\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b', re.I) -TYPE_ACTIVITY_ASK_QUESTION=1 -TYPE_ACTIVITY_ANSWER=2 -TYPE_ACTIVITY_COMMENT_QUESTION=3 -TYPE_ACTIVITY_COMMENT_ANSWER=4 -TYPE_ACTIVITY_UPDATE_QUESTION=5 -TYPE_ACTIVITY_UPDATE_ANSWER=6 -TYPE_ACTIVITY_PRIZE=7 -TYPE_ACTIVITY_MARK_ANSWER=8 -TYPE_ACTIVITY_VOTE_UP=9 -TYPE_ACTIVITY_VOTE_DOWN=10 -TYPE_ACTIVITY_CANCEL_VOTE=11 -TYPE_ACTIVITY_DELETE_QUESTION=12 -TYPE_ACTIVITY_DELETE_ANSWER=13 -TYPE_ACTIVITY_MARK_OFFENSIVE=14 -TYPE_ACTIVITY_UPDATE_TAGS=15 -TYPE_ACTIVITY_FAVORITE=16 +TYPE_ACTIVITY_ASK_QUESTION = 1 +TYPE_ACTIVITY_ANSWER = 2 +TYPE_ACTIVITY_COMMENT_QUESTION = 3 +TYPE_ACTIVITY_COMMENT_ANSWER = 4 +TYPE_ACTIVITY_UPDATE_QUESTION = 5 +TYPE_ACTIVITY_UPDATE_ANSWER = 6 +TYPE_ACTIVITY_PRIZE = 7 +TYPE_ACTIVITY_MARK_ANSWER = 8 +TYPE_ACTIVITY_VOTE_UP = 9 +TYPE_ACTIVITY_VOTE_DOWN = 10 +TYPE_ACTIVITY_CANCEL_VOTE = 11 +TYPE_ACTIVITY_DELETE_QUESTION = 12 +TYPE_ACTIVITY_DELETE_ANSWER = 13 +TYPE_ACTIVITY_MARK_OFFENSIVE = 14 +TYPE_ACTIVITY_UPDATE_TAGS = 15 +TYPE_ACTIVITY_FAVORITE = 16 TYPE_ACTIVITY_USER_FULL_UPDATED = 17 TYPE_ACTIVITY_EMAIL_UPDATE_SENT = 18 TYPE_ACTIVITY_MENTION = 19 TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT = 20 -#TYPE_ACTIVITY_EDIT_QUESTION=17 -#TYPE_ACTIVITY_EDIT_ANSWER=18 +#TYPE_ACTIVITY_EDIT_QUESTION = 17 +#TYPE_ACTIVITY_EDIT_ANSWER = 18 #todo: rename this to TYPE_ACTIVITY_CHOICES TYPE_ACTIVITY = ( @@ -250,10 +252,22 @@ DEFAULT_USER_STATUS = 'w' #number of items to show in user views USER_VIEW_DATA_SIZE = 50 +#not really dependency, but external links, which it would +#be nice to test for correctness from time to time DEPENDENCY_URLS = { - 'mathjax': 'http://www.mathjax.org/resources/docs/?installation.html', + 'akismet': 'https://akismet.com/signup/', + 'cc-by-sa': 'http://creativecommons.org/licenses/by-sa/3.0/legalcode', + 'embedding-video': \ + 'http://askbot.org/doc/optional-modules.html#embedding-video', 'favicon': 'http://en.wikipedia.org/wiki/Favicon', - 'embedding-video': 'http://askbot.org/doc/optional-modules.html#embedding-video' + 'facebook-apps': 'http://www.facebook.com/developers/createapp.php', + 'google-webmaster-tools': 'https://www.google.com/webmasters/tools/home', + 'identica-apps': 'http://identi.ca/settings/oauthapps', + 'noscript': 'https://www.google.com/support/bin/answer.py?answer=23852', + 'linkedin-apps': 'https://www.linkedin.com/secure/developer', + 'mathjax': 'http://www.mathjax.org/resources/docs/?installation.html', + 'recaptcha': 'http://google.com/recaptcha', + 'twitter-apps': 'http://dev.twitter.com/apps/', } PASSWORD_MIN_LENGTH = 8 @@ -275,6 +289,11 @@ BADGE_DISPLAY_SYMBOL = '●' MIN_REPUTATION = 1 +AVATAR_STATUS_CHOICE = ( + ('n', _('None')), + ('g', _('Gravatar')), + ('a', _('Uploaded Avatar')), +) #an exception import * because that file has only strings from askbot.const.message_keys import * diff --git a/askbot/context.py b/askbot/context.py index 5a174585..6dc38f79 100644 --- a/askbot/context.py +++ b/askbot/context.py @@ -5,6 +5,7 @@ and the application available for the templates from django.conf import settings import askbot from askbot import api +from askbot import const from askbot.conf import settings as askbot_settings from askbot.skins.loaders import get_skin from askbot.utils import url_utils @@ -22,5 +23,6 @@ def application_settings(request): return { 'settings': my_settings, 'skin': get_skin(request), - 'moderation_items': api.get_info_on_moderation_items(request.user) + 'moderation_items': api.get_info_on_moderation_items(request.user), + 'noscript_url': const.DEPENDENCY_URLS['noscript'], } diff --git a/askbot/deployment/path_utils.py b/askbot/deployment/path_utils.py index 7a1197de..6ad9fc99 100644 --- a/askbot/deployment/path_utils.py +++ b/askbot/deployment/path_utils.py @@ -101,6 +101,7 @@ def path_is_clean_for_django(directory): def create_path(directory): + """equivalent to mkdir -p""" if os.path.isdir(directory): return elif os.path.exists(directory): @@ -109,12 +110,17 @@ def create_path(directory): os.makedirs(directory) def touch(file_path, times = None): + """implementation of unix ``touch`` in python""" #http://stackoverflow.com/questions/1158076/implement-touch-using-python - with file(file_path, 'a'): - os.utime(file_path, times) + fhandle = file(file_path, 'a') + try: + os.utime(file_path, times) + finally: + fhandle.close() SOURCE_DIR = os.path.dirname(os.path.dirname(__file__)) def get_path_to_help_file(): + """returns path to the main plain text help file""" return os.path.join(SOURCE_DIR, 'doc', 'INSTALL') def deploy_into(directory, new_project = None): @@ -145,7 +151,7 @@ def deploy_into(directory, new_project = None): print '' app_dir = os.path.join(directory, 'askbot') - copy_dirs = ('doc','cron','upfiles') + copy_dirs = ('doc', 'cron', 'upfiles') dirs_copied = 0 for dir_name in copy_dirs: src = os.path.join(SOURCE_DIR, dir_name) @@ -165,6 +171,7 @@ def deploy_into(directory, new_project = None): print '' def dir_name_acceptable(directory): + """True if directory is not taken by another python module""" dir_name = os.path.basename(directory) try: imp.find_module(dir_name) diff --git a/askbot/deps/django_authopenid/backends.py b/askbot/deps/django_authopenid/backends.py index e608086c..9f8f1dfd 100644 --- a/askbot/deps/django_authopenid/backends.py +++ b/askbot/deps/django_authopenid/backends.py @@ -3,6 +3,7 @@ multiple login methods supported by the authenticator application """ import datetime +import logging from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured from django.utils.translation import ugettext as _ @@ -48,7 +49,20 @@ class AuthBackend(object): if not user.check_password(password): return None except User.DoesNotExist: - return None + try: + email_address = username + user = User.objects.get(email = email_address) + if not user.check_password(password): + return None + except User.DoesNotExist: + return None + except User.MultipleObjectsReturned: + logging.critical( + ('have more than one user with email %s ' + + 'he/she will not be able to authenticate with ' + + 'the email address in the place of user name') % email_address + ) + return None else: if login_providers[provider_name]['check_password'](username, password): try: diff --git a/askbot/doc/source/askbot/layout.html b/askbot/doc/source/askbot/layout.html index 251d8386..f1c8b509 100644 --- a/askbot/doc/source/askbot/layout.html +++ b/askbot/doc/source/askbot/layout.html @@ -1,16 +1,19 @@ {% extends "basic/layout.html" %} {% block relbar1 %} +<div class="logo"> + <a href="/doc/index.html"><h1>Askbot Project Documentation</h1></a> + <div class="topnav"> + <a href="http://twitter.com/askbot7" alt="Follow us on twitter"><img src="_static/twitter.png"/></a> + <a href="https://www.facebook.com/pages/Askbot/128297183877495" alt="like us on Facebook"><img src="_static/facebook.png"/></a> + </div> +</div> <div class="ab-proj-header"> - <ul> - <li class="first"><a href="/">Home (forum)</a> - <span class="sep">|</span> - </li> - {#<li><a href="/doc/about.html">About</a></li> - <span class="sep">|</span>#} - <li><a href="/doc/index.html">Documentation</a></li> - {#<span class="sep">|</span> - <li><a href="/doc/download.html">Download</a></li>#} - </ul> + <a href="/">Home</a> | + <a href="/en/questions/" title="Ask Questions">Ask Questions</a> | + <a href="/hire-us" alt='Hire Us'>Hire Us</a> | + <a href="/doc/index.html" alt="Documentation">Documentation</a> | + <a href="/contribute" alt='Contribute'>Contribute</a> | + <a href="/feedback/" alt='contact'>Contact</a> </div> {% endblock %} {% block relbar2 %} diff --git a/askbot/doc/source/askbot/static/facebook.png b/askbot/doc/source/askbot/static/facebook.png Binary files differnew file mode 100644 index 00000000..77ae358f --- /dev/null +++ b/askbot/doc/source/askbot/static/facebook.png diff --git a/askbot/doc/source/askbot/static/traditional.css b/askbot/doc/source/askbot/static/traditional.css index cfbdaf79..3fa381cd 100644 --- a/askbot/doc/source/askbot/static/traditional.css +++ b/askbot/doc/source/askbot/static/traditional.css @@ -8,11 +8,13 @@ * :license: BSD, see LICENSE for details. * */ +@import url(http://fonts.googleapis.com/css?family=Droid+Sans|Cabin|Cabin+Sketch:700); body { color: #000; margin: 0; padding: 0; + border-color: gray; } /* :::: LAYOUT :::: */ @@ -29,11 +31,11 @@ div.bodywrapper { div.body { background-color: white; padding: 0 20px 30px 0; - font-family: "Lucida Grande",Verdana,"Bitstream Vera Sans",Arial,sans-serif; + font-family: "Droid Sans", "Lucida Grande",Verdana,"Bitstream Vera Sans",Arial,sans-serif; } div.sphinxsidebarwrapper { - border: 1px solid #99ccff; + border: 1px solid #777; padding: 10px; margin: 55px 15px 10px 0; } @@ -42,6 +44,7 @@ div.sphinxsidebar { float: right; margin-left: -100%; width: 230px; + font-family: "Droid Sans", sans-serif; } div.clearer { @@ -59,6 +62,7 @@ div.footer { background-color: #dcdcdc; padding: 9px 0 9px 0; text-align: center; + font-family: "Droid Sans", sans-serif; } div.related { @@ -92,6 +96,11 @@ div.related li.right { } /* ::: SIDEBAR :::: */ +div.sphinxsidebar h3, +div.sphinxsidebar h4{ + font-family: 'Cabin', cursive; +} + div.sphinxsidebar h3 { margin: 0; } @@ -100,10 +109,6 @@ div.sphinxsidebar h4 { margin: 5px 0 0 0; } -div.sphinxsidebar p, -div.sphinxsidebar a { - font-family: "Lucida Grande",Verdana,"Bitstream Vera Sans",Arial,sans-serif; -} div.sphinxsidebar p.topless { margin: 5px 10px 10px 10px; @@ -332,11 +337,10 @@ a { text-decoration: none; border-bottom: 1px solid #dfdfdf; } -a:link:active { color: #ff0000; } -a:link:hover { background-color: #bbeeff; } -a:visited:hover { background-color: #bbeeff; } -a:visited { color: #3185AB; } -a:link { color: #21759B; } +a:link:active { color: #555; } +a:link:hover { color: #000; } +a:visited { color: #655; } +a:link { color: #555; } div.body h1, div.body h2, @@ -344,7 +348,7 @@ div.body h3, div.body h4, div.body h5, div.body h6 { - font-family: Georgia, Times, "Times New Roman", serif; + font-family: 'Cabin', cursive; font-weight: bold; } @@ -655,23 +659,36 @@ div.viewcode-block:target { div.ab-proj-header { width: 960px; margin: auto; - font-family: "Lucida Grande",Tahoma,"Bitstream Vera Sans",Arial,sans-serif; -} -div.ab-proj-header li { - display: inline; - padding: 0; + border-bottom: 2px solid #555; + font-family: "Droid Sans", sans-serif; } + div.ab-proj-header a, -div.ab-proj-header a:visited, +div.ab-proj-header a:visited { text-decoration: none; + color: #333; + border: none; + font-family: "Droid Sans", sans-serif; } -div.ab-proj-header ul { - margin: 0; - padding: 5px 0 0 0; - list-style: none; + +div.logo{ + font-size: 20px; + width: 960px; + margin: auto; } -div.ab-proj-header .sep { - color: #aaa; - padding: 0 10px; + +div.logo a, div.logo a:visited +{ + text-decoration: none; + color: #333; + border: none; + font-family: 'Cabin Sketch', cursive; +} + +div.topnav{ + float: right; + position: relative; + right:6px; + top:-65px; } diff --git a/askbot/doc/source/askbot/static/twitter.png b/askbot/doc/source/askbot/static/twitter.png Binary files differnew file mode 100644 index 00000000..371c9823 --- /dev/null +++ b/askbot/doc/source/askbot/static/twitter.png diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index 523a011f..c1eaa158 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -1,12 +1,29 @@ Changes in Askbot ================= -Development version (not yet on pypi) -------------------------------------- -* First user automatically becomes site administrator (Adolfo Fitoria) +Development version +------------------- +* Added support for Akismet spam detection service (Adolfo Fitoria) +* Added noscript message (Arun SAG) +* Support for url shortening with TinyUrl on link sharing (Rtnpro) +* Allowed logging in with password and email in the place of login name (Evgeny) +* Added config settings allowing adjust license information (Evgeny) -0.7.20 (Current Version) +0.7.22 (Current Version) ------------------------ +* Media resource revision is now incremented + automatically any time when media is updated (Adolfo Fitoria, Evgeny Fadeev) +* First user automatically becomes site administrator (Adolfo Fitoria) +* Avatar displayed on the sidebar can be controlled with livesettings.(Adolfo Fitoria, Evgeny Fadeev) +* Avatar box in the sidebar is ordered with priority for real faces.(Adolfo Fitoria) +* Django's createsuperuser now works with askbot (Adolfo Fitoria) + +0.7.21 +------ +This version was skipped + +0.7.20 +------ * Added support for login via self-hosted Wordpress site (Adolfo Fitoria) * Allowed basic markdown in the comments (Adolfo Fitoria) * Added this changelog (Adolfo Fitoria) diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst index c4c443d7..ea7ee34e 100644 --- a/askbot/doc/source/contributors.rst +++ b/askbot/doc/source/contributors.rst @@ -11,6 +11,7 @@ Programming and documentation * Mike Chen & Sailing Cai - original authors of CNPROG forum * Evgeny Fadeev - founder of askbot * `Adolfo Fitoria <http://fitoria.net>`_ +* `Arun SAG <http://zer0c00l.in/>`_ * Andy Knotts * Benoit Lavine (with Windriver Software, Inc.) * Jeff Madynski diff --git a/askbot/doc/source/customizing-skin-in-askbot.rst b/askbot/doc/source/customizing-skin-in-askbot.rst index 96c2ec9c..686fc3ed 100644 --- a/askbot/doc/source/customizing-skin-in-askbot.rst +++ b/askbot/doc/source/customizing-skin-in-askbot.rst @@ -56,7 +56,7 @@ Possible approaches to customize skins There are several methods at your disposal, would you like to customize askbot's appearance. -.. note:: +.. deprecated:: 0.7.21 Whenever you change any media files on disk, it will be necessary to increment "skin media revision number" in the skin settings and restart the app, @@ -120,13 +120,29 @@ by from the root account. Create a custom skin in a new directory --------------------------------------- This is technically possible, but not advisable -because a redesign of default skin is expected. +because a redesign of default skin is pending. +After the redesign your custom skins may be difficult +to update. If you still wish to follow this option, name all directories and files the same way as in the "default" skin, as some template file names are hard-coded in the askbot's python code. +Add setting ``ASKBOT_EXTRA_SKINS_DIR`` to your ``settings.py`` file +and set its value to the directory with your additional skins. + +For example:: + + ASKBOT_EXTRA_SKINS_DIR = '/home/myname/my_askbot_themes' + +And your directory structure might be:: + + /home/myname/my_askbot_themes/ + /my_theme + /templates + /media + If you are planning to seriously recode the skin - it will be worthwhile learning the ``git`` system and just follow the recipe described in the previous section - diff --git a/askbot/doc/source/index.rst b/askbot/doc/source/index.rst index 98dbf777..27f106be 100644 --- a/askbot/doc/source/index.rst +++ b/askbot/doc/source/index.rst @@ -1,8 +1,8 @@ .. _index: -============================= -Askbot Project Documentation -============================= +================== +Documentation Home +================== Askbot is an open source Question and Answer (Q&A) forum project inspired by StackOverflow and YahooAnswers. diff --git a/askbot/doc/source/initialize-database-tables.rst b/askbot/doc/source/initialize-database-tables.rst index f1281816..a296de3e 100644 --- a/askbot/doc/source/initialize-database-tables.rst +++ b/askbot/doc/source/initialize-database-tables.rst @@ -8,7 +8,11 @@ When you install Askbot the first time and any time you upgrade the software, ru python manage.py syncdb -When the script asks you if you want to create a superuser, answer **no**. +.. versionchanged:: 0.7.21 + When the script asks you if you want to create a superuser, answer yes if you want to create one. By default Askbot sets admin status(superuser) for the first user created automatically but also supports this form. + +.. deprecated:: 0.7.21 + When the script asks you if you want to create a superuser, answer **no**. Then run:: @@ -38,12 +42,12 @@ Connect to the Django development server with your Web browser. The address is t Once the fresh copy of Askbot appears in your browser, create a new account at the site. This will be your administrator account. -.. deprecated:: 0.7.20. +.. deprecated:: 0.7.20 Finally, turn the newly added user into a superuser by running:: python manage.py add_admin 1 -.. versionadded:: 0.7.20. +.. versionadded:: 0.7.20 In the new version of Askbot the first user you create on the site will be added as administrator. Here number 1 is the numeric id of the first user, enter a different number, if it is indeed different. diff --git a/askbot/doc/source/management-commands.rst b/askbot/doc/source/management-commands.rst index a91c0286..bc32dbc2 100644 --- a/askbot/doc/source/management-commands.rst +++ b/askbot/doc/source/management-commands.rst @@ -72,6 +72,12 @@ The bulk of the management commands fall into this group and will probably be th | | , including the questions that are themselves | | | marked as deleted. | +---------------------------------+-------------------------------------------------------------+ +| `update_avatar_data` | Set values of avatar types for all users; | +| | this command may take up to 2s per user, because it makes | +| | up to one http request per user to gravatar.com. | +| | This data is used to display preferentially real faces | +| | on the main page. | ++---------------------------------+-------------------------------------------------------------+ .. _email-related-commands: diff --git a/askbot/importers/stackexchange/management/commands/load_stackexchange.py b/askbot/importers/stackexchange/management/commands/load_stackexchange.py index 20155e36..1f6b2042 100644 --- a/askbot/importers/stackexchange/management/commands/load_stackexchange.py +++ b/askbot/importers/stackexchange/management/commands/load_stackexchange.py @@ -890,7 +890,7 @@ class Command(BaseCommand): u_openid.save() except AssertionError: print u'User %s (id=%d) does not have openid' % \ - (se_u.display_name, se_u.id) + (unidecode(se_u.display_name), se_u.id) sys.stdout.flush() except IntegrityError: print "Warning: have duplicate openid: %s" % se_u.open_id diff --git a/askbot/locale/en/LC_MESSAGES/django.po b/askbot/locale/en/LC_MESSAGES/django.po index 5bb75eca..a1548a93 100644 --- a/askbot/locale/en/LC_MESSAGES/django.po +++ b/askbot/locale/en/LC_MESSAGES/django.po @@ -499,7 +499,7 @@ msgstr "" #: conf/email.py:22 msgid "Prefix for the email subject line" -msgstr "Welcome to the Q&A forum" +msgstr "" #: conf/email.py:24 msgid "" diff --git a/askbot/management/commands/clean_session.py b/askbot/management/commands/clean_session.py new file mode 100644 index 00000000..6ba9352c --- /dev/null +++ b/askbot/management/commands/clean_session.py @@ -0,0 +1,50 @@ +from django.core.management.base import NoArgsCommand +from django.contrib.sessions.models import Session +from django.db import transaction +from optparse import make_option +from askbot.utils.console import print_progress +from datetime import datetime + +DELETE_LIMIT = 1000 + +class Command(NoArgsCommand): + option_list = NoArgsCommand.option_list + ( + make_option('--quiet', + action='store_true', + dest='quiet', + default=False, + help="Do not print anything when called." + ), + ) + + @transaction.commit_manually + def handle_noargs(self, **options): + '''deletes old sessions''' + quiet = options.get('quiet', False) + expired_session_count = Session.objects.filter(expire_date__lt=datetime.now()).count() + expired_session_list= Session.objects.filter(expire_date__lt=datetime.now()).values_list('session_key', flat=True) + transaction.commit() + + if not quiet: + print "There are %d expired sessions" % expired_session_count + + range_limit = len(expired_session_list) - 1 + higher_limit = lower_limit = 0 + + for i in range(DELETE_LIMIT, range_limit, DELETE_LIMIT): + lower_limit = i + higher_limit = lower_limit + DELETE_LIMIT + sublist = expired_session_list[lower_limit:higher_limit] + Session.objects.filter(session_key__in = sublist).delete() + transaction.commit() + if not quiet: + print_progress(higher_limit-1, expired_session_count) + + if higher_limit < expired_session_list: + sublist = expired_session_list[higher_limit:expired_session_count] + Session.objects.filter(session_key__in = sublist).delete() + print_progress(expired_session_count, expired_session_count) + transaction.commit() + + if not quiet: + print "sessions cleared" diff --git a/askbot/management/commands/createsuperuser.py b/askbot/management/commands/createsuperuser.py new file mode 100644 index 00000000..eb363bbd --- /dev/null +++ b/askbot/management/commands/createsuperuser.py @@ -0,0 +1,112 @@ +from django.contrib.auth.management.commands.createsuperuser import * +from django.db.models.signals import pre_save, post_save + +class Command(Command): + + def handle(self, *args, **options): + username = options.get('username', None) + email = options.get('email', None) + interactive = options.get('interactive') + verbosity = int(options.get('verbosity', 1)) + + # Do quick and dirty validation if --noinput + if not interactive: + if not username or not email: + raise CommandError("You must use --username and --email with --noinput.") + if not RE_VALID_USERNAME.match(username): + raise CommandError("Invalid username. Use only letters, digits, and underscores") + try: + is_valid_email(email) + except exceptions.ValidationError: + raise CommandError("Invalid email address.") + + # If not provided, create the user with an unusable password + password = None + + # Try to determine the current system user's username to use as a default. + try: + default_username = getpass.getuser().replace(' ', '').lower() + except (ImportError, KeyError): + # KeyError will be raised by os.getpwuid() (called by getuser()) + # if there is no corresponding entry in the /etc/passwd file + # (a very restricted chroot environment, for example). + default_username = '' + + # Determine whether the default username is taken, so we don't display + # it as an option. + if default_username: + try: + User.objects.get(username=default_username) + except User.DoesNotExist: + pass + else: + default_username = '' + + # Prompt for username/email/password. Enclose this whole thing in a + # try/except to trap for a keyboard interrupt and exit gracefully. + if interactive: + try: + + # Get a username + while 1: + if not username: + input_msg = 'Username' + if default_username: + input_msg += ' (Leave blank to use %r)' % default_username + username = raw_input(input_msg + ': ') + if default_username and username == '': + username = default_username + if not RE_VALID_USERNAME.match(username): + sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") + username = None + continue + try: + User.objects.get(username=username) + except User.DoesNotExist: + break + else: + sys.stderr.write("Error: That username is already taken.\n") + username = None + + # Get an email + while 1: + if not email: + email = raw_input('E-mail address: ') + try: + is_valid_email(email) + except exceptions.ValidationError: + sys.stderr.write("Error: That e-mail address is invalid.\n") + email = None + else: + break + + # Get a password + while 1: + if not password: + password = getpass.getpass() + password2 = getpass.getpass('Password (again): ') + if password != password2: + sys.stderr.write("Error: Your passwords didn't match.\n") + password = None + continue + if password.strip() == '': + sys.stderr.write("Error: Blank passwords aren't allowed.\n") + password = None + continue + break + except KeyboardInterrupt: + sys.stderr.write("\nOperation cancelled.\n") + sys.exit(1) + + self.remove_signals() + u = User.objects.create_superuser(username, email, password) + u.set_admin_status() + u.save() + + if verbosity >= 1: + self.stdout.write("Askbot Superuser created successfully.\n") + + + def remove_signals(self): + pre_save.receivers = [] + post_save.receivers = [] diff --git a/askbot/management/commands/update_avatar_data.py b/askbot/management/commands/update_avatar_data.py new file mode 100644 index 00000000..dd2749f4 --- /dev/null +++ b/askbot/management/commands/update_avatar_data.py @@ -0,0 +1,30 @@ +from django.core.management.base import NoArgsCommand +from django.contrib.auth.models import User +from django.db import transaction +from askbot.utils.console import print_action + +class Command(NoArgsCommand): + help = 'updates data about currently used avatars, ' + \ + 'necessary for display of avatars on the front page' + + @transaction.commit_manually + def handle_noargs(self, **options): + users = User.objects.all() + has_avatar = User.objects.exclude(avatar_type='n').count() + total_users = users.count() + print '%s users in total, %s have valid avatar' \ + % (total_users, has_avatar) + + for count, user in enumerate(users): + users_left = total_users - count + print_action( + 'Updating %s (%d users left)' % (user.username, users_left) + ) + user.update_avatar_type() + transaction.commit() + + print 'Updated all the users' + has_avatar = User.objects.exclude(avatar_type='n').count() + transaction.commit() + print '%s users in total, %s have valid avatar' \ + % (total_users, has_avatar) diff --git a/askbot/migrations/0005_install_badges.py b/askbot/migrations/0005_install_badges.py index 278f1ce1..f3c33fe5 100644 --- a/askbot/migrations/0005_install_badges.py +++ b/askbot/migrations/0005_install_badges.py @@ -4,6 +4,7 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from unidecode import unidecode INITIAL_BADGE_DATA = ( ('Disciplined', 3, 'disciplined', 'Deleted own post with score of 3 or higher', True, 0), @@ -58,9 +59,9 @@ class Migration(DataMigration): try: badge = orm.Badge.objects.get(name=name) - print 'already have badge %s' % name + print 'already have badge %s' % unidecode(name) except orm.Badge.DoesNotExist: - print 'adding new badge %s' % name + print 'adding new badge %s' % unidecode(name) badge = orm.Badge() badge.name = name @@ -79,9 +80,9 @@ class Migration(DataMigration): badge = orm.Badge.objects.get(name = name) badge.award_badge.clear() badge.delete() - print 'deleted badge %s' % name + print 'deleted badge %s' % unidecode(name) except orm.Badge.DoesNotExist: - print 'no such badge %s - so skipping' % name + print 'no such badge %s - so skipping' % unidecode(name) pass forum_app_name = os.path.basename(os.path.dirname(os.path.dirname(__file__))) diff --git a/askbot/migrations/0006_add_subscription_setting_for_comments_and_mentions.py b/askbot/migrations/0006_add_subscription_setting_for_comments_and_mentions.py index cf348c22..5d077ebb 100644 --- a/askbot/migrations/0006_add_subscription_setting_for_comments_and_mentions.py +++ b/askbot/migrations/0006_add_subscription_setting_for_comments_and_mentions.py @@ -4,6 +4,7 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from unidecode import unidecode try: from forum import const email_feed_setting_model = 'forum.EmailFeedSetting' @@ -45,7 +46,7 @@ class Migration(DataMigration): verbose_frequency = dict(const.NOTIFICATION_DELIVERY_SCHEDULE_CHOICES)[frequency] print 'added \'%s\' subscription for %s (%d)' % ( verbose_frequency, - user.username, + unidecode(user.username), user.id ) diff --git a/askbot/migrations/0009_calculate_html_field_for_comments.py b/askbot/migrations/0009_calculate_html_field_for_comments.py index 53de1832..41535dd6 100644 --- a/askbot/migrations/0009_calculate_html_field_for_comments.py +++ b/askbot/migrations/0009_calculate_html_field_for_comments.py @@ -159,8 +159,6 @@ class Migration(DataMigration): all_users = all_users, orm = orm ) - #print 'was %s' % comment.comment - #print 'now %s' % comment.html comment.save() def backwards(self, orm): diff --git a/askbot/migrations/0024_add_recipients_m2m_to_activity_and_denorm_question_on_activity.py b/askbot/migrations/0024_add_recipients_m2m_to_activity_and_denorm_question_on_activity.py index c5213526..5371d7b1 100644 --- a/askbot/migrations/0024_add_recipients_m2m_to_activity_and_denorm_question_on_activity.py +++ b/askbot/migrations/0024_add_recipients_m2m_to_activity_and_denorm_question_on_activity.py @@ -5,6 +5,7 @@ from south.v2 import DataMigration from django.db import models from askbot import const from askbot.migrations_api.version1 import API +from unidecode import unidecode #some of activities are not related to question, so they are not processed here APPROPRIATE_ACTIVITIES = ( @@ -51,7 +52,7 @@ class Migration(DataMigration): if have_problems: print 'Migration is now complete, but there were some errors:' - print '\n'.join(errors) + print unidecode('\n'.join(errors)) print 'problematic activity objects are: ' + ','.join(bad_ids) print 'This is most likely not a big issue, but if you save this error message' print 'and email to admin@askbot.org, that would help. Thanks.' diff --git a/askbot/migrations/0043_add_temporal_extra_column_for_datamigration.py b/askbot/migrations/0043_add_temporal_extra_column_for_datamigration.py new file mode 100644 index 00000000..4a8140a4 --- /dev/null +++ b/askbot/migrations/0043_add_temporal_extra_column_for_datamigration.py @@ -0,0 +1,308 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + try: + db.add_column(u'auth_user', 'avatar_type', self.gf('django.db.models.fields.CharField')(max_length=1, default='n'), keep_default=False) + except: + pass + + def backwards(self, orm): + db.delete_column(u'auth_user', 'avatar_type') + + 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.Question']", '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.Question']"}), + '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.answer': { + 'Meta': {'object_name': 'Answer', 'db_table': "u'answer'"}, + 'accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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_answers'", '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_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['askbot.Question']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + '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.answerrevision': { + 'Meta': {'ordering': "('-revision',)", 'object_name': 'AnswerRevision', 'db_table': "u'answer_revision'"}, + 'answer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Answer']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answerrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + '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.comment': { + 'Meta': {'ordering': "('-added_at',)", 'object_name': 'Comment', 'db_table': "u'comment'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'html': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2048'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'offensive_flag_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['auth.User']"}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'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'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Question']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + '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.question': { + 'Meta': {'object_name': 'Question', 'db_table': "u'question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questions'", 'to': "orm['auth.User']"}), + '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', [], {'blank': 'True', 'related_name': "'closed_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'favorite_questions'", '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_questions'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_active_in_questions'", 'to': "orm['auth.User']"}), + '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_questions'", '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_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'questions'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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.questionrevision': { + 'Meta': {'ordering': "('-revision',)", 'object_name': 'QuestionRevision', 'db_table': "u'question_revision'"}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questionrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Question']"}), + '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', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}) + }, + '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.Question']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", '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.Question']", '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'}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('content_type', 'object_id', 'user'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + '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'}) + }, + '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'}), + '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_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'}), + 'has_custom_avatar': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/migrations/0044_migrate_has_custom_avatar_field.py b/askbot/migrations/0044_migrate_has_custom_avatar_field.py new file mode 100644 index 00000000..2222e871 --- /dev/null +++ b/askbot/migrations/0044_migrate_has_custom_avatar_field.py @@ -0,0 +1,323 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models +from askbot.utils.console import print_action +from unidecode import unidecode + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + print 'Migrating users to new avatar field' + for user in orm['auth.user'].objects.all(): + print_action('migrating user: %s' % unidecode(user.username)) + if user.has_custom_avatar == True: + user.avatar_type = 'a' + else: + user.avatar_type = 'n' + user.save() + print_action( + 'user %s migrated avatar_type: %s' % \ + (unidecode(user.username), user.avatar_type) + ) + + + def backwards(self, orm): + "Write your backwards methods here." + pass + + + models = { + 'askbot.activity': { + 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"}, + 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Question']", '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.Question']"}), + '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.answer': { + 'Meta': {'object_name': 'Answer', 'db_table': "u'answer'"}, + 'accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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_answers'", '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_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['askbot.Question']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + '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.answerrevision': { + 'Meta': {'ordering': "('-revision',)", 'object_name': 'AnswerRevision', 'db_table': "u'answer_revision'"}, + 'answer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Answer']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answerrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + '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.comment': { + 'Meta': {'ordering': "('-added_at',)", 'object_name': 'Comment', 'db_table': "u'comment'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'html': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2048'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'offensive_flag_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['auth.User']"}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'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'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Question']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + '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.question': { + 'Meta': {'object_name': 'Question', 'db_table': "u'question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questions'", 'to': "orm['auth.User']"}), + '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', [], {'blank': 'True', 'related_name': "'closed_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'favorite_questions'", '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_questions'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_active_in_questions'", 'to': "orm['auth.User']"}), + '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_questions'", '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_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'questions'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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.questionrevision': { + 'Meta': {'ordering': "('-revision',)", 'object_name': 'QuestionRevision', 'db_table': "u'question_revision'"}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questionrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Question']"}), + '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', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}) + }, + '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.Question']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", '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.Question']", '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'}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('content_type', 'object_id', 'user'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + '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'}) + }, + '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_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'}), + 'has_custom_avatar': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/migrations/0045_delete_has_custom_avatar.py b/askbot/migrations/0045_delete_has_custom_avatar.py new file mode 100644 index 00000000..0653c14f --- /dev/null +++ b/askbot/migrations/0045_delete_has_custom_avatar.py @@ -0,0 +1,310 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.delete_column(u'auth_user', 'has_custom_avatar') + + def backwards(self, orm): + try: + db.add_column(u'auth_user', 'has_custom_avatar', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False) + except: + 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.Question']", '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.Question']"}), + '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.answer': { + 'Meta': {'object_name': 'Answer', 'db_table': "u'answer'"}, + 'accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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_answers'", '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_answers'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answers'", 'to': "orm['askbot.Question']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + '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.answerrevision': { + 'Meta': {'ordering': "('-revision',)", 'object_name': 'AnswerRevision', 'db_table': "u'answer_revision'"}, + 'answer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Answer']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'answerrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + '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.comment': { + 'Meta': {'ordering': "('-added_at',)", 'object_name': 'Comment', 'db_table': "u'comment'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '2048'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'html': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2048'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'offensive_flag_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['auth.User']"}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'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'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Question']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + '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.question': { + 'Meta': {'object_name': 'Question', 'db_table': "u'question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questions'", 'to': "orm['auth.User']"}), + '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', [], {'blank': 'True', 'related_name': "'closed_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'favorite_questions'", '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_questions'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_active_in_questions'", 'to': "orm['auth.User']"}), + '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_questions'", '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_questions'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'questions'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + '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.questionrevision': { + 'Meta': {'ordering': "('-revision',)", 'object_name': 'QuestionRevision', 'db_table': "u'question_revision'"}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'questionrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'revisions'", 'to': "orm['askbot.Question']"}), + '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', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}) + }, + '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.Question']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", '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.Question']", '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'}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('content_type', 'object_id', 'user'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + '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'}) + }, + '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_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'}), + 'has_custom_avatar': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index ce6d4881..85533ece 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -58,7 +58,13 @@ User.add_to_class('reputation', models.PositiveIntegerField(default=const.MIN_REPUTATION) ) User.add_to_class('gravatar', models.CharField(max_length=32)) -User.add_to_class('has_custom_avatar', models.BooleanField(default=False)) +#User.add_to_class('has_custom_avatar', models.BooleanField(default=False)) +User.add_to_class( + 'avatar_type', + models.CharField(max_length=1, + choices=const.AVATAR_STATUS_CHOICE, + default='n') +) User.add_to_class('gold', models.SmallIntegerField(default=0)) User.add_to_class('silver', models.SmallIntegerField(default=0)) User.add_to_class('bronze', models.SmallIntegerField(default=0)) @@ -120,38 +126,51 @@ def user_get_avatar_url(self, size): it will use avatar provided through that app """ if 'avatar' in django_settings.INSTALLED_APPS: - if self.has_custom_avatar == False: + if self.avatar_type == 'n': import avatar if avatar.settings.AVATAR_GRAVATAR_BACKUP: return self.get_gravatar_url(size) else: return avatar.utils.get_default_avatar_url() - kwargs = {'user_id': self.id, 'size': size} - try: - return reverse('avatar_render_primary', kwargs = kwargs) - except NoReverseMatch: - message = 'Please, make sure that avatar urls are in the urls.py '\ - 'or update your django-avatar app, '\ - 'currently it is impossible to serve avatars.' - logging.critical(message) - raise django_exceptions.ImproperlyConfigured(message) + elif self.avatar_type == 'a': + kwargs = {'user_id': self.id, 'size': size} + try: + return reverse('avatar_render_primary', kwargs = kwargs) + except NoReverseMatch: + message = 'Please, make sure that avatar urls are in the urls.py '\ + 'or update your django-avatar app, '\ + 'currently it is impossible to serve avatars.' + logging.critical(message) + raise django_exceptions.ImproperlyConfigured(message) + else: + return self.get_gravatar_url(size) else: return self.get_gravatar_url(size) - -def user_update_has_custom_avatar(self): +def user_update_avatar_type(self): """counts number of custom avatars - and if zero, sets has_custom_avatar to False, + and if zero, sets avatar_type to False, True otherwise. The method is called only if avatar application is installed. Saves the object. """ - if self.avatar_set.count() > 0: - self.has_custom_avatar = True + + if 'avatar' in django_settings.INSTALLED_APPS: + if self.avatar_set.count() > 0: + self.avatar_type = 'a' + else: + self.avatar_type = _check_gravatar(self.gravatar) else: - self.has_custom_avatar = False + self.avatar_type = _check_gravatar(self.gravatar) self.save() +def _check_gravatar(gravatar): + gravatar_url = "http://www.gravatar.com/avatar/%s?d=404" % gravatar + code = urllib.urlopen(gravatar_url).getcode() + if urllib.urlopen(gravatar_url).getcode() != 404: + return 'g' #gravatar + else: + return 'n' #none def user_get_old_vote_for_post(self, post): """returns previous vote for this post @@ -1994,7 +2013,10 @@ User.add_to_class( 'add_missing_askbot_subscriptions', user_add_missing_askbot_subscriptions ) -User.add_to_class('is_username_taken',classmethod(user_is_username_taken)) +User.add_to_class( + 'is_username_taken', + classmethod(user_is_username_taken) +) User.add_to_class( 'get_followed_question_alert_frequency', user_get_followed_question_alert_frequency @@ -2007,7 +2029,7 @@ User.add_to_class('get_absolute_url', user_get_absolute_url) User.add_to_class('get_avatar_url', user_get_avatar_url) User.add_to_class('get_gravatar_url', user_get_gravatar_url) User.add_to_class('get_anonymous_name', user_get_anonymous_name) -User.add_to_class('update_has_custom_avatar', user_update_has_custom_avatar) +User.add_to_class('update_avatar_type', user_update_avatar_type) User.add_to_class('post_question', user_post_question) User.add_to_class('edit_question', user_edit_question) User.add_to_class('retag_question', user_retag_question) @@ -2547,11 +2569,12 @@ def post_anonymous_askbot_content( they are not used in this function""" user.post_anonymous_askbot_content(session_key) -def set_user_has_custom_avatar_flag(instance, created, **kwargs): - instance.user.update_has_custom_avatar() +def set_user_avatar_type_flag(instance, created, **kwargs): + instance.user.update_avatar_type() + +def update_user_avatar_type_flag(instance, **kwargs): + instance.user.update_avatar_type() -def update_user_has_custom_avatar_flag(instance, **kwargs): - instance.user.update_has_custom_avatar() def make_admin_if_first_user(instance, **kwargs): user_count = User.objects.all().count() @@ -2570,14 +2593,15 @@ django_signals.post_save.connect( record_favorite_question, sender=FavoriteQuestion ) + if 'avatar' in django_settings.INSTALLED_APPS: from avatar.models import Avatar django_signals.post_save.connect( - set_user_has_custom_avatar_flag, + set_user_avatar_type_flag, sender=Avatar ) django_signals.post_delete.connect( - update_user_has_custom_avatar_flag, + update_user_avatar_type_flag, sender=Avatar ) @@ -2644,5 +2668,3 @@ __all__ = [ 'get_model' ] - - diff --git a/askbot/models/question.py b/askbot/models/question.py index 330d3fdb..0ddb5b08 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -373,7 +373,10 @@ class QuestionQuerySet(models.query.QuerySet): u_id = u_id.union( set(Answer.objects.filter(id__in=a_id).values_list('author', flat=True)) ) - contributors = User.objects.filter(id__in=u_id).order_by('?') + + from askbot.conf import settings as askbot_settings + avatar_limit = askbot_settings.SIDEBAR_MAIN_AVATAR_LIMIT + contributors = User.objects.filter(id__in=u_id).order_by('avatar_type', '?')[:avatar_limit] #print contributors #could not optimize this query with indices so it was split into what's now above #contributors = User.objects.filter( diff --git a/askbot/schedules.py b/askbot/schedules.py new file mode 100644 index 00000000..b9bbdbc8 --- /dev/null +++ b/askbot/schedules.py @@ -0,0 +1,19 @@ +"""tests on whether certain scheduled tasks need +to be performed at the moment""" +from datetime import datetime + +def should_update_avatar_data(request): + """True if it is time to update user's avatar data + user is taken from the request object + """ + user = request.user + if user.is_authenticated(): + if (datetime.today() - user.last_login).days <= 1: + #avatar is updated on login anyway + return False + updated_at = request.session.get('avatar_data_updated_at', None) + if updated_at is None: + return True + else: + return (datetime.now() - updated_at).days > 0 + return False diff --git a/askbot/skins/default/media/images/cc-by-sa.png b/askbot/skins/default/media/images/cc-by-sa.png Binary files differnew file mode 100644 index 00000000..f0a944e0 --- /dev/null +++ b/askbot/skins/default/media/images/cc-by-sa.png diff --git a/askbot/skins/default/media/images/cc-wiki.png b/askbot/skins/default/media/images/cc-wiki.png Binary files differdeleted file mode 100755 index 3e680538..00000000 --- a/askbot/skins/default/media/images/cc-wiki.png +++ /dev/null diff --git a/askbot/skins/default/media/jquery-openid/jquery.openid.js b/askbot/skins/default/media/jquery-openid/jquery.openid.js index bb76fcd6..7ba9adce 100644 --- a/askbot/skins/default/media/jquery-openid/jquery.openid.js +++ b/askbot/skins/default/media/jquery-openid/jquery.openid.js @@ -287,6 +287,9 @@ $.fn.authenticator = function() { signin_form.submit(); } else { + if (FB.getSession()){ + signin_form.submit(); + } FB.login(); } }); @@ -295,9 +298,21 @@ $.fn.authenticator = function() { }; var start_password_login_or_change = function(){ + //called upon clicking on one of the password login buttons reset_form(); set_provider_name($(this)); var provider_name = $(this).attr('name'); + return setup_password_login_or_change(provider_name); + }; + + var init_always_visible_password_login = function(){ + reset_form(); + //will break wordpress and ldap + provider_name_input.val('local'); + setup_password_login_or_change('local'); + }; + + var setup_password_login_or_change = function(provider_name){ var token_name = extra_token_name[provider_name] var password_action_input = $('input[name=password_action]'); if (userIsAuthenticated === true){ @@ -424,6 +439,9 @@ $.fn.authenticator = function() { }; setup_default_handlers(); + if (askbot['settings']['signin_always_show_local_login'] === true){ + init_always_visible_password_login(); + } clear_password_fields(); return this; }; diff --git a/askbot/skins/default/media/js/post.js b/askbot/skins/default/media/js/post.js index 8216c10a..52772cc0 100644 --- a/askbot/skins/default/media/js/post.js +++ b/askbot/skins/default/media/js/post.js @@ -1040,12 +1040,14 @@ EditCommentForm.prototype.createDom = function(){ this._textarea = $('<textarea></textarea>'); this._textarea.attr('id', this._id); + /* this._help_text = $('<span></span>').attr('class', 'help-text'); this._help_text.html(gettext('Markdown is allowed in the comments')); div.append(this._help_text); this._help_text = $('<div></div>').attr('class', 'clearfix'); div.append(this._help_text); + */ this._element.append(div); div.append(this._textarea); @@ -1270,7 +1272,7 @@ Comment.prototype.setContent = function(data){ this._comment_body = $('<div class="comment-body"></div>'); this._comment_body.html(this._data['html']); - this._comment_body.append(' – '); + //this._comment_body.append(' – '); this._user_link = $('<a></a>').attr('class', 'author'); this._user_link.attr('href', this._data['user_url']); @@ -1531,7 +1533,7 @@ var socialSharing = function(){ var SERVICE_DATA = { //url - template for the sharing service url, params are for the popup identica: { - url: "http://identi.ca/index.php?action=newnotice&status_textarea={TEXT}", + url: "http://identi.ca/notice/new?status_textarea={TEXT}%20{URL}", params: "width=820, height=526,toolbar=1,status=1,resizable=1,scrollbars=1" }, twitter: { @@ -1553,12 +1555,25 @@ var socialSharing = function(){ var share_page = function(service_name){ if (SERVICE_DATA[service_name]){ var url = SERVICE_DATA[service_name]['url']; - url = url.replace('{URL}', URL); - url = url.replace('{TEXT}', TEXT); - var params = SERVICE_DATA[service_name]['params']; - if(!window.open(url, "sharing", params)){ - window.location.href=share_url; - } + $.ajax({ + async: false, + url: "http://json-tinyurl.appspot.com/?&callback=?", + dataType: "json", + data: {'url':URL}, + success: function(data){ + url = url.replace('{URL}', data.tinyurl); + }, + error: function(data){ + url = url.replace('{URL}', URL); + }, + complete: function(data){ + url = url.replace('{TEXT}', TEXT); + var params = SERVICE_DATA[service_name]['params']; + if(!window.open(url, "sharing", params)){ + window.location.href=share_url; + } + } + }); } } @@ -1572,6 +1587,8 @@ var socialSharing = function(){ var ica = $('a.identica-share'); copyAltToTitle(fb); copyAltToTitle(tw); + copyAltToTitle(ln); + copyAltToTitle(ica); setupButtonEventHandlers(fb, function(){share_page("facebook")}); setupButtonEventHandlers(tw, function(){share_page("twitter")}); setupButtonEventHandlers(ln, function(){share_page("linkedin")}); diff --git a/askbot/skins/default/media/js/utils.js b/askbot/skins/default/media/js/utils.js index c49da02b..ec55e535 100644 --- a/askbot/skins/default/media/js/utils.js +++ b/askbot/skins/default/media/js/utils.js @@ -3,6 +3,11 @@ var mediaUrl = function(resource){ return scriptUrl + 'm/' + askbotSkin + '/' + resource; }; +var cleanUrl = function(url){ + var re = new RegExp('//', 'g'); + return url.replace(re, '/'); +}; + var copyAltToTitle = function(sel){ sel.attr('title', sel.attr('alt')); }; diff --git a/askbot/skins/default/media/style/jquery.autocomplete.css b/askbot/skins/default/media/style/jquery.autocomplete.css index 1ad98ddf..b3d7b759 100644 --- a/askbot/skins/default/media/style/jquery.autocomplete.css +++ b/askbot/skins/default/media/style/jquery.autocomplete.css @@ -28,7 +28,7 @@ } .acLoading { - background : url('indicator.gif') right center no-repeat; + background : url('../images/indicator.gif') right center no-repeat; } .acSelect { diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css index 7627be5e..1431135f 100644 --- a/askbot/skins/default/media/style/style.css +++ b/askbot/skins/default/media/style/style.css @@ -194,6 +194,10 @@ blockquote { background: #777; } +#ground p { + margin-bottom:0; +} + #ab-logo { padding: 0px 0px 0px 10px; position: absolute; @@ -203,6 +207,10 @@ blockquote { width: 70px; } +img.license-logo { + margin: 6px 0 10px 0; +} + #ab-meta-nav, #ab-main-nav { position: absolute; @@ -1100,6 +1108,12 @@ div.comment .comment-body { color: #666; } +div.comment .comment-body p{ + font-size:inherit; + margin-bottom: 3px; + padding: 0; +} + div.comment .comment-delete { float: right; width: 14px; @@ -1147,13 +1161,15 @@ div.comment .upvote:hover { .comments textarea { display: block; - height: 48px; - width: 560px; - margin: 3px 0px; + height: 42px; + width: 572px; + margin: 6px 0 5px 1px; font-family: sans-serif; + outline: none; + overflow:auto; font-size: 12px; - line-height: 130%; - padding: 4px; + line-height: 140%; + padding-left:2px; } .comments input { @@ -1184,6 +1200,9 @@ div.comment .upvote:hover { float: right; text-align:right; color: gray; + margin-bottom: 0px; + margin-top: 0px; + line-height: 50%; } span.text-counter { @@ -1900,6 +1919,21 @@ button::-moz-focus-inner { text-decoration: none; } +.noscript { + position: fixed; + top: 0px; + left: 0px; + width: 100%; + z-index: 100; + padding: 5px 0; + text-align: center; + font-family: sans-serif; + font-size: 120%; + font-weight: Bold; + color: #FFFFFF; + background-color: #AE0000; +} + .big { font-size: 15px; } diff --git a/askbot/skins/default/templates/authopenid/providers_javascript.html b/askbot/skins/default/templates/authopenid/providers_javascript.html index 6ef86b29..0fe72eb3 100644 --- a/askbot/skins/default/templates/authopenid/providers_javascript.html +++ b/askbot/skins/default/templates/authopenid/providers_javascript.html @@ -34,13 +34,8 @@ {% else %} var userIsAuthenticated = false; {% endif %} - $("body").authenticator(); - {% if settings.SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN %} - {% if settings.SIGNIN_LOCAL_ENABLED %} - $('input.password').remove(); - {% endif %} - {%endif%} askbot['settings']['signin_always_show_local_login'] = {% if settings.SIGNIN_ALWAYS_SHOW_LOCAL_LOGIN %}true{% else %}false{% endif %}; + $("body").authenticator(); </script> {% if settings.FACEBOOK_KEY and settings.FACEBOOK_SECRET %} <div id="fb-root"></div> @@ -51,11 +46,8 @@ var ret = FB.init({appId: '{{settings.FACEBOOK_KEY}}', status: true, cookie: true, xfbml: true}); FB.Event.subscribe('auth.sessionChange', function(response){ if (response.session) { - // A user has logged in, and a new cookie has been saved $('#signin-form').submit(); - } else { - // The user has logged out, and the cookie has been cleared - } + } }); }; }); diff --git a/askbot/skins/default/templates/authopenid/signin.html b/askbot/skins/default/templates/authopenid/signin.html index 37636207..49c447a1 100644 --- a/askbot/skins/default/templates/authopenid/signin.html +++ b/askbot/skins/default/templates/authopenid/signin.html @@ -94,7 +94,7 @@ {% endif %}
<table class="login">
<tr>
- <td><label for="id_username">{% trans %}Login name{% endtrans %}</label></td>
+ <td><label for="id_username">{% trans %}Login or email{% endtrans %}</label></td>
<td>{{login_form.username}}</td>
</tr>
<tr>
diff --git a/askbot/skins/default/templates/base.html b/askbot/skins/default/templates/base.html index 31abe043..9f779945 100644 --- a/askbot/skins/default/templates/base.html +++ b/askbot/skins/default/templates/base.html @@ -60,6 +60,11 @@ {% include "blocks/bottom_scripts.html" %} {% block endjs %} {% endblock %} + <script type="text/javascript"> + for (url_name in askbot['urls']){ + askbot['urls'][url_name] = cleanUrl(askbot['urls'][url_name]); + } + </script> </body> </html> <!-- end template base.html --> diff --git a/askbot/skins/default/templates/blocks/bottom_scripts.html b/askbot/skins/default/templates/blocks/bottom_scripts.html index aaad70c0..771c13a4 100644 --- a/askbot/skins/default/templates/blocks/bottom_scripts.html +++ b/askbot/skins/default/templates/blocks/bottom_scripts.html @@ -2,6 +2,11 @@ this template is included at the very bottow of the main template "base.html" #} +<div id="no-javascript"> + <noscript class="noscript"> + {% trans app_name = settings.APP_SHORT_NAME %}Please note: {{app_name}} requires javascript to work properly, please enable javascript in your browser, <a href="{{noscript_url}}">here is how</a>{% endtrans %} + </noscript> +</div> <script type="text/javascript"> var i18nLang = '{{settings.LANGUAGE_CODE}}'; var scriptUrl = '/{{settings.ASKBOT_URL}}' diff --git a/askbot/skins/default/templates/blocks/footer.html b/askbot/skins/default/templates/blocks/footer.html index 582e6d33..94c409ef 100644 --- a/askbot/skins/default/templates/blocks/footer.html +++ b/askbot/skins/default/templates/blocks/footer.html @@ -1,32 +1,53 @@ <!-- template footer.html --> <div id="ground"> - <div> - <div class="footerLinks" > - <a href="{% url about %}">{% trans %}about{% endtrans %}</a><span class="link-separator"> |</span> - <a href="{% url faq %}">{% trans %}faq{% endtrans %}</a><span class="link-separator"> |</span> - <a href="{% url privacy %}">{% trans %}privacy policy{% endtrans %}</a><span class="link-separator"> |</span> - {% spaceless %} - <a href= - {% if settings.FEEDBACK_SITE_URL %} - "{{settings.FEEDBACK_SITE_URL}}" - target="_blank"> - {% else %} - "{% url feedback %}?next={{request.path}}"> - {% endif %} - {% trans %}give feedback{% endtrans %} - </a> - {% endspaceless %} - </div> - <p> - <a href="http://askbot.org" target="_blank"> - powered by ASKBOT version {{settings.ASKBOT_VERSION}} - </a><br/>{{settings.APP_COPYRIGHT}} - </p> - </div> - <div id="licenseLogo"> - <a href="http://creativecommons.org/licenses/by/3.0/"> - <img src="{{"/images/cc-wiki.png"|media }}" title="Creative Commons: Attribution - Share Alike" alt="cc-wiki" width="50" height="68" /> + <p class="footerLinks" > + <a href="{% url about %}">{% trans %}about{% endtrans %}</a><span class="link-separator"> |</span> + <a href="{% url faq %}">{% trans %}faq{% endtrans %}</a><span class="link-separator"> |</span> + <a href="{% url privacy %}">{% trans %}privacy policy{% endtrans %}</a><span class="link-separator"> |</span> + {% spaceless %} + <a href= + {% if settings.FEEDBACK_SITE_URL %} + "{{settings.FEEDBACK_SITE_URL}}" + target="_blank"> + {% else %} + "{% url feedback %}?next={{request.path}}"> + {% endif %} + {% trans %}give feedback{% endtrans %} + </a> + {% endspaceless %} + </p> + <p> + <a href="http://askbot.org" target="_blank"> + Powered by Askbot version {{settings.ASKBOT_VERSION}} </a> - </div> + </p> + {% if settings.USE_LICENSE %}{# could be factored out into separate template #} + {% if settings.LICENSE_USE_URL %} + <p>{{settings.APP_COPYRIGHT}} {% trans + license_title=settings.LICENSE_TITLE, + license_url=settings.LICENSE_URL + %}Content on this site is licensed under a <a href="{{license_url}}">{{license_title}}</a> license.{% endtrans %}</p> + {% if settings.LICENSE_USE_LOGO %} + <a href="{{ settings.LICENSE_URL}}"> + <img + class="license-logo" + src="{{settings.LICENSE_LOGO_URL|media }}" + title="{{settings.LICENSE_ACRONYM}}" + alt="{{settings.LICENSE_ACRONYM}}" + /> + </a> + {% endif %} + {% else %} + <p>{{settings.APP_COPYRIGHT}} {% trans license=settings.LICENSE_TITLE %}Content on this site is licensed under a {{license}}{% endtrans %}</p> + {% if settings.LICENSE_USE_LOGO %} + <img + class="license-logo" + src="{{settings.LICENSE_LOGO_URL|media }}" + title="{{settings.LICENSE_ACRONYM}}" + alt="{{settings.LICENSE_ACRONYM}}" + /> + {% endif %} + {% endif %} + {% endif %} </div> <!-- end template footer.html --> diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index fa15426f..7ddbd70f 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -22,7 +22,7 @@ {%- macro share(site = None, site_label = None, icon = False) -%} <a class="{{ site }}-share{% if icon == True %} icon{% endif %}" - alt="{% trans %}Share this question on {{site}}{% endtrans %}" + title="{% trans %}Share this question on {{site}}{% endtrans %}" >{% if icon == False %}{% if site_label %}{{ site_label }}{% else %}{{ site }}{% endif %}{% endif %}</a> {%- endmacro -%} @@ -138,8 +138,7 @@ {%- macro paginator(p, position='left') -%}{# p is paginator context dictionary #} {% spaceless %} {% if p.is_paginated %} - <div class="paginator"> - <div style="float:{{position}}"> + <div class="paginator" style="float:{{position}}"> {% if p.has_previous %} <span class="prev"><a href="{{p.base_url}}page={{ p.previous }}{{ p.extend_url }}" title="{% trans %}previous{% endtrans %}"> « {% trans %}previous{% endtrans %}</a></span> @@ -169,7 +168,6 @@ <span class="next"><a href="{{p.base_url}}page={{ p.next }}{{ p.extend_url }}" title="{% trans %}next page{% endtrans %}">{% trans %}next page{% endtrans %} »</a></span> {% endif %} </div> - </div> {% endif %} {% endspaceless %} {%- endmacro -%} @@ -494,7 +492,7 @@ for the purposes of the AJAX comment editor #} {% endif %} </div> <div class="comment-body"> - {{comment.html}} – + {{comment.html}} <a class="author" href="{{comment.user.get_profile_url()}}" diff --git a/askbot/skins/default/templates/main_page/javascript.html b/askbot/skins/default/templates/main_page/javascript.html index 09a5d15b..14dfe3cd 100644 --- a/askbot/skins/default/templates/main_page/javascript.html +++ b/askbot/skins/default/templates/main_page/javascript.html @@ -9,10 +9,14 @@ Hilite.exact = false; Hilite.elementid = "question-list"; Hilite.debug_referrer = location.href; + {% if update_avatar_data == True %} + var today = new Date();{#add timestamp to prevent browser caching #} + $.getJSON('{% url user_update_has_custom_avatar %}?t=' + today.getTime()); + {% endif %} }); - askbot['urls']['mark_interesting_tag'] = scriptUrl + '{% trans %}mark-tag/{% endtrans %}{% trans %}interesting/{% endtrans %}'; - askbot['urls']['mark_ignored_tag'] = scriptUrl + '{% trans %}mark-tag/{% endtrans %}{% trans %}ignored/{% endtrans %}'; - askbot['urls']['unmark_tag'] = scriptUrl + '{% trans %}unmark-tag/{% endtrans %}'; + askbot['urls']['mark_interesting_tag'] = scriptUrl + '{% url mark_interesting_tag %}'; + askbot['urls']['mark_ignored_tag'] = scriptUrl + '{% url mark_ignored_tag %}'; + askbot['urls']['unmark_tag'] = scriptUrl + '{% url unmark_tag %}'; askbot['urls']['set_tag_filter_strategy'] = '{% url "set_tag_filter_strategy" %}'; askbot['urls']['questions'] = '{% url "questions" %}'; askbot['urls']['question_url_template'] = scriptUrl + '{% trans %}question/{% endtrans %}{{ "{{QuestionID}}/" }}'; diff --git a/askbot/skins/default/templates/tags.html b/askbot/skins/default/templates/tags.html index d37cc9e7..7d9026af 100644 --- a/askbot/skins/default/templates/tags.html +++ b/askbot/skins/default/templates/tags.html @@ -31,6 +31,7 @@ <span>{% trans %}Nothing found{% endtrans %}</span> {% endif %} {% if tags.object_list %} + <div class='clearfix'></div> <ul class='tags'> {% for tag in tags.object_list %} <li> diff --git a/askbot/skins/utils.py b/askbot/skins/utils.py index 7eaeb304..e7997f7a 100644 --- a/askbot/skins/utils.py +++ b/askbot/skins/utils.py @@ -10,6 +10,7 @@ import logging import urllib from django.conf import settings as django_settings from django.utils.datastructures import SortedDict +from askbot.utils import hasher class MediaNotFound(Exception): """raised when media file is not found""" @@ -140,10 +141,11 @@ def get_media_url(url, ignore_missing = False): #determine from which skin take the media file try: use_skin = resolve_skin_for_media(media=url, preferred_skin = use_skin) - except MediaNotFound, e: - log_message = 'missing media resource %s in skin %s' \ - % (url, use_skin) - logging.critical(log_message) + except MediaNotFound: + if ignore_missing == False: + log_message = 'missing media resource %s in skin %s' \ + % (url, use_skin) + logging.critical(log_message) return None url = use_skin + '/media/' + url @@ -160,3 +162,33 @@ def get_media_url(url, ignore_missing = False): #after = datetime.datetime.now() #print after - before return url + +def update_media_revision(skin = None): + """update skin media revision number based on the contents + of the skin media directory""" + from askbot.conf import settings as askbot_settings + resource_revision = askbot_settings.MEDIA_RESOURCE_REVISION + + if skin: + if skin in get_skin_choices(): + skin_path = get_path_to_skin(skin) + else: + raise MediaNotFound('Skin %s not found' % skin) + else: + skin = 'default' + skin_path = get_path_to_skin(askbot_settings.ASKBOT_DEFAULT_SKIN) + + media_dirs = [os.path.join(skin_path, 'media'),] + + if skin != 'default': + #we have default skin as parent of the custom skin + default_skin_path = get_path_to_skin('default') + media_dirs.append(os.path.join(default_skin_path, 'media')) + + current_hash = hasher.get_hash_of_dirs(media_dirs) + + if current_hash != askbot_settings.MEDIA_RESOURCE_REVISION_HASH: + askbot_settings.update('MEDIA_RESOURCE_REVISION', resource_revision + 1) + askbot_settings.update('MEDIA_RESOURCE_REVISION_HASH', current_hash) + logging.debug('MEDIA_RESOURCE_REVISION changed') + askbot_settings.MEDIA_RESOURCE_REVISION diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py index bb269600..cc7f86c0 100644 --- a/askbot/startup_procedures.py +++ b/askbot/startup_procedures.py @@ -22,6 +22,7 @@ PREAMBLE = """\n ************************""" def askbot_warning(line): + """prints a warning with the nice header, but does not quit""" print >> sys.stderr, PREAMBLE + '\n' + line def format_as_text_tuple_entries(items): @@ -81,7 +82,9 @@ def test_middleware(): 'askbot.middleware.view_log.ViewLogMiddleware', ) if 'debug_toolbar' in django_settings.INSTALLED_APPS: - required_middleware += ('debug_toolbar.middleware.DebugToolbarMiddleware',) + required_middleware += ( + 'debug_toolbar.middleware.DebugToolbarMiddleware', + ) installed_middleware_set = set(django_settings.MIDDLEWARE_CLASSES) missing_middleware_set = set(required_middleware) - installed_middleware_set @@ -101,7 +104,8 @@ https://github.com/ASKBOT/askbot-devel/blob/master/askbot/setup_templates/settin ) #'debug_toolbar.middleware.DebugToolbarMiddleware', - remove_middleware_set = set(canceled_middleware) & installed_middleware_set + remove_middleware_set = set(canceled_middleware) \ + & installed_middleware_set if remove_middleware_set: error_message = """\n\nPlease remove the following middleware entries from the list of MIDDLEWARE_CLASSES in your settings.py - these are not used any more:\n\n""" @@ -111,6 +115,7 @@ the list of MIDDLEWARE_CLASSES in your settings.py - these are not used any more def test_i18n(): + """askbot requires use of USE_I18N setting""" if getattr(django_settings, 'USE_I18N', False) == False: raise ImproperlyConfigured( 'Please set USE_I18N = True in settings.py and ' @@ -119,26 +124,32 @@ def test_i18n(): ) def try_import(module_name, pypi_package_name): + """tries importing a module and advises to install + A corresponding Python package in the case import fails""" try: load_module(module_name) - except ImportError, e: - message = unicode(e) + ' run\npip install %s' % pypi_package_name + except ImportError, error: + message = unicode(error) + ' run\npip install %s' % pypi_package_name message += '\nTo install all the dependencies at once, type:' message += '\npip install -r askbot_requirements.txt\n' raise ImproperlyConfigured(message) def test_modules(): + """tests presence of required modules""" + try_import('akismet', 'akismet') try_import('recaptcha_works', 'django-recaptcha-works') def test_postgres(): - '''Validates postgres buggy driver 2.4.2''' + """Checks for the postgres buggy driver, version 2.4.2""" if hasattr(django_settings, 'DATABASE_ENGINE'): if django_settings.DATABASE_ENGINE in ('postgresql_psycopg2',): try: import psycopg2 version = psycopg2.__version__.split(' ')[0].split('.') if version == ['2', '4', '2']: - raise ImproperlyConfigured('Please install psycopg2 version 2.4.1,\n version 2.4.2 has a bug') + raise ImproperlyConfigured( + 'Please install psycopg2 version 2.4.1,\n version 2.4.2 has a bug' + ) elif version > ['2', '4', '2']: pass #don't know what to do else: @@ -181,5 +192,6 @@ def run(): try: badges.init_badges() transaction.commit() - except: + except Exception, error: + print error transaction.rollback() diff --git a/askbot/urls.py b/askbot/urls.py index c6d79492..54c50d96 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -10,8 +10,11 @@ from django.utils.translation import ugettext as _ from askbot import views from askbot.feed import RssLastestQuestionsFeed from askbot.sitemap import QuestionsSitemap +from askbot.skins.utils import update_media_revision admin.autodiscover() +update_media_revision()#needs to be run once, so put it here + feeds = { 'rss': RssLastestQuestionsFeed } @@ -205,6 +208,11 @@ urlpatterns = patterns('', name='user_profile' ), url( + r'^%s$' % _('users/update_has_custom_avatar/'), + views.users.update_has_custom_avatar, + name='user_update_has_custom_avatar' + ), + url( r'^%s$' % _('badges/'), views.meta.badges, name='badges' diff --git a/askbot/utils/console.py b/askbot/utils/console.py index 470856b5..496bbd65 100644 --- a/askbot/utils/console.py +++ b/askbot/utils/console.py @@ -72,5 +72,5 @@ def print_progress(elapsed, total, nowipe = False): operation, in percent, to the console and clear the output with a backspace character to have the number increment in-place""" - output = '%6.2f%%' % 100 * float(elapsed)/float(total) + output = '%6.2f%%' % (100 * float(elapsed)/float(total)) print_action(output, nowipe) diff --git a/askbot/utils/decorators.py b/askbot/utils/decorators.py index f2c86cd5..29e92645 100644 --- a/askbot/utils/decorators.py +++ b/askbot/utils/decorators.py @@ -7,14 +7,17 @@ import inspect import logging from django.conf import settings from django.core import exceptions as django_exceptions -from django.core import urlresolvers +from django.core.urlresolvers import reverse +from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponse, HttpResponseForbidden, Http404 from django.http import HttpResponseRedirect from django.utils import simplejson from django.utils.translation import ugettext as _ +from django.utils.encoding import smart_str from askbot import exceptions as askbot_exceptions from askbot.conf import settings as askbot_settings from askbot.utils import url_utils +from askbot import get_version def auto_now_timestamp(func): """decorator that will automatically set @@ -33,12 +36,12 @@ def auto_now_timestamp(func): def ajax_login_required(view_func): @functools.wraps(view_func) - def wrap(request,*args,**kwargs): + def wrap(request, *args, **kwargs): if request.user.is_authenticated(): - return view_func(request,*args,**kwargs) + return view_func(request, *args, **kwargs) else: json = simplejson.dumps({'login_required':True}) - return HttpResponseForbidden(json,mimetype='application/json') + return HttpResponseForbidden(json, mimetype='application/json') return wrap @@ -74,14 +77,13 @@ def post_only(view_func): return view_func(request, *args, **kwargs) return wrapper - def ajax_only(view_func): @functools.wraps(view_func) - def wrapper(request,*args,**kwargs): + def wrapper(request, *args, **kwargs): if not request.is_ajax(): raise Http404 try: - data = view_func(request,*args,**kwargs) + data = view_func(request, *args, **kwargs) except Exception, e: message = unicode(e) if message == '': @@ -99,7 +101,7 @@ def ajax_only(view_func): else: data['success'] = 1 json = simplejson.dumps(data) - return HttpResponse(json,mimetype='application/json') + return HttpResponse(json, mimetype='application/json') return wrapper def check_authorization_to_post(func_or_message): @@ -166,3 +168,53 @@ def profile(log_file): return _inner return _outer + +def check_spam(field): + '''Decorator to check if there is spam in the form''' + + def decorator(view_func): + @functools.wraps(view_func) + def wrapper(request, *args, **kwargs): + + if askbot_settings.USE_AKISMET and askbot_settings.AKISMET_API_KEY == "": + raise ImproperlyConfigured('You have not set AKISMET_API_KEY') + + if askbot_settings.USE_AKISMET and request.method == "POST": + comment = smart_str(request.POST[field]) + data = {'user_ip': request.META["REMOTE_ADDR"], + 'user_agent': request.environ['HTTP_USER_AGENT'], + 'comment_author': smart_str(request.user.username), + } + if request.user.is_authenticated(): + data.update({'comment_author_email': request.user.email}) + + from akismet import Akismet + api = Akismet( + askbot_settings.AKISMET_API_KEY, + smart_str(askbot_settings.APP_URL), + "Askbot/%s" % get_version() + ) + + if api.comment_check(comment, data, build_data=False): + logging.debug( + 'Spam detected in %s post at: %s', + request.user.username, + datetime.datetime.now() + ) + spam_message = _( + 'Spam was detected on your post, sorry ' + 'for if this is a mistake' + ) + if request.is_ajax(): + return HttpResponseForbidden( + spam_message, + mimetype="application/json" + ) + else: + request.user.message_set.create(message=spam_message) + return HttpResponseRedirect(reverse('index')) + + return view_func(request, *args, **kwargs) + return wrapper + + return decorator diff --git a/askbot/utils/functions.py b/askbot/utils/functions.py index a56ed897..d31d9027 100644 --- a/askbot/utils/functions.py +++ b/askbot/utils/functions.py @@ -2,12 +2,13 @@ import re import datetime from django.utils.translation import ugettext as _ from django.utils.translation import ungettext +from django.contrib.auth.models import User def get_from_dict_or_object(source, key): try: return source[key] except: - return getattr(source,key) + return getattr(source, key) def is_iterable(thing): @@ -53,7 +54,7 @@ def not_a_robot_request(request): return False -def diff_date(date, limen=2, use_on_prefix = False): +def diff_date(date, use_on_prefix = False): now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#??? diff = now - date days = diff.days @@ -74,9 +75,17 @@ def diff_date(date, limen=2, use_on_prefix = False): elif days == 1: return _('yesterday') elif minutes >= 60: - return ungettext('%(hr)d hour ago','%(hr)d hours ago',hours) % {'hr':hours} + return ungettext( + '%(hr)d hour ago', + '%(hr)d hours ago', + hours + ) % {'hr':hours} else: - return ungettext('%(min)d min ago','%(min)d mins ago',minutes) % {'min':minutes} + return ungettext( + '%(min)d min ago', + '%(min)d mins ago', + minutes + ) % {'min':minutes} #todo: this function may need to be removed to simplify the paginator functionality LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5 @@ -126,3 +135,10 @@ def setup_paginator(context): "pages_outside_trailing_range": pages_outside_trailing_range, "extend_url" : extend_url } + +def get_admin(): + '''Returns an admin users, usefull for raising flags''' + try: + return User.objects.filter(is_superuser=True)[0] + except: + raise Exception('there is no admin users') diff --git a/askbot/utils/hasher.py b/askbot/utils/hasher.py new file mode 100644 index 00000000..5a73213d --- /dev/null +++ b/askbot/utils/hasher.py @@ -0,0 +1,43 @@ +"""hasher function that will calculate sha1 hash +directory contents +""" +import hashlib, os +import logging + +def get_hash_of_dirs(dirs): + """Hasher function for a directory and its files""" + sha_hash = hashlib.sha1() + for directory in dirs: + if not os.path.exists (directory): + return -1 + + try: + for root, dirs, files in os.walk(directory): + for names in files: + filepath = os.path.join(root, names) + try: + file_obj = open(filepath, 'rb') + except Exception, error: + # You can't open the file for some reason + logging.critical( + 'cannot open file %s: %s', + filepath, + error + ) + file_obj.close() + continue + + while 1: + # Read file in as little chunks + buf = file_obj.read(4096) + if not buf : break + sha_hash.update(hashlib.sha1(buf).hexdigest()) + file_obj.close() + + except Exception: + import traceback + # Print the stack traceback + traceback.print_exc() + return -2 + + return sha_hash.hexdigest() diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 20d13adc..ab4ab87b 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -29,6 +29,7 @@ from askbot import exceptions from askbot.utils.diff import textDiff as htmldiff from askbot.forms import AdvancedSearchForm, AnswerForm, ShowQuestionForm from askbot import models +from askbot import schedules from askbot.models.badges import award_badges_signal from askbot import const from askbot.utils import functions @@ -304,6 +305,7 @@ def questions(request): 'tag_list_type' : tag_list_type, 'font_size' : font_size, 'tag_filter_strategy_choices': const.TAG_FILTER_STRATEGY_CHOICES, + 'update_avatar_data': schedules.should_update_avatar_data(request), } assert(request.is_ajax() == False) diff --git a/askbot/views/users.py b/askbot/views/users.py index acf1d38f..f1f29f71 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -17,9 +17,10 @@ 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.http import HttpResponse +from django.http import HttpResponse, HttpResponseForbidden from django.http import HttpResponseRedirect, Http404 from django.utils.translation import ugettext as _ +from django.utils import simplejson from django.views.decorators import csrf from askbot.utils.slug import slugify from askbot.utils.html import sanitize_html @@ -1007,3 +1008,13 @@ def user(request, id, slug=None, tab_name=None): 'user_follow_feature_on': ('followit' in django_settings.INSTALLED_APPS), } return user_view_func(request, profile_owner, context) + +def update_has_custom_avatar(request): + """updates current avatar type data for the user + """ + if request.is_ajax() and request.user.is_authenticated(): + if request.user.avatar_type in ('n', 'g'): + request.user.update_avatar_type() + request.session['avatar_data_updated_at'] = datetime.datetime.now() + return HttpResponse(simplejson.dumps({'status':'ok'}), mimetype='application/json') + return HttpResponseForbidden() diff --git a/askbot/views/writers.py b/askbot/views/writers.py index a2540a90..fcc98761 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -189,6 +189,7 @@ def import_data(request): #@login_required #actually you can post anonymously, but then must register @csrf.csrf_protect @decorators.check_authorization_to_post(_('Please log in to ask questions')) +@decorators.check_spam('text') def ask(request):#view used to ask a new question """a view to ask a new question gives space for q title, body, tags and checkbox for to post as wiki @@ -240,6 +241,18 @@ def ask(request):#view used to ask a new question ) question.save() return HttpResponseRedirect(url_utils.get_login_url()) + else: + form = forms.AskForm(request.POST) + if 'title' in request.GET: + #normally this title is inherited from search query + #but it is possible to ask with a parameter title in the url query + form.initial['title'] = request.GET['title'] + else: + #attempt to extract title from previous search query + search_state = request.session.get('search_state', None) + if search_state: + query = search_state.query + form.initial['title'] = query else: #this branch is for the initial load of ask form form = forms.AskForm() @@ -249,7 +262,7 @@ def ask(request):#view used to ask a new question form.initial['title'] = request.GET['title'] else: #attempt to extract title from previous search query - search_state = request.session.get('search_state',None) + search_state = request.session.get('search_state', None) if search_state: query = search_state.query form.initial['title'] = query @@ -319,6 +332,7 @@ def retag_question(request, id): @login_required @csrf.csrf_protect +@decorators.check_spam('text') def edit_question(request, id): """edit question view """ @@ -406,6 +420,7 @@ def edit_question(request, id): @login_required @csrf.csrf_protect +@decorators.check_spam('text') def edit_answer(request, id): answer = get_object_or_404(models.Answer, id=id) try: @@ -464,6 +479,7 @@ def edit_answer(request, id): #todo: rename this function to post_new_answer @decorators.check_authorization_to_post(_('Please log in to answer questions')) +@decorators.check_spam('text') def answer(request, id):#process a new answer """view that posts new answer @@ -548,6 +564,7 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po data = simplejson.dumps(json_comments) return HttpResponse(data, mimetype="application/json") +@decorators.check_spam('comment') def post_comments(request):#generic ajax handler to load comments to an object # only support get post comments by ajax now user = request.user @@ -587,6 +604,7 @@ def post_comments(request):#generic ajax handler to load comments to an object raise Http404 @decorators.ajax_only +@decorators.check_spam('text') def edit_comment(request): if request.user.is_authenticated(): comment_id = int(request.POST['comment_id']) @@ -609,7 +627,7 @@ def edit_comment(request): 'user_id': comment.user.id, 'is_deletable': is_deletable, 'is_editable': is_editable, - 'score': comment.score, + 'score': comment.score, 'voted': comment.is_upvoted_by(request.user), } else: diff --git a/askbot_requirements.txt b/askbot_requirements.txt index 70bc5874..a1d3b603 100644 --- a/askbot_requirements.txt +++ b/askbot_requirements.txt @@ -1,3 +1,4 @@ +akismet django>=1.1.2 Jinja2 Coffin>=0.3 @@ -7,6 +7,7 @@ import sys #you might want to install django-debug-toolbar as well install_requires = [ + 'akismet', 'django>=1.1.2', 'Jinja2', 'Coffin>=0.3', |