diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2011-03-28 23:03:16 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2011-03-28 23:03:16 -0400 |
commit | 96a9863db21fc84af38ee28684807849ade25b97 (patch) | |
tree | 09d752e19aab3f4deb31d77ec68c2f12b48ace57 | |
parent | 8ae8fcdc858af16e8f2d5ab87ae1a833cde52356 (diff) | |
download | askbot-96a9863db21fc84af38ee28684807849ade25b97.tar.gz askbot-96a9863db21fc84af38ee28684807849ade25b97.tar.bz2 askbot-96a9863db21fc84af38ee28684807849ade25b97.zip |
expanded range of supported versions of django and added patches for csrf_token
-rw-r--r-- | askbot/__init__.py | 13 | ||||
-rw-r--r-- | askbot/deployment/assertions.py | 26 | ||||
-rw-r--r-- | askbot/deployment/package_utils.py | 28 | ||||
-rw-r--r-- | askbot/deps/django_authopenid/views.py | 57 | ||||
-rw-r--r-- | askbot/exceptions.py | 4 | ||||
-rw-r--r-- | askbot/middleware/pagesize.py | 4 | ||||
-rw-r--r-- | askbot/models/__init__.py | 4 | ||||
-rw-r--r-- | askbot/patches/__init__.py | 28 | ||||
-rw-r--r-- | askbot/patches/coffin_patches.py | 34 | ||||
-rw-r--r-- | askbot/patches/django_patches.py | 327 | ||||
-rw-r--r-- | askbot/skins/default/templates/404.html | 2 | ||||
-rw-r--r-- | askbot/skins/default/templates/500.html | 2 | ||||
-rw-r--r-- | askbot/skins/loaders.py | 24 | ||||
-rw-r--r-- | askbot/startup_procedures.py | 2 | ||||
-rw-r--r-- | askbot/templatetags/extra_tags.py | 15 | ||||
-rw-r--r-- | askbot/tests/page_load_tests.py | 33 | ||||
-rw-r--r-- | askbot/version.py | 0 | ||||
-rw-r--r-- | askbot/views/readers.py | 1 | ||||
-rw-r--r-- | setup.py | 10 |
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 @@ -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', |