updated to sqlalchemy 0.4, moved and cleaned up the code
This commit is contained in:
parent
4b0df72c0c
commit
3399c3b322
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 '<pre class="syntax">%s</pre>' % 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])
|
||||
)
|
||||
})
|
||||
|
@ -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."""
|
||||
|
@ -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):
|
||||
|
@ -10,30 +10,28 @@
|
||||
"""
|
||||
from werkzeug.routing import Map, Rule
|
||||
|
||||
urlmap = Map(
|
||||
[
|
||||
# paste interface
|
||||
Rule('/', endpoint='pastes/new_paste'),
|
||||
Rule('/show/<int:paste_id>/', endpoint='pastes/show_paste'),
|
||||
Rule('/raw/<int:paste_id>/', endpoint='pastes/raw_paste'),
|
||||
Rule('/compare/', endpoint='pastes/compare_paste'),
|
||||
Rule('/compare/<int:new_id>/<int:old_id>/', endpoint='pastes/compare_paste'),
|
||||
Rule('/unidiff/<int:new_id>/<int:old_id>/', endpoint='pastes/unidiff_paste'),
|
||||
Rule('/tree/<int:paste_id>/', endpoint='pastes/show_tree'),
|
||||
urlmap = Map([
|
||||
# paste interface
|
||||
Rule('/', endpoint='pastes/new_paste'),
|
||||
Rule('/show/<int:paste_id>/', endpoint='pastes/show_paste'),
|
||||
Rule('/raw/<int:paste_id>/', endpoint='pastes/raw_paste'),
|
||||
Rule('/compare/', endpoint='pastes/compare_paste'),
|
||||
Rule('/compare/<int:new_id>/<int:old_id>/', endpoint='pastes/compare_paste'),
|
||||
Rule('/unidiff/<int:new_id>/<int:old_id>/', endpoint='pastes/unidiff_paste'),
|
||||
Rule('/tree/<int:paste_id>/', endpoint='pastes/show_tree'),
|
||||
|
||||
# paste list
|
||||
Rule('/all/', endpoint='pastes/show_all'),
|
||||
Rule('/all/<int:page>/', endpoint='pastes/show_all'),
|
||||
# paste list
|
||||
Rule('/all/', endpoint='pastes/show_all'),
|
||||
Rule('/all/<int:page>/', 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/<topic>/', endpoint='static/help'),
|
||||
# static pages
|
||||
Rule('/about/', endpoint='static/about'),
|
||||
Rule('/help/', endpoint='static/help'),
|
||||
Rule('/help/<topic>/', endpoint='static/help'),
|
||||
|
||||
# colorscheme
|
||||
Rule('/colorscheme/', endpoint='pastes/set_colorscheme'),
|
||||
],
|
||||
)
|
||||
# colorscheme
|
||||
Rule('/colorscheme/', endpoint='pastes/set_colorscheme'),
|
||||
])
|
||||
|
117
lodgeit/utils.py
Normal file
117
lodgeit/utils.py
Normal file
@ -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)
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user