scribeengine/scribeengine/lib/base.py

241 lines
10 KiB
Python

# -*- 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
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 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
log = logging.getLogger(__name__)
class BaseController(WSGIController):
def __before__(self):
#c.theme_name = Session.query(Configuration).get(u'theme').value
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)
c.today = datetime.today()
if not c.thismonth:
c.thismonth = datetime.now()
self._add_javascript(u'jquery.js')
if c.jsvalidation:
self._add_javascript(u'jquery.validate.js')
self._add_javascript(u'scribeengine.js')
self._add_jsinit(u'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 <method>_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, subdir=u'global'):
"""
This method dynamically adds javascript files to the <head> section
of the template.
"""
if not getattr(c, u'scripts', None):
c.scripts = []
c.scripts.append((filename, subdir))
def _add_jsinit(self, filename):
"""
This method dynamically includes a special javascript initialisation
file to the <head> section of the template.
"""
c.jsinit = filename
def _add_stylesheet(self, filename, subdir=None):
"""
This method dynamically adds javascript files to the <head> 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('/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)