some documentation and move login to /admin/

This commit is contained in:
posativ 2012-12-16 19:34:15 +01:00
parent 588a8c306b
commit a753045f8b
6 changed files with 40 additions and 31 deletions

10
README
View File

@ -3,9 +3,9 @@ Isso Ich schrei sonst
You love static blog generators (especially [Acrylamid][1] *cough*) and the You love static blog generators (especially [Acrylamid][1] *cough*) and the
only option to interact with the community is [Disqus][2]. There's nothing only option to interact with the community is [Disqus][2]. There's nothing
wrong with it, but if you care about the privacy of your audience you should wrong with it, but if you care about the privacy of your audience you are
better use a comment system that is under your control. This is, were Isso better off with a comment system that is under your control. This is, were
comes into play. Isso comes into play.
[1]: https://github.com/posativ/acrylamid [1]: https://github.com/posativ/acrylamid
[2]: http://disqus.com/ [2]: http://disqus.com/
@ -24,7 +24,7 @@ Features/Roadmap
- [x] simple JSON API, hence comments are JavaScript-only - [x] simple JSON API, hence comments are JavaScript-only
- [x] create comments and modify/delete within a time range as user - [x] create comments and modify/delete within a time range as user
- [ ] Ping/Trackback support - [ ] Ping/Trackback support
- [w] simple admin interface - [x] simple admin interface
- [w] easy integration, similar to Disqus - [w] easy integration, similar to Disqus
- [ ] spam filtering using [http:bl][3] - [ ] spam filtering using [http:bl][3]
@ -34,7 +34,7 @@ Development
----------- -----------
*Note:* This project is proudly made with the Not Invented Here syndrome, *Note:* This project is proudly made with the Not Invented Here syndrome,
instead of `werkzeug` or `bottle` it uses about 100 lines to manage WSGI instead of `werkzeug` or `bottle` it uses about 150 lines to manage WSGI
and instead of JQuery it uses ender.js. and instead of JQuery it uses ender.js.
You'll need [2.6 ≤ python ≤ 2.7][4], [ender.js][5] and [YUI Compressor][6]. You'll need [2.6 ≤ python ≤ 2.7][4], [ender.js][5] and [YUI Compressor][6].

View File

@ -28,9 +28,10 @@ sys.setdefaultencoding('utf-8') # we only support UTF-8 and python 2.X :-)
import io import io
import os import os
import json import json
import locale
import traceback import traceback
from optparse import OptionParser, make_option, SUPPRESS_HELP from optparse import OptionParser, make_option
from itsdangerous import URLSafeTimedSerializer from itsdangerous import URLSafeTimedSerializer
@ -41,6 +42,9 @@ from isso.utils import determine, import_object, IssoEncoder
_dumps = json.dumps _dumps = json.dumps
setattr(json, 'dumps', lambda obj, **kw: _dumps(obj, cls=IssoEncoder, **kw)) setattr(json, 'dumps', lambda obj, **kw: _dumps(obj, cls=IssoEncoder, **kw))
# set user's preferred locale, XXX conflicts with email.util.parse_date ... m(
# locale.setlocale(locale.LC_ALL, '')
class Isso(object): class Isso(object):
@ -74,7 +78,6 @@ class Isso(object):
lambda r: (wsgi.Rule(r[0]), r[1], r[2] if isinstance(r[2], list) else [r[2]]), [ lambda r: (wsgi.Rule(r[0]), r[1], r[2] if isinstance(r[2], list) else [r[2]]), [
# moderation panel # moderation panel
('/', admin.login, ['HEAD', 'GET', 'POST']),
('/admin/', admin.index, ['HEAD', 'GET', 'POST']), ('/admin/', admin.index, ['HEAD', 'GET', 'POST']),
# assets # assets
@ -86,7 +89,6 @@ class Isso(object):
('/1.0/<(.+?):path>/<(int):id>', comment.get, ['HEAD', 'GET']), ('/1.0/<(.+?):path>/<(int):id>', comment.get, ['HEAD', 'GET']),
('/1.0/<(.+?):path>/<(int):id>', comment.modify, ['PUT', 'DELETE']), ('/1.0/<(.+?):path>/<(int):id>', comment.modify, ['PUT', 'DELETE']),
('/1.0/<(.+?):path>/<(int):id>/approve', comment.approve, 'PUT'), ('/1.0/<(.+?):path>/<(int):id>/approve', comment.approve, 'PUT'),
('/1.0/<(.+?):path>', comment.get, 'GET'), ('/1.0/<(.+?):path>', comment.get, 'GET'),
]) ])
@ -174,8 +176,6 @@ def main():
make_option("--sqlite", dest="sqlite", metavar='FILE', default="/tmp/sqlite.db", make_option("--sqlite", dest="sqlite", metavar='FILE', default="/tmp/sqlite.db",
help="use SQLite3 database"), help="use SQLite3 database"),
make_option("--port", dest="port", default=8000, help="webserver port"), make_option("--port", dest="port", default=8000, help="webserver port"),
make_option("--debug", dest="production", action="store_false", default=True,
help=SUPPRESS_HELP),
] ]
parser = OptionParser(option_list=options) parser = OptionParser(option_list=options)
@ -185,7 +185,7 @@ def main():
print 'isso', __version__ print 'isso', __version__
sys.exit(0) sys.exit(0)
app = Isso({'SQLITE': options.sqlite, 'PRODUCTION': options.production, 'MODERATION': True}) app = Isso({'SQLITE': options.sqlite, 'MODERATION': True})
if len(args) > 0 and args[0] == 'import': if len(args) > 0 and args[0] == 'import':
if len(args) < 2: if len(args) < 2:
@ -195,7 +195,8 @@ def main():
with io.open(args[1], encoding='utf-8') as fp: with io.open(args[1], encoding='utf-8') as fp:
migrate.disqus(app.db, fp.read()) migrate.disqus(app.db, fp.read())
else: sys.exit(0)
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
httpd = make_server('127.0.0.1', 8080, app, server_class=wsgi.ThreadedWSGIServer) httpd = make_server('127.0.0.1', 8080, app, server_class=wsgi.ThreadedWSGIServer)
httpd.serve_forever() httpd.serve_forever()

