# -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 ############################################################################### # ScribeEngine - Open Source Blog Software # # --------------------------------------------------------------------------- # # Copyright (c) 2010 Raoul Snyman # # --------------------------------------------------------------------------- # # This program is free software; you can redistribute it and/or modify it # # under the terms of the GNU General Public License as published by the Free # # Software Foundation; version 2 of the License. # # # # This program is distributed in the hope that it will be useful, but WITHOUT # # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # # more details. # # # # You should have received a copy of the GNU General Public License along # # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ The base Controller API Provides the BaseController class for subclassing. """ from calendar import Calendar from datetime import datetime, timedelta from decorator import decorator import logging from paste.request import construct_url from webob.exc import HTTPMovedPermanently from pylons import c, request, session, response, config from pylons.controllers import WSGIController from pylons.templating import render_mako from sqlalchemy.sql.expression import asc, desc from formencode import Schema, Invalid from monthdelta import monthdelta from pytz import timezone from scribeengine.lib import helpers as h from scribeengine.lib.validation import jsvalidate from scribeengine.model.meta import Session from scribeengine.model import Variable, User, Category, Page, Post log = logging.getLogger(__name__) class BaseController(WSGIController): def __before__(self): if session.get(u'REMOTE_USER'): c.current_user = Session.query(User).get(session[u'REMOTE_USER']) c.blog_title = Session.query(Variable).get(u'blog title').value c.blog_slogan = Session.query(Variable).get(u'blog slogan').value c.categories = Session.query(Category).order_by(Category.name.asc()).all() c.pages = Session.query(Page).all() c.calendar = Calendar(6) server_tz = timezone(config.get(u'server.timezone', u'UTC')) user_tz = c.current_user.timezone if c.current_user and c.current_user.timezone else u'UTC' c.today = datetime.now(server_tz).astimezone(timezone(user_tz)) if not c.thismonth: c.thismonth = c.today #datetime.utcnow().astimezone(timezone(c.current_user.timezone)) c.prev_month = c.thismonth - monthdelta(1) c.next_month = c.thismonth + monthdelta(1) month_start = datetime(c.thismonth.year, c.thismonth.month, 1, 0, 0, 0, 0) month_end = c.next_month.replace(day=1, hour=23, minute=59, second=59, microsecond=9999) - timedelta(seconds=1) posts = Session.query(Post)\ .filter(Post.created >= month_start)\ .filter(Post.created <= month_end)\ .all() c.month_posts = {} for post in posts: if post.created.day not in c.month_posts: c.month_posts[post.created.day] = [] c.month_posts[post.created.day].append(post) self._add_javascript(u'jquery.js') self._add_javascript(u'jquery.metadata.js') if c.jsvalidation: self._add_javascript(u'jquery.validate.js') self._add_javascript(u'ScribeEngine.js') self._add_jsinit(u'ScribeEngine.Init.js') def __call__(self, environ, start_response): """Invoke the Controller""" # WSGIController.__call__ dispatches to the Controller method # the request is routed to. This routing information is # available in environ['pylons.routes_dict'] try: # If there's a jsschema function, create a template context variable # that will be used to call in the JavaScript validation. route = u'/%s/%s' % (environ[u'pylons.routes_dict'][u'controller'], environ[u'pylons.routes_dict'][u'action']) action = environ[u'pylons.routes_dict'][u'action'] post = u'%s_POST' % action jsschema = u'%s_jsschema' % action schema = u'%s_schema' % action if getattr(self, jsschema, None) is not None: c.jsvalidation = route + u'_jsschema' if environ[u'REQUEST_METHOD'].upper() == u'POST': # Set up an initial, empty, set of form values. c.form_values = {} # Do validation according to schema here, even bfore it hits the _POST validators = getattr(self, schema, None) if validators: schema = self._make_schema(validators) unvalidated_fields = {} for key in schema.fields.keys(): if key in request.POST: value = request.POST.getall(key) if len(value) == 1: unvalidated_fields[key] = value[0] elif len(value) == 0: unvalidated_fields[key] = None else: unvalidated_fields[key] = value success, results = self._validate(schema, unvalidated_fields) # Any error messages plus the form values are put into the # c variable so that it is available to the called method. c.form_errors = results[u'errors'] c.form_values = results[u'values'] if not success: # If validation failed, go back to the original method. return WSGIController.__call__(self, environ, start_response) # Run through all of the POST variables, and make sure that # we stick any remaining variables into c.form_values for key in request.POST.keys(): if key not in c.form_values: value = request.POST.getall(key) if len(value) == 1: c.form_values[key] = value[0] elif len(value) == 0: c.form_values[key] = None else: c.form_values[key] = value # So, at this stage, we've done all is necessary, but this is # a POST, so send us on to a POST method if it exists. if getattr(self, post, None): environ[u'pylons.routes_dict'][u'action'] = post return WSGIController.__call__(self, environ, start_response) finally: Session.remove() def _make_schema(self, validators_function): """ _make_schema is a method which is used to automatically convert a dictionary of FormEncode validators into a schema. """ validators = validators_function() return Schema(**validators) def _validate(self, schema, params): """ Validate the (usually POST) request parameters against a FormEncode schema. Returns either: `(True, values)` Validation passed, and the values returned are converted (ie, of the correct type and normalised - leading and/or trailing spaces removed, etc.) `(False, errors)` Validation failed, and the errors returned are a dictionary: `values` The values as given in the request `errors` The validation errors (as many as can be given) """ if callable(schema): validator = schema() else: validator = schema # Remove 'submit', so that the schema doesn't need to contain it if u'submit' in params: del params[u'submit'] try: values = validator.to_python(params) except Invalid, e: if e.error_dict: # Standard case - each item in the schema that fails is added # to error_dict with the key being the item name and the value # a FormEncode error object. error_dict = e.error_dict.copy() else: # On full-form validation failure (for example, sending additional # data), the form isn't validated, and thus there is no error_dict error_dict = {} error_dict[None] = e.unpack_errors() return False, {u'values': params, u'errors': error_dict} return True, {u'values': values, u'errors': {}} def _add_javascript(self, filename): """ This method dynamically adds javascript files to the section of the template. """ if not getattr(c, u'scripts', None): c.scripts = [] c.scripts.append(filename) def _add_jsinit(self, filename): """ This method dynamically includes a special javascript initialisation file to the section of the template. """ c.jsinit = filename def _add_stylesheet(self, filename, subdir=None): """ This method dynamically adds javascript files to the section of the template. """ if not getattr(c, u'styles', None): c.styles = [] if subdir is None: subdir = c.theme_name c.styles.append(filename, subdir) def authenticate(permission=None): """ A decorator used to check the access level of a member against a permission. """ def validate(func, self, *args, **kwargs): if session.get(u'REMOTE_USER'): user = Session.query(User).get(session[u'REMOTE_USER']) if user: if permission and not user.has_permission(permission): h.flash.set_message( u'You don\'t have access to that area.', u'error') h.redirect_to('/') return func(self, *args, **kwargs) else: h.flash.set_message( u'You don\'t have access to that area.', u'error') h.redirect_to('/') else: session[u'redirect_url'] = request.environ[u'PATH_INFO'] session.save() h.flash.set_message(u'You need to be logged in to do that.', u'error') h.redirect_to('/account/login') return decorator(validate) def render(template): if request.environ['PATH_INFO'] == '/': c.page_title = u'%s | %s ' % (c.blog_title, c.blog_slogan) else: c.page_title = u'%s | %s ' % (c.page_title, c.blog_title) return render_mako(template)