258 lines
11 KiB
Python
258 lines
11 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, 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 <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):
|
|
"""
|
|
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)
|
|
|
|
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('/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)
|
|
|