Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

766 lines (689 sloc) 30.349 kb
"""tests to be performed
in the beginning of models/
the purpose of this module is to validate deployment of askbot
question: why not run these from askbot/
the main function is run_startup_tests
import sys
import os
import re
import urllib
import askbot
import south
from django.db import transaction, connection
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from askbot.utils.loading import load_module
from askbot.utils.functions import enumerate_string_list
from askbot.utils.url_utils import urls_equal
from urlparse import urlparse
PREAMBLE = """\n
* *
* Askbot self-test *
* *
FOOTER = """\n
If necessary, type ^C (Ctrl-C) to stop the program.
class AskbotConfigError(ImproperlyConfigured):
"""Prints an error with a preamble and possibly a footer"""
def __init__(self, error_message):
msg = PREAMBLE + error_message
if sys.__stdin__.isatty():
#print footer only when askbot is run from the shell
msg += FOOTER
super(AskbotConfigError, self).__init__(msg)
def askbot_warning(line):
"""prints a warning with the nice header, but does not quit"""
print >> sys.stderr, PREAMBLE + '\n' + line
def print_errors(error_messages, header = None, footer = None):
"""if there is one or more error messages,
raise ``class:AskbotConfigError`` with the human readable
contents of the message
* ``header`` - text to show above messages
* ``footer`` - text to show below messages
if len(error_messages) == 0:
if len(error_messages) > 1:
error_messages = enumerate_string_list(error_messages)
message = ''
if header: message += header + '\n'
message += 'Please attend to the following:\n\n'
message += '\n\n'.join(error_messages)
if footer:
message += '\n\n' + footer
raise AskbotConfigError(message)
def format_as_text_tuple_entries(items):
"""prints out as entries or tuple containing strings
ready for copy-pasting into say django settings file"""
return " '%s'," % "',\n '".join(items)
# *validate emails in
def test_askbot_url():
"""Tests the ASKBOT_URL setting for the
well-formedness and raises the :class:`AskbotConfigError`
exception, if the setting is not good.
url = django_settings.ASKBOT_URL
if url != '':
if isinstance(url, str) or isinstance(url, unicode):
msg = 'setting ASKBOT_URL must be of string or unicode type'
raise AskbotConfigError(msg)
if url == '/':
msg = 'value "/" for ASKBOT_URL is invalid. '+ \
'Please, either make ASKBOT_URL an empty string ' + \
'or a non-empty path, ending with "/" but not ' + \
'starting with "/", for example: "forum/"'
raise AskbotConfigError(msg)
except AssertionError:
msg = 'if ASKBOT_URL setting is not empty, ' + \
'it must end with /'
raise AskbotConfigError(msg)
assert(not url.startswith('/'))
except AssertionError:
msg = 'if ASKBOT_URL setting is not empty, ' + \
'it must not start with /'
def test_middleware():
"""Checks that all required middleware classes are
installed in the django file. If that is not the
case - raises an AskbotConfigError exception.
required_middleware = [
if 'debug_toolbar' in django_settings.INSTALLED_APPS:
found_middleware = [x for x in django_settings.MIDDLEWARE_CLASSES
if x in required_middleware]
if found_middleware != required_middleware:
# either middleware is out of order or it's missing an item
missing_middleware_set = set(required_middleware) - set(found_middleware)
middleware_text = ''
if missing_middleware_set:
error_message = """\n\nPlease add the following middleware (listed after this message)
to the MIDDLEWARE_CLASSES variable in your site file.
The order the middleware records is important, please take a look at the example in\n\n"""
middleware_text = format_as_text_tuple_entries(missing_middleware_set)
# middleware is out of order
error_message = """\n\nPlease check the order of middleware closely.
The order the middleware records is important, please take a look at the example in
for the correct order.\n\n"""
raise AskbotConfigError(error_message + middleware_text)
#middleware that was used in the past an now removed
canceled_middleware = [
invalid_middleware = [x for x in canceled_middleware
if x in django_settings.MIDDLEWARE_CLASSES]
if invalid_middleware:
error_message = """\n\nPlease remove the following middleware entries from
the list of MIDDLEWARE_CLASSES in your - these are not used any more:\n\n"""
middleware_text = format_as_text_tuple_entries(invalid_middleware)
raise AskbotConfigError(error_message + middleware_text)
def try_import(module_name, pypi_package_name, short_message = False):
"""tries importing a module and advises to install
A corresponding Python package in the case import fails"""
except ImportError, error:
message = 'Error: ' + unicode(error)
message += '\n\nPlease run: >pip install %s' % pypi_package_name
if short_message == False:
message += '\n\nTo install all the dependencies at once, type:'
message += '\npip install -r askbot_requirements.txt'
message += '\n\nType ^C to quit.'
raise AskbotConfigError(message)
def test_modules():
"""tests presence of required modules"""
from askbot import REQUIREMENTS
for module_name, pip_path in REQUIREMENTS.items():
try_import(module_name, pip_path)
def test_postgres():
"""Checks for the postgres buggy driver, version 2.4.2"""
if 'postgresql_psycopg2' in askbot.get_database_engine_name():
import psycopg2
version = psycopg2.__version__.split(' ')[0].split('.')
if version == ['2', '4', '2']:
raise AskbotConfigError(
'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
pass #everythin is ok
def test_encoding():
"""prints warning if encoding error is not UTF-8"""
if hasattr(sys.stdout, 'encoding'):
if sys.stdout.encoding != 'UTF-8':
'Your output encoding is not UTF-8, there may be '
'issues with the software when anything is printed '
'to the terminal or log files'
def test_template_loader():
"""Sends a warning if you have an old style template
loader that used to send a warning"""
old_template_loader = 'askbot.skins.loaders.load_template_source'
if old_template_loader in django_settings.TEMPLATE_LOADERS:
raise AskbotConfigError(
"\nPlease change: \n"
"'askbot.skins.loaders.load_template_source', to\n"
"in the TEMPLATE_LOADERS of your file"
def test_celery():
"""Tests celery settings
todo: we are testing two things here
that correct name is used for the setting
and that a valid value is chosen
broker_backend = getattr(django_settings, 'BROKER_BACKEND', None)
broker_transport = getattr(django_settings, 'BROKER_TRANSPORT', None)
delay_time = getattr(django_settings, 'NOTIFICATION_DELAY_TIME', None)
delay_setting_info = 'The delay is in seconds - used to throttle ' + \
'instant notifications note that this delay will work only if ' + \
'celery daemon is running Please search about ' + \
'"celery daemon setup" for details'
if delay_time is None:
raise AskbotConfigError(
'\nPlease add to your\n' + \
if not isinstance(delay_time, int):
raise AskbotConfigError(
'\nNOTIFICATION_DELAY_TIME setting must have a numeric value\n' + \
if broker_backend is None:
if broker_transport is None:
raise AskbotConfigError(
"\nPlease add\n"
'BROKER_TRANSPORT = "djkombu.transport.DatabaseTransport"\n'
"or other valid value to your file"
#todo: check that broker transport setting is valid
if broker_backend != broker_transport:
raise AskbotConfigError(
"\nPlease rename setting BROKER_BACKEND to BROKER_TRANSPORT\n"
"in your file\n"
"If you have both in your - then\n"
"delete the BROKER_BACKEND setting and leave the BROKER_TRANSPORT"
if hasattr(django_settings, 'BROKER_BACKEND') and not hasattr(django_settings, 'BROKER_TRANSPORT'):
raise AskbotConfigError(
"\nPlease rename setting BROKER_BACKEND to BROKER_TRANSPORT\n"
"in your file"
def test_media_url():
"""makes sure that setting `MEDIA_URL`
has leading slash"""
media_url = django_settings.MEDIA_URL
#todo: add proper url validation to MEDIA_URL setting
if not (media_url.startswith('/') or media_url.startswith('http')):
raise AskbotConfigError(
"\nMEDIA_URL parameter must be a unique url on the site\n"
"and must start with a slash - e.g. /media/ or http(s)://"
class SettingsTester(object):
"""class to test contents of the file"""
def __init__(self, requirements = None):
"""loads the settings module and inits some variables
parameter `requirements` is a dictionary with keys
as setting names and values - another dictionary, which
has keys (optional, if noted and required otherwise)::
* required_value (optional)
* error_message
self.settings = load_module(os.environ['DJANGO_SETTINGS_MODULE'])
self.messages = list()
self.requirements = requirements
def test_setting(self, name,
value = None, message = None,
test_for_absence = False,
replace_hint = None
"""if setting does is not present or if the value != required_value,
adds an error message
if test_for_absence:
if hasattr(self.settings, name):
if replace_hint:
value = getattr(self.settings, name)
message += replace_hint % value
if not hasattr(self.settings, name):
elif value and getattr(self.settings, name) != value:
def run(self):
for setting_name in self.requirements:
if len(self.messages) != 0:
raise AskbotConfigError(
'\n\nTime to do some maintenance of your\n\n* ' +
'\n\n* '.join(self.messages)
def test_new_skins():
"""tests that there are no directories in the `askbot/skins`
because we've moved skin files a few levels up"""
askbot_root = askbot.get_install_directory()
for item in os.listdir(os.path.join(askbot_root, 'skins')):
item_path = os.path.join(askbot_root, 'skins', item)
if os.path.isdir(item_path):
raise AskbotConfigError(
('Time to move skin files from %s.\n'
'Now we have `askbot/templates` and `askbot/media`') % item_path
def test_staticfiles():
"""tests configuration of the staticfiles app"""
errors = list()
import django
django_version = django.VERSION
if django_version[0] == 1 and django_version[1] < 3:
staticfiles_app_name = 'staticfiles'
wrong_staticfiles_app_name = 'django.contrib.staticfiles'
try_import('staticfiles', 'django-staticfiles')
import staticfiles
if staticfiles.__version__[0] != 1:
raise AskbotConfigError(
'Please use the newest available version of '
'django-staticfiles app, type\n'
'pip install --upgrade django-staticfiles'
if not hasattr(django_settings, 'STATICFILES_STORAGE'):
raise AskbotConfigError(
'Configure STATICFILES_STORAGE setting as desired, '
'a reasonable default is\n'
staticfiles_app_name = 'django.contrib.staticfiles'
wrong_staticfiles_app_name = 'staticfiles'
if staticfiles_app_name not in django_settings.INSTALLED_APPS:
'Add to the INSTALLED_APPS section of your\n'
" '%s'," % staticfiles_app_name
if wrong_staticfiles_app_name in django_settings.INSTALLED_APPS:
'Remove from the INSTALLED_APPS section of your\n'
" '%s'," % wrong_staticfiles_app_name
static_url = django_settings.STATIC_URL or ''
if static_url is None or str(static_url).strip() == '':
'Add STATIC_URL setting to your file. '
'The setting must be a url at which static files '
'are accessible.'
url = urlparse(static_url).path
if not (url.startswith('/') and url.endswith('/')):
#a simple check for the url
'Path in the STATIC_URL must start and end with the /.'
if django_settings.ADMIN_MEDIA_PREFIX != static_url + 'admin/':
# django_settings.STATICFILES_DIRS can have strings or tuples
staticfiles_dirs = [d[1] if isinstance(d, tuple) else d
for d in django_settings.STATICFILES_DIRS]
default_skin_tuple = None
askbot_root = askbot.get_install_directory()
old_default_skin_dir = os.path.abspath(os.path.join(askbot_root, 'skins'))
for dir_entry in django_settings.STATICFILES_DIRS:
if isinstance(dir_entry, tuple):
if dir_entry[0] == 'default/media':
default_skin_tuple = dir_entry
elif isinstance(dir_entry, str):
if os.path.abspath(dir_entry) == old_default_skin_dir:
'Remove from STATICFILES_DIRS in your file:\n' + dir_entry
askbot_root = os.path.dirname(askbot.__file__)
default_skin_media_dir = os.path.abspath(os.path.join(askbot_root, 'media'))
if default_skin_tuple:
media_dir = default_skin_tuple[1]
if default_skin_media_dir != os.path.abspath(media_dir):
'Add to STATICFILES_DIRS the following entry: '
"('default/media', os.path.join(ASKBOT_ROOT, 'media')),"
extra_skins_dir = getattr(django_settings, 'ASKBOT_EXTRA_SKINS_DIR', None)
if extra_skins_dir is not None:
if not os.path.isdir(extra_skins_dir):
'Directory specified with settning ASKBOT_EXTRA_SKINS_DIR '
'must exist and contain your custom skins for askbot.'
if extra_skins_dir not in staticfiles_dirs:
'your file.\n'
'NOTE: it might be necessary to move the line with '
if django_settings.STATICFILES_STORAGE == \
if os.path.dirname(django_settings.STATIC_ROOT) == '':
#static root is needed only for local storoge of
#the static files
raise AskbotConfigError(
'Specify the static files directory '
'with setting STATIC_ROOT'
if errors:
'Run command (after fixing the above errors)\n'
' python collectstatic\n'
if django_settings.STATICFILES_STORAGE == \
if not os.path.isdir(django_settings.STATIC_ROOT):
'Please run command\n\n'
' python collectstatic'
def test_csrf_cookie_domain():
"""makes sure that csrf cookie domain setting is acceptable"""
#todo: maybe use the same steps to clean domain name
csrf_cookie_domain = django_settings.CSRF_COOKIE_DOMAIN
if csrf_cookie_domain is None or str(csrf_cookie_domain.strip()) == '':
raise AskbotConfigError(
'Please add settings CSRF_COOKIE_DOMAN and CSRF_COOKIE_NAME '
'settings - both are required. '
'CSRF_COOKIE_DOMAIN must match the domain name of yor site, '
'without the http(s):// prefix and without the port number.\n'
'Examples: \n'
if csrf_cookie_domain == 'localhost':
raise AskbotConfigError(
'Please do not use value "localhost" for the setting '
'instead use, a real IP '
'address or domain name.'
'\nThe value must match the network location you type in the '
'web browser to reach your site.'
if re.match(r'https?://', csrf_cookie_domain):
raise AskbotConfigError(
'please remove http(s):// prefix in the CSRF_COOKIE_DOMAIN '
if ':' in csrf_cookie_domain:
raise AskbotConfigError(
'Please do not use port number in the CSRF_COOKIE_DOMAIN '
def test_settings_for_test_runner():
"""makes sure that debug toolbar is disabled when running tests"""
errors = list()
if 'debug_toolbar' in django_settings.INSTALLED_APPS:
'When testing - remove debug_toolbar from INSTALLED_APPS'
if 'debug_toolbar.middleware.DebugToolbarMiddleware' in \
'When testing - remove debug_toolbar.middleware.DebugToolbarMiddleware '
def test_avatar():
"""if "avatar" is in the installed apps,
checks that the module is actually installed"""
if 'avatar' in django_settings.INSTALLED_APPS:
try_import('Image', 'PIL', short_message = True)
'-e git+git://',
short_message = True
def test_haystack():
if 'haystack' in django_settings.INSTALLED_APPS:
try_import('haystack', 'django-haystack', short_message = True)
if getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False):
errors = list()
if not hasattr(django_settings, 'HAYSTACK_SEARCH_ENGINE'):
message = "Please HAYSTACK_SEARCH_ENGINE to an appropriate value, value 'simple' can be used for basic testing"
if not hasattr(django_settings, 'HAYSTACK_SITECONF'):
message = 'Please add HAYSTACK_SITECONF = ""'
footer = 'Please refer to haystack documentation at'
print_errors(errors, footer=footer)
def test_custom_user_profile_tab():
tab_settings = getattr(django_settings, setting_name, None)
if tab_settings:
if not isinstance(tab_settings, dict):
print "Setting %s must be a dictionary!!!" % setting_name
name = tab_settings.get('NAME', None)
slug = tab_settings.get('SLUG', None)
func_name = tab_settings.get('CONTENT_GENERATOR', None)
errors = list()
if (name is None) or (not(isinstance(name, basestring))):
errors.append("%s['NAME'] must be a string" % setting_name)
if (slug is None) or (not(isinstance(slug, str))):
errors.append("%s['SLUG'] must be an ASCII string" % setting_name)
if urllib.quote_plus(slug) != slug:
"%s['SLUG'] must be url safe, make it simple" % setting_name
func = load_module(func_name)
except ImportError:
errors.append("%s['CONTENT_GENERATOR'] must be a dotted path to a function" % setting_name)
header = 'Custom user profile tab is configured incorrectly in your file'
footer = 'Please carefully read about adding a custom user profile tab.'
print_errors(errors, header = header, footer = footer)
def get_tinymce_sample_config():
"""returns the sample configuration for TinyMCE
as string"""
askbot_root = askbot.get_install_directory()
file_path = os.path.join(
askbot_root, 'setup_templates', ''
config_file = open(file_path, 'r')
sample_config =
return sample_config
def test_tinymce():
"""tests the tinymce editor setup"""
errors = list()
if 'tinymce' not in django_settings.INSTALLED_APPS:
errors.append("add 'tinymce', to the INSTALLED_APPS")
required_attrs = (
missing_attrs = list()
for attr in required_attrs:
if not hasattr(django_settings, attr):
if missing_attrs:
errors.append('add missing settings: %s' % ', '.join(missing_attrs))
#check compressor setting
compressor_on = getattr(django_settings, 'TINYMCE_COMPRESSOR', False)
if compressor_on is False:
errors.append('add line: TINYMCE_COMPRESSOR = True')
#todo: add pointer to instructions on how to debug tinymce:
#1) add ('tiny_mce', os.path.join(ASKBOT_ROOT, 'media/js/tinymce')),
#2) add this to the main urlconf:
# (
# r'^m/(?P<path>.*)$',
# 'django.views.static.serve',
# {'document_root': static_root}
# ),
#3) set `TINYMCE_COMPRESSOR = False`
#4) set DEBUG = False
#then - tinymce compressing will be disabled and it will
#be possible to debug custom tinymce plugins that are used with askbot
config = getattr(django_settings, 'TINYMCE_DEFAULT_CONFIG', None)
if config:
if 'convert_urls' in config:
if config['convert_urls'] is not False:
message = "set 'convert_urls':False in TINYMCE_DEFAULT_CONFIG"
message = "add to TINYMCE_DEFAULT_CONFIG\n'convert_urls': False,"
#check js root setting - before version 0.7.44 we used to have
#"common" skin and after we combined it into the default
js_root = getattr(django_settings, 'TINYMCE_JS_ROOT', '')
old_relative_js_path = 'common/media/js/tinymce/'
relative_js_path = 'default/media/js/tinymce/'
expected_js_root = os.path.join(django_settings.STATIC_ROOT, relative_js_path)
old_expected_js_root = os.path.join(django_settings.STATIC_ROOT, old_relative_js_path)
if os.path.normpath(js_root) != os.path.normpath(expected_js_root):
error_tpl = "add line: TINYMCE_JS_ROOT = os.path.join(STATIC_ROOT, '%s')"
if os.path.normpath(js_root) == os.path.normpath(old_expected_js_root):
error_tpl += '\nNote: we have moved files from "common" into "default"'
errors.append(error_tpl % relative_js_path)
#check url setting
url = getattr(django_settings, 'TINYMCE_URL', '')
expected_url = django_settings.STATIC_URL + relative_js_path
old_expected_url = django_settings.STATIC_URL + old_relative_js_path
if urls_equal(url, expected_url) is False:
error_tpl = "add line: TINYMCE_URL = STATIC_URL + '%s'"
if urls_equal(url, old_expected_url):
error_tpl += '\nNote: we have moved files from "common" into "default"'
errors.append(error_tpl % relative_js_path)
if errors:
header = 'Please add the tynymce editor configuration ' + \
'to your file.'
footer = 'You might want to use this sample configuration ' + \
'as template:\n\n' + get_tinymce_sample_config()
print_errors(errors, header=header, footer=footer)
def test_longerusername():
"""tests proper installation of the "longerusername" app
errors = list()
if 'longerusername' not in django_settings.INSTALLED_APPS:
"add 'longerusername', as the first item in the INSTALLED_APPS"
index = django_settings.INSTALLED_APPS.index('longerusername')
if index != 0:
message = "move 'longerusername', to the beginning of INSTALLED_APPS"
raise AskbotConfigError(message)
if errors:
errors.append('run "python migrate longerusername"')
def run_startup_tests():
"""function that runs
all startup tests, mainly checking settings config so far
#todo: refactor this when another test arrives
settings_tester = SettingsTester({
'value': True,
'message': "add line CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True"
'USE_I18N': {
'value': True,
'message': 'Please set USE_I18N = True and\n'
'set the LANGUAGE_CODE parameter correctly'
'message': 'add setting LOGIN_REDIRECT_URL - an url\n'
'where you want to send users after they log in\n'
'a reasonable default is\n'
'test_for_absence': True,
'message': 'Please replace setting ASKBOT_FILE_UPLOAD_DIR ',
'replace_hint': "with MEDIA_ROOT = '%s'"
'test_for_absence': True,
'message': 'Please replace setting ASKBOT_UPLOADED_FILES_URL ',
'replace_hint': "with MEDIA_URL = '/%s'"
'value': True,
'message': 'Please add: RECAPTCHA_USE_SSL = True'
'value': '',
'message': 'Please add: HAYSTACK_SITECONF = ""'
if ' test' in ' '.join(sys.argv):
def run():
"""runs all the startup procedures"""
except AskbotConfigError, error:
print error
from askbot.models import badges
except Exception, error:
print error
Jump to Line
Something went wrong with that request. Please try again.