View File

@ -15,25 +15,20 @@ mako = TemplateLookup(directories=[join(dirname(__file__), 'templates')], input_
render = lambda template, **context: mako.get_template(template).render_unicode(**context) render = lambda template, **context: mako.get_template(template).render_unicode(**context)
def login(app, environ, request): def index(app, environ, request):
if request.method == 'POST': if request.method == 'POST':
if request.form.getfirst('secret') == app.SECRET: if request.form.getfirst('secret') == app.SECRET:
return 301, '', { return 301, '', {
'Location': '/admin/', 'Location': '/admin/',
'Set-Cookie': setcookie('admin', app.signer.dumps('*'), 'Set-Cookie': setcookie('admin', app.signer.dumps('*'),
max_age=app.MAX_AGE, path='/') max_age=app.MAX_AGE, path='/')}
} return 403, '', {}
else:
return 200, render('login.mako').encode('utf-8'), {'Content-Type': 'text/html'}
def index(app, environ, request):
try: try:
app.unsign(request.cookies.get('admin', '')) app.unsign(request.cookies.get('admin', ''))
except (SignatureExpired, BadSignature): except (SignatureExpired, BadSignature):
return 301, '', {'Location': '/'} return 200, render('login.mako').encode('utf-8'), {'Content-Type': 'text/html'}
ctx = {'app': app, 'request': request} ctx = {'app': app, 'request': request}
return 200, render('admin.mako', **ctx).encode('utf-8'), {'Content-Type': 'text/html'} return 200, render('admin.mako', **ctx).encode('utf-8'), {'Content-Type': 'text/html'}

View File

@ -14,7 +14,6 @@ from isso import json, models, utils, wsgi
def create(app, environ, request, path): def create(app, environ, request, path):
if app.PRODUCTION and not utils.urlexists(app.HOST, path): if app.PRODUCTION and not utils.urlexists(app.HOST, path):
print app.HOST, path
return 400, 'URL does not exist', {} return 400, 'URL does not exist', {}
try: try:

View File

@ -32,7 +32,7 @@
<!-- login form --> <!-- login form -->
<div id="login"> <div id="login">
<form action="/" method="post"> <form action="/admin/" method="post">
<input name="secret" placeholder="secret" type="password" /> <input name="secret" placeholder="secret" type="password" />
<input type="submit" name="submit" value="Login" class="button" /> <input type="submit" name="submit" value="Login" class="button" />
</form> </form>

View File

@ -21,6 +21,8 @@ from wsgiref.simple_server import WSGIServer
class Request(object): class Request(object):
"""A ``werkzeug.wrappers.Request``-like object but much less powerful.
Fits exactly the needs of Isso."""
def __init__(self, environ): def __init__(self, environ):
@ -66,6 +68,14 @@ class Request(object):
class Rule(str): class Rule(str):
"""A quick and dirty approach to URL route creation. It uses the
following format:
- ``<(int):name>`` matches any integer, same for float
- ``<(.+?):path>`` matches any given regular expression
With ``Rule.match(url)`` you can test whether a route a) matches
and if so b) retrieve saved variables."""
repl = {'int': r'[0-9]+', 'float': r'\-?[0-9]+\.[0-9]+'} repl = {'int': r'[0-9]+', 'float': r'\-?[0-9]+\.[0-9]+'}
@ -94,7 +104,7 @@ class Rule(str):
kwargs = match.groupdict() kwargs = match.groupdict()
for key, value in kwargs.items(): for key, value in kwargs.items():
for type in int, float: for type in int, float: # may convert false-positives
try: try:
kwargs[key] = type(value) kwargs[key] = type(value)
break break
@ -105,6 +115,9 @@ class Rule(str):
def sendfile(filename, root, environ): def sendfile(filename, root, environ):
"""Return file object if found. This function is heavily inspired by
bottles's `static_file` function and uses the same mechanism to avoid
access to e.g. `/etc/shadow`."""
headers = {} headers = {}
root = abspath(root) + os.sep root = abspath(root) + os.sep
@ -133,6 +146,7 @@ def sendfile(filename, root, environ):
def static(app, environ, request, directory, path): def static(app, environ, request, directory, path):
"""A view that returns the requested path from directory."""
try: try:
return sendfile(path, join(dirname(__file__), directory), environ) return sendfile(path, join(dirname(__file__), directory), environ)