diff --git a/lodgeit/application.py b/lodgeit/application.py index fd767af..5c45fdb 100644 --- a/lodgeit/application.py +++ b/lodgeit/application.py @@ -11,96 +11,13 @@ import os import sqlalchemy from datetime import datetime, timedelta -from werkzeug.wrappers import BaseRequest, BaseResponse -from werkzeug.utils import SharedDataMiddleware -from werkzeug.routing import NotFound, RequestRedirect -from jinja import Environment, PackageLoader - +from werkzeug import SharedDataMiddleware, ClosingIterator +from lodgeit.utils import _local_manager, ctx, jinja_environment, \ + Request, generate_user_hash, NotFound, RequestRedirect, redirect +from lodgeit.database import metadata, db, Paste +from lodgeit.lib.antispam import AntiSpam from lodgeit.urls import urlmap from lodgeit.controllers import get_controller -from lodgeit.database import metadata, generate_user_hash, Paste -from lodgeit.lib.antispam import AntiSpam - - -#: jinja environment for all the templates -jinja_environment = Environment(loader=PackageLoader('lodgeit', 'views', - use_memcache=False, - auto_reload=False -)) - - -def datetimeformat(): - """ - Helper filter for the template - """ - def wrapped(env, ctx, value): - return value.strftime('%Y-%m-%d %H:%M') - return wrapped - -jinja_environment.filters['datetimeformat'] = datetimeformat - - -def render_template(req, template_name, **context): - """ - Render a template to a response. This automatically fetches - the list of new replies for the layout template. It also - adds the current request to the context. This is used for the - welcome message. - """ - if req.method == 'GET': - context['new_replies'] = Paste.fetch_replies(req) - context['request'] = req - t = jinja_environment.get_template(template_name) - return Response(t.render(context), mimetype='text/html; charset=utf-8') - - -def redirect(url, code=302): - """ - Redirect to somewhere. Returns a nice response object. - """ - return Response('Page Moved to %s' % url, - headers=[('Location', url), - ('Content-Type', 'text/plain')], - status=code) - - -class Request(BaseRequest): - """ - Subclass of the `BaseRequest` object. automatically creates a new - `user_hash` and sets `first_visit` to `True` if it's a new user. - It also stores the engine and dbsession on it. - """ - charset = 'utf-8' - - def __init__(self, environ, app): - self.app = app - self.engine = app.engine - self.dbsession = sqlalchemy.create_session(app.engine) - super(Request, self).__init__(environ) - - # check the user hash. an empty cookie is considered - # begin a new session. - self.user_hash = '' - self.first_visit = False - if 'user_hash' in self.cookies: - self.user_hash = self.cookies['user_hash'] - if not self.user_hash: - self.user_hash = generate_user_hash() - self.first_visit = True - - -class Response(BaseResponse): - """ - Subclass the response object for later extension. - """ - charset = 'utf-8' - - -class PageNotFound(NotFound): - """ - Internal exception used to tell the application to show the - error page. - """ class LodgeIt(object): @@ -114,40 +31,53 @@ class LodgeIt(object): self.engine = sqlalchemy.create_engine(dburi) #: make sure all tables exist. metadata.create_all(self.engine) - #: create a new antispam instance - self.antispam = AntiSpam(self) + #: bind the application to the current context local + self.bind_to_context() + #: create a new AntiSpam instance + self.antispam = AntiSpam() + + def bind_to_context(self): + ctx.application = self def __call__(self, environ, start_response): """ Minimal WSGI application for request dispatching. """ - req = Request(environ, self) + #: bind the application to the new context local + self.bind_to_context() + request = Request(environ) + request.bind_to_context() urls = urlmap.bind_to_environ(environ) try: - endpoint, args = urls.match(req.path) - handler = get_controller(endpoint, req) + endpoint, args = urls.match(request.path) + handler = get_controller(endpoint) resp = handler(**args) except NotFound: - handler = get_controller(self.not_found[0], req) + handler = get_controller(self.not_found[0]) resp = handler(**self.not_found[1]) except RequestRedirect, err: resp = redirect(err.new_url) else: - if req.first_visit: - resp.set_cookie('user_hash', req.user_hash, + if request.first_visit: + resp.set_cookie('user_hash', request.user_hash, expires=datetime.utcnow() + timedelta(days=31) ) - # call the response as WSGI app - return resp(environ, start_response) + + return ClosingIterator(resp(environ, start_response), + [_local_manager.cleanup, db.session.remove]) -def make_app(dburi): +def make_app(dburi, debug=False, shell=False): """ Apply the used middlewares and create the application. """ static_path = os.path.join(os.path.dirname(__file__), 'static') app = LodgeIt(dburi) - app = SharedDataMiddleware(app, { - '/static': static_path - }) + if debug: + app.engine.echo = True + if not shell: + # we don't need access to the shared data middleware in shell mode + app = SharedDataMiddleware(app, { + '/static': static_path + }) return app diff --git a/lodgeit/controllers/__init__.py b/lodgeit/controllers/__init__.py index b698370..841e7a7 100644 --- a/lodgeit/controllers/__init__.py +++ b/lodgeit/controllers/__init__.py @@ -11,18 +11,13 @@ class BaseController(object): """ - Base controller. add some stuff to the dict on instanciation + Base controller class. This does nothing *yet* but + maybe is usefull later. """ - def __init__(self, req): - self.request = req - self.app = req.app - self.engine = req.engine - self.dbsession = req.dbsession - -def get_controller(name, req): +def get_controller(name): cname, hname = name.split('/') module = __import__('lodgeit.controllers.' + cname, None, None, ['']) - controller = module.controller(req) + controller = module.controller() return getattr(controller, hname) diff --git a/lodgeit/controllers/pastes.py b/lodgeit/controllers/pastes.py index 16ac0f8..f751b03 100644 --- a/lodgeit/controllers/pastes.py +++ b/lodgeit/controllers/pastes.py @@ -8,12 +8,10 @@ :copyright: 2007 by Armin Ronacher. :license: BSD """ -import sqlalchemy as meta - -from lodgeit.application import render_template, redirect, PageNotFound, \ - Response +from lodgeit.utils import redirect, PageNotFound, Response, \ + ctx, render_template from lodgeit.controllers import BaseController -from lodgeit.database import Paste +from lodgeit.database import db, Paste from lodgeit.lib.highlighting import LANGUAGES, STYLES, get_style from lodgeit.lib.pagination import generate_pagination @@ -32,34 +30,35 @@ class PasteController(BaseController): """ code = error = '' language = 'text' - pastes = self.dbsession.query(Paste) + pastes = db.session.query(Paste) - if self.request.method == 'POST': - code = self.request.form.get('code') - language = self.request.form.get('language') + if ctx.request.method == 'POST': + code = ctx.request.form.get('code') + language = ctx.request.form.get('language') try: - parent = pastes.selectfirst(Paste.c.paste_id == - int(self.request.form.get('parent'))) + parent = pastes.filter(Paste.paste_id == + int(ctx.request.form.get('parent'))).first() except (KeyError, ValueError, TypeError): parent = None - spam = self.request.form.get('webpage')# or \ - #self.app.antispam.is_spam(code, language) + spam = ctx.request.form.get('webpage') + #TODO: use AntiSpam again + # or \ #self.app.antispam.is_spam(code, language) if spam: error = 'contains spam' if code and language and not error: - paste = Paste(code, language, parent, self.request.user_hash) - self.dbsession.save(paste) - self.dbsession.flush() + paste = Paste(code, language, parent, ctx.request.user_hash) + db.session.save(paste) + db.session.flush() return redirect(paste.url) else: - parent = self.request.args.get('reply_to') + parent = ctx.request.args.get('reply_to') if parent is not None and parent.isdigit(): - parent = pastes.selectfirst(Paste.c.paste_id == parent) + parent = pastes.filter(Paste.paste_id == parent).first() code = parent.code language = parent.language - return render_template(self.request, 'new_paste.html', + return render_template('new_paste.html', languages=LANGUAGES, parent=parent, code=code, @@ -71,19 +70,19 @@ class PasteController(BaseController): """ Show an existing paste. """ - linenos = self.request.args.get('linenos') != 'no' - pastes = self.dbsession.query(Paste) - paste = pastes.selectfirst(Paste.c.paste_id == paste_id) + linenos = ctx.request.args.get('linenos') != 'no' + pastes = db.session.query(Paste) + paste = pastes.filter(Paste.c.paste_id == paste_id).first() if paste is None: raise PageNotFound() if raw: return Response(paste.code, mimetype='text/plain; charset=utf-8') - style, css = get_style(self.request) + style, css = get_style(ctx.request) paste.rehighlight(linenos) - return render_template(self.request, 'show_paste.html', + return render_template('show_paste.html', paste=paste, style=style, css=css, @@ -101,10 +100,10 @@ class PasteController(BaseController): """ Display the tree of some related pastes. """ - paste = Paste.resolve_root(self.dbsession, paste_id) + paste = Paste.resolve_root(paste_id) if paste is None: raise PageNotFound() - return render_template(self.request, 'paste_tree.html', + return render_template('paste_tree.html', paste=paste, current=paste_id ) @@ -113,28 +112,26 @@ class PasteController(BaseController): """ Paginated list of pages. """ - raise PageNotFound() + raise PageNotFound() #XXX: activate again? def link(page): if page == 1: return '/all/' return '/all/%d' % page - pastes = self.dbsession.query(Paste).select( - order_by=[meta.desc(Paste.c.pub_date)], - limit=10, - offset=10 * (page - 1) - ) + pastes = db.session.query(Paste).order_by( + Paste.c.pub_date.desc() + ).limit(10).offset(10*(page-1)) if not pastes and page != 1: raise PageNotFound() for paste in pastes: paste.rehighlight() - return render_template(self.request, 'show_all.html', + return render_template('show_all.html', pastes=pastes, pagination=generate_pagination(page, 10, - Paste.count(self.request.engine), link), - css=get_style(self.request)[1] + Paste.count(), link), + css=get_style(ctx.request)[1] ) def compare_paste(self, new_id=None, old_id=None): @@ -143,15 +140,15 @@ class PasteController(BaseController): """ # redirect for the compare form box if old_id is new_id is None: - old_id = self.request.form.get('old', '-1').lstrip('#') - new_id = self.request.form.get('new', '-1').lstrip('#') + old_id = ctx.request.form.get('old', '-1').lstrip('#') + new_id = ctx.request.form.get('new', '-1').lstrip('#') return redirect('/compare/%s/%s' % (old_id, new_id)) - pastes = self.dbsession.query(Paste) - old = pastes.selectfirst(Paste.c.paste_id == old_id) - new = pastes.selectfirst(Paste.c.paste_id == new_id) + pastes = db.session.query(Paste) + old = pastes.filter(Paste.c.paste_id == old_id).first() + new = pastes.filter(Paste.c.paste_id == new_id).first() if old is None or new is None: raise PageNotFound() - return render_template(self.request, 'compare_paste.html', + return render_template('compare_paste.html', old=old, new=new, diff=old.compare_to(new, template=True) @@ -161,9 +158,9 @@ class PasteController(BaseController): """ Render an udiff for the two pastes. """ - pastes = self.dbsession.query(Paste) - old = pastes.selectfirst(Paste.c.paste_id == old_id) - new = pastes.selectfirst(Paste.c.paste_id == new_id) + pastes = db.session.query(Paste) + old = pastes.filter(Paste.c.paste_id == old_id).first() + new = pastes.filter(Paste.c.paste_id == new_id).first() if old is None or new is None: raise PageNotFound() return Response(old.compare_to(new), mimetype='text/plain') @@ -173,8 +170,8 @@ class PasteController(BaseController): Minimal view that updates the style session cookie. Redirects back to the page the user is coming from. """ - style_name = self.request.form.get('style') - resp = redirect(self.request.environ.get('HTTP_REFERER') or '/') + style_name = ctx.request.form.get('style') + resp = redirect(ctx.request.environ.get('HTTP_REFERER') or '/') if style_name in STYLES: resp.set_cookie('style', style_name) return resp diff --git a/lodgeit/controllers/static.py b/lodgeit/controllers/static.py index 91b783d..27a979a 100644 --- a/lodgeit/controllers/static.py +++ b/lodgeit/controllers/static.py @@ -8,7 +8,7 @@ :copyright: 2007 by Armin Ronacher. :license: BSD """ -from lodgeit.application import render_template, PageNotFound +from lodgeit.utils import ctx, PageNotFound, render_template from lodgeit.controllers import BaseController from lodgeit.lib.xmlrpc import xmlrpc @@ -26,10 +26,10 @@ known_help_pages = set(x[0] for x in HELP_PAGES) class StaticController(BaseController): def not_found(self): - return render_template(self.request, 'not_found.html') + return render_template('not_found.html') def about(self): - return render_template(self.request, 'about.html') + return render_template('about.html') def help(self, topic=None): if topic is None: @@ -38,12 +38,14 @@ class StaticController(BaseController): tmpl_name = 'help/%s.html' % topic else: raise PageNotFound() - return render_template(self.request, tmpl_name, - help_topics=HELP_PAGES, - current_topic=topic, - xmlrpc_url='http://%s/xmlrpc/' % - self.request.environ['SERVER_NAME'], - xmlrpc_methods=xmlrpc.get_public_methods()) + return render_template( + tmpl_name, + help_topics=HELP_PAGES, + current_topic=topic, + xmlrpc_url='http://%s/xmlrpc/' % + ctx.request.environ['SERVER_NAME'], + xmlrpc_methods=xmlrpc.get_public_methods() + ) controller = StaticController diff --git a/lodgeit/controllers/xmlrpc.py b/lodgeit/controllers/xmlrpc.py index cc843f2..a3f2ebf 100644 --- a/lodgeit/controllers/xmlrpc.py +++ b/lodgeit/controllers/xmlrpc.py @@ -8,11 +8,9 @@ :copyright: 2007 by Armin Ronacher, Georg Brandl. :license: BSD """ -import sqlalchemy as meta - -from lodgeit.application import render_template +from lodgeit.utils import ctx, render_template from lodgeit.controllers import BaseController -from lodgeit.database import Paste +from lodgeit.database import db, Paste from lodgeit.lib.xmlrpc import xmlrpc, exported from lodgeit.lib.highlighting import STYLES, LANGUAGES, get_style, \ get_language_for @@ -21,13 +19,13 @@ from lodgeit.lib.highlighting import STYLES, LANGUAGES, get_style, \ class XmlRpcController(BaseController): def handle_request(self): - if self.request.method == 'POST': - return xmlrpc.handle_request(self.request) - return render_template(self.request, 'xmlrpc.html') + if ctx.request.method == 'POST': + return xmlrpc.handle_request() + return render_template('xmlrpc.html') @exported('pastes.newPaste') -def pastes_new_paste(request, language, code, parent_id=None, +def pastes_new_paste(language, code, parent_id=None, filename='', mimetype=''): """ Create a new paste. Return the new ID. @@ -38,13 +36,13 @@ def pastes_new_paste(request, language, code, parent_id=None, if not language: language = get_language_for(filename or '', mimetype or '') paste = Paste(code, language, parent_id) - request.dbsession.save(paste) - request.dbsession.flush() + db.session.save(paste) + db.session.flush() return paste.paste_id @exported('pastes.getPaste') -def pastes_get_paste(request, paste_id): +def pastes_get_paste(paste_id): """ Get all known information about a paste by a given paste id. @@ -52,52 +50,51 @@ def pastes_get_paste(request, paste_id): `paste_id`, `code`, `parsed_code`, `pub_date`, `language`, `parent_id`, `url`. """ - paste = request.dbsession.query(Paste).selectfirst(Paste.c.paste_id == - paste_id) + paste = db.session.query(Paste).filter(Paste.c.paste_id == + paste_id).first() if paste is None: return False return paste.to_xmlrpc_dict() @exported('pastes.getDiff') -def pastes_get_diff(request, old_id, new_id): +def pastes_get_diff(old_id, new_id): """ Compare the two pastes and return an unified diff. """ - paste = request.dbsession.query(Paste) - old = pastes.selectfirst(Paste.c.paste_id == old_id) - new = pastes.selectfirst(Paste.c.paste_id == new_id) + pastes = db.session.query(Paste) + old = pastes.filter(Paste.c.paste_id == old_id).first() + new = pastes.filter(Paste.c.paste_id == new_id).first() if old is None or new is None: return False return old.compare_to(new) @exported('pastes.getRecent') -def pastes_get_recent(request, amount=5): +def pastes_get_recent(amount=5): """ Return information dict (see `getPaste`) about the last `amount` pastes. """ amount = min(amount, 20) return [x.to_xmlrpc_dict() for x in - request.dbsession.query(Paste).select( - order_by=[meta.desc(Paste.c.pub_date)], - limit=amount - )] + db.session.query(Paste).order_by( + Paste.pub_date.desc() + ).limit(amount)] @exported('pastes.getLast') -def pastes_get_last(request): +def pastes_get_last(): """ Get information dict (see `getPaste`) for the most recent paste. """ - rv = pastes_get_recent(request, 1) + rv = pastes_get_recent(1) if rv: return rv[0] return {} @exported('pastes.getLanguages') -def pastes_get_languages(request): +def pastes_get_languages(): """ Get a list of supported languages. """ @@ -105,7 +102,7 @@ def pastes_get_languages(request): @exported('styles.getStyles') -def styles_get_styles(request): +def styles_get_styles(): """ Get a list of supported styles. """ @@ -113,7 +110,7 @@ def styles_get_styles(request): @exported('styles.getStylesheet') -def styles_get_stylesheet(request, name): +def styles_get_stylesheet(name): """ Return the stylesheet for a given style. """ @@ -121,48 +118,48 @@ def styles_get_stylesheet(request, name): @exported('antispam.addRule', hidden=True) -def antispam_add_rule(request, rule): - request.app.antispam.add_rule(rule) +def antispam_add_rule(rule): + ctx.application.antispam.add_rule(rule) @exported('antispam.removeRule', hidden=True) -def antispam_remove_rule(request, rule): - request.app.antispam.remove_rule(rule) +def antispam_remove_rule(rule): + ctx.application.antispam.remove_rule(rule) @exported('antispam.getRules', hidden=True) -def antispam_get_rules(request): - return sorted(request.app.antispam.get_rules()) +def antispam_get_rules(): + return sorted(ctx.application.antispam.get_rules()) @exported('antispam.hasRule', hidden=True) -def antispam_has_rule(request, rule): - return request.app.antispam.rule_exists(rule) +def antispam_has_rule(rule): + return ctx.application.antispam.rule_exists(rule) @exported('antispam.addSyncSource', hidden=True) -def antispam_add_sync_source(request, url): - request.app.antispam.add_sync_source(url) +def antispam_add_sync_source(url): + ctx.application.antispam.add_sync_source(url) @exported('antispam.removeSyncSource', hidden=True) -def antispam_remove_sync_source(request, url): - request.app.antispam.remove_sync_source(url) +def antispam_remove_sync_source(url): + ctx.application.antispam.remove_sync_source(url) @exported('antispam.getSyncSources', hidden=True) -def antispam_get_sync_sources(request): - return sorted(request.app.antispam.get_sync_sources()) +def antispam_get_sync_sources(): + return sorted(ctx.application.antispam.get_sync_sources()) @exported('antispam.hasSyncSource', hidden=True) -def antispam_has_sync_source(request, url): - return url in request.app.antispam.get_sync_sources() +def antispam_has_sync_source(url): + return url in ctx.application.antispam.get_sync_sources() @exported('antispam.triggerSync', hidden=True) -def antispam_trigger_sync(request): - request.app.antispam.sync_sources() +def antispam_trigger_sync(): + ctx.application.antispam.sync_sources() controller = XmlRpcController diff --git a/lodgeit/database.py b/lodgeit/database.py index 2759b51..05833b4 100644 --- a/lodgeit/database.py +++ b/lodgeit/database.py @@ -10,47 +10,65 @@ """ import time import difflib -import sqlalchemy as meta +from types import ModuleType +import sqlalchemy +from sqlalchemy import orm +from sqlalchemy.orm.scoping import ScopedSession from cgi import escape -from random import random -from hashlib import sha1 from datetime import datetime - +from lodgeit.utils import _local_manager, ctx from lodgeit.lib.highlighting import highlight, LANGUAGES -metadata = meta.MetaData() + +def session_factory(): + return orm.create_session(bind=ctx.application.engine) + +session = ScopedSession(session_factory, scopefunc=_local_manager.get_ident) + +#: create a fake module for easy access to database session methods +db = ModuleType('db') +key = value = mod = None +for mod in sqlalchemy, orm: + for key, value in mod.__dict__.iteritems(): + if key in mod.__all__: + setattr(db, key, value) +del key, mod, value + +db.__doc__ = __doc__ +for name in 'delete', 'save', 'flush', 'execute', 'begin', 'query', \ + 'commit', 'rollback', 'clear', 'refresh', 'expire': + setattr(db, name, getattr(session, name)) +db.session = session -pastes = meta.Table('pastes', metadata, - meta.Column('paste_id', meta.Integer, primary_key=True), - meta.Column('code', meta.Unicode), - meta.Column('parsed_code', meta.Unicode), - meta.Column('parent_id', meta.Integer, meta.ForeignKey('pastes.paste_id'), +metadata = db.MetaData() + +pastes = db.Table('pastes', metadata, + db.Column('paste_id', db.Integer, primary_key=True), + db.Column('code', db.Unicode), + db.Column('parsed_code', db.Unicode), + db.Column('parent_id', db.Integer, db.ForeignKey('pastes.paste_id'), nullable=True), - meta.Column('pub_date', meta.DateTime), - meta.Column('language', meta.Unicode(30)), - meta.Column('user_hash', meta.Unicode(40), nullable=True), - meta.Column('handled', meta.Boolean, nullable=False) + db.Column('pub_date', db.DateTime), + db.Column('language', db.Unicode(30)), + db.Column('user_hash', db.Unicode(40), nullable=True), + db.Column('handled', db.Boolean, nullable=False) ) -spam_rules = meta.Table('spam_rules', metadata, - meta.Column('rule_id', meta.Integer, primary_key=True), - meta.Column('rule', meta.Unicode) +spam_rules = db.Table('spam_rules', metadata, + db.Column('rule_id', db.Integer, primary_key=True), + db.Column('rule', db.Unicode) ) -spamsync_sources = meta.Table('spamsync_sources', metadata, - meta.Column('source_id', meta.Integer, primary_key=True), - meta.Column('url', meta.Unicode), - meta.Column('last_update', meta.DateTime) +spamsync_sources = db.Table('spamsync_sources', metadata, + db.Column('source_id', db.Integer, primary_key=True), + db.Column('url', db.Unicode), + db.Column('last_update', db.DateTime) ) -def generate_user_hash(): - return sha1('%s|%s' % (random(), time.time())).hexdigest() - - class Paste(object): def __init__(self, code, language, parent=None, user_hash=None): @@ -113,36 +131,36 @@ class Paste(object): return '
%s
' % code @staticmethod - def fetch_replies(req): + def fetch_replies(): """ - Get the new replies for the owern of a request and flag them + Get the new replies for the ower of a request and flag them as handled. """ - s = meta.select([pastes.c.paste_id], - pastes.c.user_hash == req.user_hash - ) - paste_list = req.dbsession.query(Paste).select( - (Paste.c.parent_id.in_(s)) & - (Paste.c.handled == False) & - (Paste.c.user_hash != req.user_hash), - order_by=[meta.desc(Paste.c.pub_date)] + s = db.select([pastes.c.paste_id], + Paste.user_hash == ctx.request.user_hash ) + + paste_list = db.session.query(Paste).filter(db.and_( + Paste.parent_id.in_(s), + Paste.handled == False, + Paste.user_hash != ctx.request.user_hash, + )).order_by(pastes.c.paste_id.desc()).all() + to_mark = [p.paste_id for p in paste_list] - req.engine.execute(pastes.update(pastes.c.paste_id.in_(*to_mark)), - handled=True - ) + db.execute(pastes.update(pastes.c.paste_id.in_(to_mark), + values={'handled': True})) return paste_list @staticmethod - def count(con): - s = meta.select([meta.func.count(pastes.c.paste_id)]) - return con.execute(s).fetchone()[0] + def count(): + s = db.select([db.func.count(pastes.c.paste_id)]) + return db.execute(s).fetchone()[0] @staticmethod - def resolve_root(sess, paste_id): - q = sess.query(Paste) + def resolve_root(paste_id): + pastes = db.query(Paste) while True: - paste = q.selectfirst(Paste.c.paste_id == paste_id) + paste = pastes.filter(Paste.c.paste_id == paste_id).first() if paste is None: return if paste.parent_id is None: @@ -150,10 +168,10 @@ class Paste(object): paste_id = paste.parent_id -meta.mapper(Paste, pastes, properties={ - 'children': meta.relation(Paste, +db.mapper(Paste, pastes, properties={ + 'children': db.relation(Paste, primaryjoin=pastes.c.parent_id==pastes.c.paste_id, cascade='all', - backref=meta.backref('parent', remote_side=[pastes.c.paste_id]) + backref=db.backref('parent', remote_side=[pastes.c.paste_id]) ) }) diff --git a/lodgeit/lib/antispam.py b/lodgeit/lib/antispam.py index d787a76..a27f11d 100644 --- a/lodgeit/lib/antispam.py +++ b/lodgeit/lib/antispam.py @@ -12,7 +12,7 @@ import re import urllib import time from datetime import datetime, timedelta -from lodgeit.database import spam_rules, spamsync_sources +from lodgeit.database import db, spam_rules, spamsync_sources UPDATE_INTERVAL = 60 * 60 * 24 * 7 @@ -24,23 +24,22 @@ class AntiSpam(object): updated from the moin moin server) and checks strings against it. """ - def __init__(self, app): - self.engine = app.engine + def __init__(self): self._rules = {} self.sync_with_db() def add_sync_source(self, url): """Add a new sync source.""" - self.engine.execute(spamsync_sources.insert(), url=url) + db.session.execute(spamsync_sources.insert(), url=url) def remove_sync_source(self, url): """Remove a sync soruce.""" - self.engine.execute(spamsync_sources.delete( - spamsync_sources.c.url == url)) + db.session.execute(spamsync_sources.delete( + spamsync_sources.c.url == url)) def get_sync_sources(self): """Get a list of all spamsync sources.""" - return set(x.url for x in self.engine.execute( + return set(x.url for x in db.session.execute( spamsync_sources.select())) def add_rule(self, rule, noinsert=False): @@ -52,12 +51,12 @@ class AntiSpam(object): return self._rules[rule] = regex if not noinsert: - self.engine.execute(spam_rules.insert(), rule=rule) + db.session.execute(spam_rules.insert(), rule=rule) def remove_rule(self, rule): """Remove a rule from the database.""" self._rules.pop(rule, None) - self.engine.execute(spam_rules.delete(spam_rules.c.rule == rule)) + db.session.execute(spam_rules.delete(spam_rules.c.rule == rule)) def get_rules(self): """Get a list of all spam rules.""" @@ -71,7 +70,7 @@ class AntiSpam(object): """Sync with the database.""" # compile rules from the database and save them on the instance processed = set() - for row in self.engine.execute(spam_rules.select()): + for row in db.session.execute(spam_rules.select()): if row.rule in processed: continue processed.add(row.rule) @@ -83,14 +82,14 @@ class AntiSpam(object): for rule in set(self._rules) - processed: del self._rules[rule] to_delete.append(rule) - self.engine.execute(spam_rules.delete( - spam_rules.c.rule.in_(*to_delete))) + db.session.execute(spam_rules.delete( + spam_rules.c.rule.in_(to_delete))) # otherwise add the rules to the database else: for rule in set(self._rules) - processed: - self.engine.execute(spam_rules.insert(), - rule=rule) + db.session.execute(spam_rules.insert(), + rule=rule) def sync_sources(self): """Trigger the syncing.""" @@ -103,7 +102,7 @@ class AntiSpam(object): update_after = datetime.utcnow() - timedelta(seconds=UPDATE_INTERVAL) q = (spamsync_sources.c.last_update == None) | \ (spamsync_sources.c.last_update < update_after) - sources = list(self.engine.execute(spamsync_sources.select(q))) + sources = list(db.session.execute(spamsync_sources.select(q))) if sources: for source in sources: self.sync_source(source.url) @@ -113,8 +112,8 @@ class AntiSpam(object): """Sync one source.""" self.load_rules(url) q = spamsync_sources.c.url == url - self.engine.execute(spamsync_sources.update(q), - last_update=datetime.utcnow()) + db.session.execute(spamsync_sources.update(q), + last_update=datetime.utcnow()) def load_rules(self, url): """Load some rules from an URL.""" diff --git a/lodgeit/lib/xmlrpc.py b/lodgeit/lib/xmlrpc.py index 7ae2d44..28fc4ed 100644 --- a/lodgeit/lib/xmlrpc.py +++ b/lodgeit/lib/xmlrpc.py @@ -11,8 +11,8 @@ import re import inspect from SimpleXMLRPCServer import SimpleXMLRPCDispatcher +from lodgeit.utils import ctx, Response -from lodgeit.application import Response _strip_re = re.compile(r'[\x00-\x08\x0B-\x1F]') @@ -26,10 +26,10 @@ class XMLRPCRequestHandler(SimpleXMLRPCDispatcher): def list_methods(self, request): return [x['name'] for x in self.get_public_methods()] - def handle_request(self, request): + def handle_request(self): def dispatch(method_name, params): - return self.funcs[method_name](request, *params) - response = self._marshaled_dispatch(request.data, dispatch) + return self.funcs[method_name](*params) + response = self._marshaled_dispatch(ctx.request.data, dispatch) return Response(response, mimetype='text/xml') def get_public_methods(self): diff --git a/lodgeit/urls.py b/lodgeit/urls.py index e70b41b..83f149d 100644 --- a/lodgeit/urls.py +++ b/lodgeit/urls.py @@ -10,30 +10,28 @@ """ from werkzeug.routing import Map, Rule -urlmap = Map( - [ - # paste interface - Rule('/', endpoint='pastes/new_paste'), - Rule('/show//', endpoint='pastes/show_paste'), - Rule('/raw//', endpoint='pastes/raw_paste'), - Rule('/compare/', endpoint='pastes/compare_paste'), - Rule('/compare///', endpoint='pastes/compare_paste'), - Rule('/unidiff///', endpoint='pastes/unidiff_paste'), - Rule('/tree//', endpoint='pastes/show_tree'), +urlmap = Map([ + # paste interface + Rule('/', endpoint='pastes/new_paste'), + Rule('/show//', endpoint='pastes/show_paste'), + Rule('/raw//', endpoint='pastes/raw_paste'), + Rule('/compare/', endpoint='pastes/compare_paste'), + Rule('/compare///', endpoint='pastes/compare_paste'), + Rule('/unidiff///', endpoint='pastes/unidiff_paste'), + Rule('/tree//', endpoint='pastes/show_tree'), - # paste list - Rule('/all/', endpoint='pastes/show_all'), - Rule('/all//', endpoint='pastes/show_all'), + # paste list + Rule('/all/', endpoint='pastes/show_all'), + Rule('/all//', endpoint='pastes/show_all'), - # xmlrpc - Rule('/xmlrpc/', endpoint='xmlrpc/handle_request'), + # xmlrpc + Rule('/xmlrpc/', endpoint='xmlrpc/handle_request'), - # static pages - Rule('/about/', endpoint='static/about'), - Rule('/help/', endpoint='static/help'), - Rule('/help//', endpoint='static/help'), + # static pages + Rule('/about/', endpoint='static/about'), + Rule('/help/', endpoint='static/help'), + Rule('/help//', endpoint='static/help'), - # colorscheme - Rule('/colorscheme/', endpoint='pastes/set_colorscheme'), - ], -) + # colorscheme + Rule('/colorscheme/', endpoint='pastes/set_colorscheme'), +]) diff --git a/lodgeit/utils.py b/lodgeit/utils.py new file mode 100644 index 0000000..b29c867 --- /dev/null +++ b/lodgeit/utils.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" + lodgeit.utils + ~~~~~~~~~~~~~ + + Serveral utilities used by LodgeIt. + + :copyright: 2007 by Christopher Grebs. + :license: BSD +""" +import time +from hashlib import sha1 +from random import random +from types import ModuleType +from werkzeug import Local, LocalManager, LocalProxy, BaseRequest, \ + BaseResponse +from werkzeug.routing import NotFound, RequestRedirect + +from jinja import Environment, PackageLoader + + +#: context locals +_local = Local() +_local_manager = LocalManager([_local]) + +#: fake module type for easy access to context locals +ctx = ModuleType('ctx') +ctx.__doc__ = 'Module that holds all context locals' + +#: some variables for direct access to the context locals +ctx.application = LocalProxy(_local, 'application') +ctx.request = LocalProxy(_local, 'request') + +#: jinja environment for all the templates +jinja_environment = Environment(loader=PackageLoader('lodgeit', 'views', + use_memcache=False, + auto_reload=False +)) + +def datetimeformat(): + """ + Helper filter for the template + """ + def wrapped(env, ctx, value): + return value.strftime('%Y-%m-%d %H:%M') + return wrapped + +jinja_environment.filters['datetimeformat'] = datetimeformat + + +def generate_user_hash(): + return sha1('%s|%s' % (random(), time.time())).hexdigest() + + +class Request(BaseRequest): + """ + Subclass of the `BaseRequest` object. automatically creates a new + `user_hash` and sets `first_visit` to `True` if it's a new user. + It also stores the engine and dbsession on it. + """ + charset = 'utf-8' + + def __init__(self, environ): + self.app = ctx.application + super(Request, self).__init__(environ) + + # check the user hash. an empty cookie is considered + # begin a new session. + self.user_hash = '' + self.first_visit = False + if 'user_hash' in self.cookies: + self.user_hash = self.cookies['user_hash'] + if not self.user_hash: + self.user_hash = generate_user_hash() + self.first_visit = True + + def bind_to_context(self): + ctx.request = self + + +class Response(BaseResponse): + """ + Subclass the response object for later extension. + """ + charset = 'utf-8' + + +class PageNotFound(NotFound): + """ + Internal exception used to tell the application to show the + error page. + """ + + +def render_template(template_name, **tcontext): + """ + Render a template to a response. This automatically fetches + the list of new replies for the layout template. It also + adds the current request to the context. This is used for the + welcome message. + """ + from lodgeit.database import Paste + if ctx.request.method == 'GET': + tcontext['new_replies'] = Paste.fetch_replies() + tcontext['request'] = ctx.request + t = jinja_environment.get_template(template_name) + return Response(t.render(tcontext), mimetype='text/html; charset=utf-8') + + +def redirect(url, code=302): + """ + Redirect to somewhere. Returns a nice response object. + """ + return Response('Page Moved to %s' % url, + headers=[('Location', url), + ('Content-Type', 'text/plain')], + status=code) diff --git a/runlodgeit.py b/runlodgeit.py index 92f5937..6daf90d 100644 --- a/runlodgeit.py +++ b/runlodgeit.py @@ -1,6 +1,37 @@ from lodgeit.application import make_app +from lodgeit.utils import ctx +from lodgeit.database import db +from werkzeug import script from werkzeug.debug import DebuggedApplication from werkzeug.serving import run_simple +from werkzeug.utils import create_environ, run_wsgi_app -app = DebuggedApplication(make_app('sqlite:////tmp/lodgeit.db')) -run_simple('localhost', 7000, app, True) +dburi = 'sqlite:////tmp/lodgeit.db' + +def run_app(app, path='/'): + env = create_environ(path) + return run_wsgi_app(app, env) + +action_runserver = script.make_runserver( + lambda: make_app(dburi), + use_reloader=True) + +action_rundserver = script.make_runserver( + lambda: DebuggedApplication(make_app(dburi, True), evalex=True), + use_reloader=True) + +action_shell = script.make_shell( + lambda: { + 'app': make_app(dburi, False, True), + 'ctx': ctx, + 'db': db, + 'run_app': run_app + }, + ('\nWelcome to the interactive shell environment of LodgeIt!\n' + '\n' + 'You can use the following predefined objects: app, ctx, db.\n' + 'To run the application (creates a request) use *run_app*.') +) + +if __name__ == '__main__': + script.run()