summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/__init__.py13
-rw-r--r--askbot/deployment/assertions.py26
-rw-r--r--askbot/deployment/package_utils.py28
-rw-r--r--askbot/deps/django_authopenid/views.py57
-rw-r--r--askbot/exceptions.py4
-rw-r--r--askbot/middleware/pagesize.py4
-rw-r--r--askbot/models/__init__.py4
-rw-r--r--askbot/patches/__init__.py28
-rw-r--r--askbot/patches/coffin_patches.py34
-rw-r--r--askbot/patches/django_patches.py327
-rw-r--r--askbot/skins/default/templates/404.html2
-rw-r--r--askbot/skins/default/templates/500.html2
-rw-r--r--askbot/skins/loaders.py24
-rw-r--r--askbot/startup_procedures.py2
-rw-r--r--askbot/templatetags/extra_tags.py15
-rw-r--r--askbot/tests/page_load_tests.py33
-rw-r--r--askbot/version.py0
-rw-r--r--askbot/views/readers.py1
-rw-r--r--setup.py10
19 files changed, 543 insertions, 71 deletions
diff --git a/askbot/__init__.py b/askbot/__init__.py
index fa30780c..d159c315 100644
--- a/askbot/__init__.py
+++ b/askbot/__init__.py
@@ -6,7 +6,17 @@ basic actions on behalf of the forum application
"""
import os
import smtplib
+import sys
import logging
+from askbot import patches
+from askbot.deployment.assertions import assert_package_compatibility
+
+VERSION = (0, 6, 75)
+
+#necessary for interoperability of django and coffin
+assert_package_compatibility()
+patches.patch_django()
+patches.patch_coffin()#must go after django
def get_install_directory():
"""returns path to directory
@@ -15,8 +25,9 @@ def get_install_directory():
"""
return os.path.dirname(__file__)
+
def get_version():
"""returns version of the askbot app
this version is meaningful for pypi only
"""
- return '0.6.74'
+ return '.'.join([str(subversion) for subversion in VERSION])
diff --git a/askbot/deployment/assertions.py b/askbot/deployment/assertions.py
new file mode 100644
index 00000000..0db62b84
--- /dev/null
+++ b/askbot/deployment/assertions.py
@@ -0,0 +1,26 @@
+"""assertions regarding deployment of askbot
+todo: move here stuff from startup_procedures.py
+
+the reason - some assertions need to be run in askbot/__init__
+as opposed to startup_procedures.py - which are executed in the
+beginning of the models module
+"""
+from askbot.deployment import package_utils
+from askbot.exceptions import DeploymentError
+
+def assert_package_compatibility():
+ """raises an exception if any known incompatibilities
+ are found
+ """
+ (django_major, django_minor, django_micro) = package_utils.get_django_version()
+ if django_major < 1:
+ raise DeploymentError('Django version < 1.0 is not supported by askbot')
+
+ coffin_version = package_utils.get_coffin_version()
+ if coffin_version == (0, 3, 0) and django_major == 1 and django_minor > 1:
+ raise DeploymentError(
+ 'Coffin package version 0.3 is not compatible '
+ 'with the current version of Django, please upgrade '
+ 'coffin to at least 0.3.3'
+ )
+
diff --git a/askbot/deployment/package_utils.py b/askbot/deployment/package_utils.py
new file mode 100644
index 00000000..c2a9b65c
--- /dev/null
+++ b/askbot/deployment/package_utils.py
@@ -0,0 +1,28 @@
+"""utilities that determine versions of packages
+that are part of askbot
+
+versions of all packages are normalized to three-tuples
+of integers (missing zeroes added)
+"""
+import coffin
+import django
+
+def get_coffin_version():
+ """Returns version of Coffin package
+ as a three integer value tuple
+ """
+ version = coffin.__version__
+ if len(version) == 2:
+ micro_version = 0
+ elif len(version) == 3:
+ micro_version = version[2]
+ else:
+ raise ValueError('unsupported version of coffin %s' % '.'.join(version))
+ major_version = version[0]
+ minor_version = version[1]
+ return (major_version, minor_version, micro_version)
+
+def get_django_version():
+ """returns three-tuple for the version
+ of django"""
+ return django.VERSION[:3]
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index 6aa1dab2..75d6986e 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -45,7 +45,7 @@ from django.utils.html import escape
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.core.mail import send_mail
-from askbot.skins.loaders import ENV
+from askbot.skins.loaders import render_into_skin
from askbot.deps.openid.consumer.consumer import Consumer, \
SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
@@ -554,9 +554,7 @@ def show_signin_view(
data['major_login_providers'] = major_login_providers.values()
data['minor_login_providers'] = minor_login_providers.values()
- template = ENV.get_template('authopenid/signin.html')
- context = RequestContext(request, data)
- return HttpResponse(template.render(context))
+ return render_into_skin('authopenid/signin.html', data, request)
@login_required
def delete_login_method(request):
@@ -813,7 +811,6 @@ def register(request, login_provider_name=None, user_identifier=None):
provider_logo = providers[login_provider_name]
logging.debug('printing authopenid/complete.html output')
- template = ENV.get_template('authopenid/complete.html')
data = {
'openid_register_form': register_form,
'email_feeds_form': email_feeds_form,
@@ -823,8 +820,7 @@ def register(request, login_provider_name=None, user_identifier=None):
'login_type':'openid',
'gravatar_faq_url':reverse('faq') + '#gravatar',
}
- context = RequestContext(request, data)
- return HttpResponse(template.render(context))
+ return render_into_skin('authopenid/complete.html', data, request)
def signin_failure(request, message):
"""
@@ -895,7 +891,7 @@ def signup_with_password(request):
# send email
#subject = _("Welcome email subject line")
- #message_template = ENV.get_template(
+ #message_template = get_emplate(
# 'authopenid/confirm_email.txt'
#)
#message_context = Context({
@@ -933,9 +929,11 @@ def signup_with_password(request):
'minor_login_providers': minor_login_providers.values(),
'login_form': login_form
}
- template = ENV.get_template('authopenid/signup_with_password.html')
- context = RequestContext(request, context_data)
- return HttpResponse(template.render(context))
+ return render_into_skin(
+ 'authopenid/signup_with_password.html',
+ context_data,
+ request
+ )
#what if request is not posted?
@login_required
@@ -1003,15 +1001,14 @@ def _send_email_key(user):
to user's email address
"""
subject = _("Recover your %(site)s account") % {'site': askbot_settings.APP_SHORT_NAME}
- message_template = ENV.get_template('authopenid/email_validation.txt')
- import settings
- message_context = Context({
- 'validation_link': askbot_settings.APP_URL + reverse(
- 'user_account_recover',
- kwargs={'key':user.email_key}
- )
- })
- message = message_template.render(message_context)
+ data = {
+ 'validation_link': askbot_settings.APP_URL + \
+ reverse(
+ 'user_account_recover',
+ kwargs={'key':user.email_key}
+ )
+ }
+ message = render_into_skin('authopenid/email_validation.txt', data)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
def send_new_email_key(user,nomessage=False):
@@ -1037,14 +1034,16 @@ def send_email_key(request):
"""
if askbot_settings.EMAIL_VALIDATION == True:
if request.user.email_isvalid:
- template = ENV.get_template('authopenid/changeemail.html')
data = {
'email': request.user.email,
'action_type': 'key_not_sent',
'change_link': reverse('user_changeemail')
}
- context = RequestContext(request, data)
- return HttpResponse(template.render(context))
+ return render_into_skin(
+ 'authopenid/changeemail.html',
+ data,
+ request
+ )
else:
send_new_email_key(request.user)
return validation_email_sent(request)
@@ -1107,14 +1106,12 @@ def validation_email_sent(request):
set to True bolean value, basically dead now"""
assert(askbot_settings.EMAIL_VALIDATION == True)
logging.debug('')
- template = ENV.get_template('authopenid/changeemail.html')
data = {
'email': request.user.email,
'change_email_url': reverse('user_changeemail'),
'action_type': 'validate'
}
- context = RequestContext(request, data)
- return HttpResponse(template.render(context))
+ return render_into_skin('authopenid/changeemail.html', data, request)
def verifyemail(request,id=None,key=None):
"""
@@ -1129,10 +1126,12 @@ def verifyemail(request,id=None,key=None):
user.email_isvalid = True
clear_email_validation_message(user)
user.save()
- template = ENV.get_template('authopenid/changeemail.html')
data = {'action_type': 'validation_complete'}
- context = RequestContext(request, data)
- return HttpResponse(template.render(context))
+ return render_into_skin(
+ 'authopenid/changeemail.html',
+ data,
+ request
+ )
else:
logging.error('hmm, no user found for email validation message - foul play?')
raise Http404
diff --git a/askbot/exceptions.py b/askbot/exceptions.py
index a1d76a67..d2d5ddf0 100644
--- a/askbot/exceptions.py
+++ b/askbot/exceptions.py
@@ -1,6 +1,10 @@
from django.core import exceptions
from django.utils.translation import ugettext as _
+class DeploymentError(exceptions.ImproperlyConfigured):
+ """raised when there is some error with deployment"""
+ pass
+
class LoginRequired(exceptions.PermissionDenied):
"""raised when an operation required a logged
in user"""
diff --git a/askbot/middleware/pagesize.py b/askbot/middleware/pagesize.py
index 8808fb7a..f10607ad 100644
--- a/askbot/middleware/pagesize.py
+++ b/askbot/middleware/pagesize.py
@@ -52,6 +52,6 @@ class QuestionsPageSizeMiddleware(object):
#500.html needs RequestContext, while handler500 only receives Context
#need to log some more details about the request
logging.critical(utils.http.get_request_info(request))
- from askbot.skins.loaders import ENV
- template = ENV.get_template('500.jinja.html')
+ from askbot.skins.loaders import get_template
+ template = get_template('500.jinja.html', request)
return HttpResponse(template.render(RequestContext(request)))
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index e3194151..78a98192 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -2010,8 +2010,8 @@ def send_instant_notifications_about_activity_in_post(
if update_activity.activity_type not in acceptable_types:
return
- from askbot.skins.loaders import ENV
- template = ENV.get_template('instant_notification.html')
+ from askbot.skins.loaders import get_template
+ template = get_template('instant_notification.html')
update_type_map = const.RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES
update_type = update_type_map[update_activity.activity_type]
diff --git a/askbot/patches/__init__.py b/askbot/patches/__init__.py
new file mode 100644
index 00000000..3c5e0d28
--- /dev/null
+++ b/askbot/patches/__init__.py
@@ -0,0 +1,28 @@
+"""module for monkey patching that is
+necessary for interoperability of different
+versions of various components used in askbot
+"""
+import django
+from askbot.patches import django_patches
+from askbot.deployment import package_utils
+
+def patch_django():
+ """earlier versions of Django do not have
+ csrf token and function called import_library
+ (the latter is needed by coffin)
+ """
+ (major, minor, micro) = package_utils.get_django_version()
+ if major == 1 and minor < 2:
+ django_patches.add_import_library_function()
+ django_patches.add_csrf_protection()
+
+def patch_coffin():
+ """coffin before version 0.3.4
+ does not have csrf_token template tag.
+ This patch must be applied after the django patches
+ """
+ from askbot.patches import coffin_patches
+
+ (major, minor, micro) = package_utils.get_coffin_version()
+ if major == 0 and minor == 3 and micro < 4:
+ coffin_patches.add_csrf_token_tag()
diff --git a/askbot/patches/coffin_patches.py b/askbot/patches/coffin_patches.py
new file mode 100644
index 00000000..92e3bc09
--- /dev/null
+++ b/askbot/patches/coffin_patches.py
@@ -0,0 +1,34 @@
+"""patches for the coffin module"""
+from jinja2 import nodes
+from jinja2 import Markup
+from jinja2.ext import Extension
+
+class CsrfTokenExtension(Extension):
+ """Jinja2-version of the ``csrf_token`` tag.
+
+ Adapted from a snippet by Jason Green:
+ http://www.djangosnippets.org/snippets/1847/
+
+ This tag is a bit stricter than the Django tag in that it doesn't
+ simply ignore any invalid arguments passed in.
+ """
+
+ tags = set(['csrf_token'])
+
+ def parse(self, parser):
+ lineno = parser.stream.next().lineno
+ return nodes.Output([
+ self.call_method('_render', [nodes.Name('csrf_token', 'load')])
+ ]).set_lineno(lineno)
+
+ def _render(self, csrf_token):
+ from django.template.defaulttags import CsrfTokenNode
+ return Markup(CsrfTokenNode().render({'csrf_token': csrf_token}))
+
+def add_csrf_token_tag():
+ """adds csrf token tag to the default library"""
+ import coffin.template.defaulttags
+ coffin.template.defaulttags.CsrfTokenExtension = CsrfTokenExtension
+ csrf_token = CsrfTokenExtension
+ coffin.template.defaulttags.csrf_token = csrf_token
+ coffin.template.defaulttags.register.tag(csrf_token)
diff --git a/askbot/patches/django_patches.py b/askbot/patches/django_patches.py
new file mode 100644
index 00000000..baab64af
--- /dev/null
+++ b/askbot/patches/django_patches.py
@@ -0,0 +1,327 @@
+"""a module for patching django"""
+import imp
+import os
+import sys
+from django.utils.safestring import mark_safe
+from django.utils.functional import lazy
+from django.template import Node
+
+def module_has_submodule(package, module_name):
+ """See if 'module' is in 'package'."""
+ name = ".".join([package.__name__, module_name])
+ if name in sys.modules:
+ return True
+ for finder in sys.meta_path:
+ if finder.find_module(name):
+ return True
+ for entry in package.__path__: # No __path__, then not a package.
+ try:
+ # Try the cached finder.
+ finder = sys.path_importer_cache[entry]
+ if finder is None:
+ # Implicit import machinery should be used.
+ try:
+ file_, _, _ = imp.find_module(module_name, [entry])
+ if file_:
+ file_.close()
+ return True
+ except ImportError:
+ continue
+ # Else see if the finder knows of a loader.
+ elif finder.find_module(name):
+ return True
+ else:
+ continue
+ except KeyError:
+ # No cached finder, so try and make one.
+ for hook in sys.path_hooks:
+ try:
+ finder = hook(entry)
+ # XXX Could cache in sys.path_importer_cache
+ if finder.find_module(name):
+ return True
+ else:
+ # Once a finder is found, stop the search.
+ break
+ except ImportError:
+ # Continue the search for a finder.
+ continue
+ else:
+ # No finder found.
+ # Try the implicit import machinery if searching a directory.
+ if os.path.isdir(entry):
+ try:
+ file_, _, _ = imp.find_module(module_name, [entry])
+ if file_:
+ file_.close()
+ return True
+ except ImportError:
+ pass
+ # XXX Could insert None or NullImporter
+ else:
+ # Exhausted the search, so the module cannot be found.
+ return False
+
+class CsrfTokenNode(Node):
+ def render(self, context):
+ csrf_token = context.get('csrf_token', None)
+ if csrf_token:
+ if csrf_token == 'NOTPROVIDED':
+ return mark_safe(u"")
+ else:
+ return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % csrf_token)
+ else:
+ # It's very probable that the token is missing because of
+ # misconfiguration, so we raise a warning
+ from django.conf import settings
+ if settings.DEBUG:
+ import warnings
+ warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.")
+ return u''
+
+def get_token(request):
+ """
+ Returns the the CSRF token required for a POST form.
+ A side effect of calling this function is to make the the csrf_protect
+ decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
+ header to the outgoing response. For this reason, you may need to use this
+ function lazily, as is done by the csrf context processor.
+ """
+ request.META["CSRF_COOKIE_USED"] = True
+ return request.META.get("CSRF_COOKIE", None)
+
+def csrf(request):
+ """
+ Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if
+ it has not been provided by either a view decorator or the middleware
+ """
+ def _get_val():
+ token = get_token(request)
+ if token is None:
+ # In order to be able to provide debugging info in the
+ # case of misconfiguration, we use a sentinel value
+ # instead of returning an empty dict.
+ return 'NOTPROVIDED'
+ else:
+ return token
+ _get_val = lazy(_get_val, str)
+ return {'csrf_token': _get_val() }
+
+"""
+Cross Site Request Forgery Middleware.
+This module provides a middleware that implements protection
+against request forgeries from other sites.
+"""
+import itertools
+import re
+import random
+from django.conf import settings
+from django.core.urlresolvers import get_callable
+from django.utils.hashcompat import md5_constructor
+from django.utils.safestring import mark_safe
+_POST_FORM_RE = \
+ re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
+_HTML_TYPES = ('text/html', 'application/xhtml+xml')
+# Use the system (hardware-based) random number generator if it exists.
+if hasattr(random, 'SystemRandom'):
+ randrange = random.SystemRandom().randrange
+else:
+ randrange = random.randrange
+_MAX_CSRF_KEY = 18446744073709551616L # 2 << 63
+def _get_failure_view():
+ """
+ Returns the view to be used for CSRF rejections
+ """
+ return get_callable(settings.CSRF_FAILURE_VIEW)
+
+def _get_new_csrf_key():
+ return md5_constructor("%s%s"
+ % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
+
+def _make_legacy_session_token(session_id):
+ return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
+
+class CsrfViewMiddleware(object):
+ """
+ Middleware that requires a present and correct csrfmiddlewaretoken
+ for POST requests that have a CSRF cookie, and sets an outgoing
+ CSRF cookie.
+ This middleware should be used in conjunction with the csrf_token template
+ tag.
+ """
+ def process_view(self, request, callback, callback_args, callback_kwargs):
+ if getattr(callback, 'csrf_exempt', False):
+ return None
+ if getattr(request, 'csrf_processing_done', False):
+ return None
+ reject = lambda s: _get_failure_view()(request, reason=s)
+ def accept():
+ # Avoid checking the request twice by adding a custom attribute to
+ # request. This will be relevant when both decorator and middleware
+ # are used.
+ request.csrf_processing_done = True
+ return None
+ # If the user doesn't have a CSRF cookie, generate one and store it in the
+ # request, so it's available to the view. We'll store it in a cookie when
+ # we reach the response.
+ try:
+ request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME]
+ cookie_is_new = False
+ except KeyError:
+ # No cookie, so create one. This will be sent with the next
+ # response.
+ request.META["CSRF_COOKIE"] = _get_new_csrf_key()
+ # Set a flag to allow us to fall back and allow the session id in
+ # place of a CSRF cookie for this request only.
+ cookie_is_new = True
+ if request.method == 'POST':
+ if getattr(request, '_dont_enforce_csrf_checks', False):
+ # Mechanism to turn off CSRF checks for test suite. It comes after
+ # the creation of CSRF cookies, so that everything else continues to
+ # work exactly the same (e.g. cookies are sent etc), but before the
+ # any branches that call reject()
+ return accept()
+ if request.is_ajax():
+ # .is_ajax() is based on the presence of X-Requested-With. In
+ # the context of a browser, this can only be sent if using
+ # XmlHttpRequest. Browsers implement careful policies for
+ # XmlHttpRequest:
+ #
+ # * Normally, only same-domain requests are allowed.
+ #
+ # * Some browsers (e.g. Firefox 3.5 and later) relax this
+ # carefully:
+ #
+ # * if it is a 'simple' GET or POST request (which can
+ # include no custom headers), it is allowed to be cross
+ # domain. These requests will not be recognized as AJAX.
+ #
+ # * if a 'preflight' check with the server confirms that the
+ # server is expecting and allows the request, cross domain
+ # requests even with custom headers are allowed. These
+ # requests will be recognized as AJAX, but can only get
+ # through when the developer has specifically opted in to
+ # allowing the cross-domain POST request.
+ #
+ # So in all cases, it is safe to allow these requests through.
+ return accept()
+ if request.is_secure():
+ # Strict referer checking for HTTPS
+ referer = request.META.get('HTTP_REFERER')
+ if referer is None:
+ return reject("Referer checking failed - no Referer.")
+ # The following check ensures that the referer is HTTPS,
+ # the domains match and the ports match. This might be too strict.
+ good_referer = 'https://%s/' % request.get_host()
+ if not referer.startswith(good_referer):
+ return reject("Referer checking failed - %s does not match %s." %
+ (referer, good_referer))
+ # If the user didn't already have a CSRF cookie, then fall back to
+ # the Django 1.1 method (hash of session ID), so a request is not
+ # rejected if the form was sent to the user before upgrading to the
+ # Django 1.2 method (session independent nonce)
+ if cookie_is_new:
+ try:
+ session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
+ csrf_token = _make_legacy_session_token(session_id)
+ except KeyError:
+ # No CSRF cookie and no session cookie. For POST requests,
+ # we insist on a CSRF cookie, and in this way we can avoid
+ # all CSRF attacks, including login CSRF.
+ return reject("No CSRF or session cookie.")
+ else:
+ csrf_token = request.META["CSRF_COOKIE"]
+ # check incoming token
+ request_csrf_token = request.POST.get('csrfmiddlewaretoken', None)
+ if request_csrf_token != csrf_token:
+ if cookie_is_new:
+ # probably a problem setting the CSRF cookie
+ return reject("CSRF cookie not set.")
+ else:
+ return reject("CSRF token missing or incorrect.")
+ return accept()
+ def process_response(self, request, response):
+ if getattr(response, 'csrf_processing_done', False):
+ return response
+ # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
+ # never called, probaby because a request middleware returned a response
+ # (for example, contrib.auth redirecting to a login page).
+ if request.META.get("CSRF_COOKIE") is None:
+ return response
+ if not request.META.get("CSRF_COOKIE_USED", False):
+ return response
+ # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
+ response.set_cookie(settings.CSRF_COOKIE_NAME,
+ request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
+ domain=settings.CSRF_COOKIE_DOMAIN)
+ # Content varies with the CSRF cookie, so set the Vary header.
+ from django.utils.cache import patch_vary_headers
+ patch_vary_headers(response, ('Cookie',))
+ response.csrf_processing_done = True
+ return response
+
+from django.utils.decorators import decorator_from_middleware
+from functools import wraps
+
+csrf_protect = decorator_from_middleware(CsrfViewMiddleware)
+csrf_protect.__name__ = "csrf_protect"
+csrf_protect.__doc__ = """
+This decorator adds CSRF protection in exactly the same way as
+CsrfViewMiddleware, but it can be used on a per view basis. Using both, or
+using the decorator multiple times, is harmless and efficient.
+"""
+
+def add_import_library_function():
+
+ #this definition is copy/pasted from django 1.2 source code
+ #it is necessary to make Coffin library happy
+ from django.utils.importlib import import_module
+ class InvalidTemplateLibrary(Exception):
+ pass
+
+ def import_library(taglib_module):
+ """Load a template tag library module.
+ Verifies that the library contains a 'register' attribute, and
+ returns that attribute as the representation of the library
+ """
+ app_path, taglib = taglib_module.rsplit('.',1)
+ app_module = import_module(app_path)
+ try:
+ mod = import_module(taglib_module)
+ except ImportError, e:
+ # If the ImportError is because the taglib submodule does not exist, that's not
+ # an error that should be raised. If the submodule exists and raised an ImportError
+ # on the attempt to load it, that we want to raise.
+ if not module_has_submodule(app_module, taglib):
+ return None
+ else:
+ raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e))
+ try:
+ return mod.register
+ except AttributeError:
+ raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module)
+
+ import django.template
+ django.template.import_library = import_library
+
+def add_csrf_protection():
+ """adds csrf_token template tag to django
+ Must be used if version of django is < 1.2
+
+ Also adds csrf function to the context processor
+ and the csrf_protect decorator for the views
+ """
+ import django.template.defaulttags
+ def csrf_token(parser, token):
+ return CsrfTokenNode()
+ django.template.defaulttags.CsrfTokenNode = CsrfTokenNode
+ django.template.defaulttags.register.tag(csrf_token)
+
+ #add csrf context processor
+ import django.core.context_processors
+ django.core.context_processors.csrf = csrf
+
+ #add csrf_protect decorator
+ import django.views.decorators
+ django.views.decorators.csrf = imp.new_module('csrf')
+ django.views.decorators.csrf.csrf_protect = csrf_protect
diff --git a/askbot/skins/default/templates/404.html b/askbot/skins/default/templates/404.html
index 2a3933d8..158bfb94 100644
--- a/askbot/skins/default/templates/404.html
+++ b/askbot/skins/default/templates/404.html
@@ -1,5 +1,5 @@
{% load extra_tags %}
-{% include_jinja "404.jinja.html" %}
+{% include_jinja "404.jinja.html" request %}
{% comment %}
this one has to be a django template because of use of default hander404
{% endcomment %}
diff --git a/askbot/skins/default/templates/500.html b/askbot/skins/default/templates/500.html
index 04d706ec..8ec1bce4 100644
--- a/askbot/skins/default/templates/500.html
+++ b/askbot/skins/default/templates/500.html
@@ -1,5 +1,5 @@
{% load extra_tags %}
-{% include_jinja "500.jinja.html" %}
+{% include_jinja "500.jinja.html" request %}
{% comment %}this template must be django
because of the use of default handler500
{% endcomment %}
diff --git a/askbot/skins/loaders.py b/askbot/skins/loaders.py
index 1f2fa0fc..04c398bc 100644
--- a/askbot/skins/loaders.py
+++ b/askbot/skins/loaders.py
@@ -3,14 +3,17 @@ from django.template.loaders import filesystem
from django.template import RequestContext
from django.http import HttpResponse
from django.utils import translation
-from askbot.conf import settings as askbot_settings
from django.conf import settings as django_settings
from coffin.common import CoffinEnvironment
from jinja2 import loaders as jinja_loaders
from jinja2.exceptions import TemplateNotFound
from jinja2.utils import open_if_exists
+from askbot.conf import settings as askbot_settings
from askbot.skins import utils
+from coffin import template
+template.add_to_builtins('askbot.templatetags.extra_filters_jinja')
+
#module for skinning askbot
#via ASKBOT_DEFAULT_SKIN configureation variable (not django setting)
@@ -103,29 +106,26 @@ class SkinEnvironment(CoffinEnvironment):
return '<link href="%s" rel="stylesheet" type="text/css" />' % url
return ''
-ENV = SkinEnvironment(
- autoescape=False,
- extensions=['jinja2.ext.i18n'],
- skin = askbot_settings.ASKBOT_DEFAULT_SKIN
- #loader = SkinLoader()
- )
-ENV.set_language(django_settings.LANGUAGE_CODE)
-
def load_skins():
skins = dict()
for skin_name in utils.get_available_skins():
- skins[skin_name] = SkinEnvironment(skin = skin_name)
+ skins[skin_name] = SkinEnvironment(
+ skin = skin_name,
+ extensions=['jinja2.ext.i18n',]
+ )
skins[skin_name].set_language(django_settings.LANGUAGE_CODE)
+ #from askbot.templatetags import extra_filters_jinja as filters
+ #skins[skin_name].filters['media'] = filters.media
return skins
SKINS = load_skins()
-def get_skin(request):
+def get_skin(request = None):
"""retreives the skin environment
for a given request (request var is not used at this time)"""
return SKINS[askbot_settings.ASKBOT_DEFAULT_SKIN]
-def get_template(template, request):
+def get_template(template, request = None):
"""retreives template for the skin
request variable will be used in the future to set
template according to the user preference or admins preference
diff --git a/askbot/startup_procedures.py b/askbot/startup_procedures.py
index f2309dde..dffca0f1 100644
--- a/askbot/startup_procedures.py
+++ b/askbot/startup_procedures.py
@@ -3,6 +3,8 @@ in the beginning of models/__init__.py
the purpose of this module is to validate deployment of askbot
+question: why not run these from askbot/__init__.py?
+
the main function is run_startup_tests
"""
from django.db import transaction
diff --git a/askbot/templatetags/extra_tags.py b/askbot/templatetags/extra_tags.py
index faddccfb..9d21ca83 100644
--- a/askbot/templatetags/extra_tags.py
+++ b/askbot/templatetags/extra_tags.py
@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from askbot.utils import functions
from askbot.utils.slug import slugify
-from askbot.skins.loaders import ENV
+from askbot.skins.loaders import get_template
register = template.Library()
@@ -117,10 +117,12 @@ def cnprog_paginator(context):
class IncludeJinja(template.Node):
"""http://www.mellowmorning.com/2010/08/24/"""
- def __init__(self, filename):
+ def __init__(self, filename, request_var):
self.filename = filename
+ self.request_var = template.Variable(request_var)
def render(self, context):
- jinja_template = ENV.get_template(self.filename)
+ request = self.request_var.resolve(context)
+ jinja_template = get_template(self.filename, request)
return jinja_template.render(context)
@register.tag
@@ -128,11 +130,12 @@ def include_jinja(parser, token):
bits = token.contents.split()
#Check if a filename was given
- if len(bits) != 2:
+ if len(bits) != 3:
error_message = '%r tag requires the name of the ' + \
- 'template to be included included'
+ 'template and the request variable'
raise template.TemplateSyntaxError(error_message % bits[0])
filename = bits[1]
+ request_var = bits[2]
#Remove quotes or raise error
if filename[0] in ('"', "'") and filename[-1] == filename[0]:
@@ -140,4 +143,4 @@ def include_jinja(parser, token):
else:
raise template.TemplateSyntaxError('file name must be quoted')
- return IncludeJinja(filename)
+ return IncludeJinja(filename, request_var)
diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py
index 28e1ea6b..ab02efa8 100644
--- a/askbot/tests/page_load_tests.py
+++ b/askbot/tests/page_load_tests.py
@@ -1,23 +1,30 @@
-from django.test import TestCase, signals
-from jinja2.environment import Template as Jinja2Template
+from django.test import TestCase
+from django.test import signals
from django.template import defaultfilters
from django.core.urlresolvers import reverse
+import coffin
import coffin.template
from askbot import models
from askbot.utils.slug import slugify
+from askbot.deployment import package_utils
import sys
-#note - this code can be run only once
-ORIG_JINJA2_RENDERER = Jinja2Template.render
-def instrumented_render(template_object, *args, **kwargs):
- context = dict(*args, **kwargs)
- signals.template_rendered.send(
- sender=template_object,
- template=template_object,
- context=context
- )
- return ORIG_JINJA2_RENDERER(template_object, *args, **kwargs)
-Jinja2Template.render = instrumented_render
+def patch_jinja2():
+ from jinja2 import Template
+ ORIG_JINJA2_RENDERER = Template.render
+ def instrumented_render(template_object, *args, **kwargs):
+ context = dict(*args, **kwargs)
+ signals.template_rendered.send(
+ sender=template_object,
+ template=template_object,
+ context=context
+ )
+ return ORIG_JINJA2_RENDERER(template_object, *args, **kwargs)
+ Template.render = instrumented_render
+
+(CMAJOR, CMINOR, CMICRO) = package_utils.get_coffin_version()
+if CMAJOR == 0 and CMINOR == 3 and CMICRO < 4:
+ patch_jinja2()
class PageLoadTestCase(TestCase):
def try_url(
diff --git a/askbot/version.py b/askbot/version.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/askbot/version.py
diff --git a/askbot/views/readers.py b/askbot/views/readers.py
index ac988c9a..20a0df58 100644
--- a/askbot/views/readers.py
+++ b/askbot/views/readers.py
@@ -63,6 +63,7 @@ def questions(request):
List of Questions, Tagged questions, and Unanswered questions.
matching search query or user selection
"""
+ #before = datetime.datetime.now()
#don't allow to post to this view
if request.method == 'POST':
raise Http404
diff --git a/setup.py b/setup.py
index 198aacaf..a76dc68e 100644
--- a/setup.py
+++ b/setup.py
@@ -7,9 +7,9 @@ import sys
#you might want to install django-debug-toolbar as well
install_requires = [
- 'django==1.1.2',
+ 'django>=1.1.2',
'Jinja2',
- 'Coffin==0.3.0',
+ 'Coffin>=0.3',
'South>=0.7.1',
'oauth2',
'recaptcha-client',
@@ -24,7 +24,9 @@ install_requires = [
'django-kombu==0.9.2',
]
-import askbot
+#todo: have a dirty version retriever that
+#parses it out from askbot/__init__.py but does not
+#import it as there are issues
WIN_PLATFORMS = ('win32', 'cygwin',)
if sys.platform not in WIN_PLATFORMS:
@@ -32,7 +34,7 @@ if sys.platform not in WIN_PLATFORMS:
setup(
name = "askbot",
- version = askbot.get_version(),
+ version = '0.6.75',#remember to manually set this correctly
description = 'Question and Answer forum, like StackOverflow, written in python and Django',
packages = find_packages(),
author = 'Evgeny.Fadeev',