some documentation and move login to /admin/
This commit is contained in:
parent
588a8c306b
commit
a753045f8b
10
README
10
README
@ -3,9 +3,9 @@ Isso – Ich schrei sonst
|
||||
|
||||
You love static blog generators (especially [Acrylamid][1] *cough*) and the
|
||||
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
|
||||
better use a comment system that is under your control. This is, were Isso
|
||||
comes into play.
|
||||
wrong with it, but if you care about the privacy of your audience you are
|
||||
better off with a comment system that is under your control. This is, were
|
||||
Isso comes into play.
|
||||
|
||||
[1]: https://github.com/posativ/acrylamid
|
||||
[2]: http://disqus.com/
|
||||
@ -24,7 +24,7 @@ Features/Roadmap
|
||||
- [x] simple JSON API, hence comments are JavaScript-only
|
||||
- [x] create comments and modify/delete within a time range as user
|
||||
- [ ] Ping/Trackback support
|
||||
- [w] simple admin interface
|
||||
- [x] simple admin interface
|
||||
- [w] easy integration, similar to Disqus
|
||||
- [ ] spam filtering using [http:bl][3]
|
||||
|
||||
@ -34,7 +34,7 @@ Development
|
||||
-----------
|
||||
|
||||
*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.
|
||||
|
||||
You'll need [2.6 ≤ python ≤ 2.7][4], [ender.js][5] and [YUI Compressor][6].
|
||||
|
@ -28,9 +28,10 @@ sys.setdefaultencoding('utf-8') # we only support UTF-8 and python 2.X :-)
|
||||
import io
|
||||
import os
|
||||
import json
|
||||
import locale
|
||||
import traceback
|
||||
|
||||
from optparse import OptionParser, make_option, SUPPRESS_HELP
|
||||
from optparse import OptionParser, make_option
|
||||
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
|
||||
@ -41,6 +42,9 @@ from isso.utils import determine, import_object, IssoEncoder
|
||||
_dumps = json.dumps
|
||||
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):
|
||||
|
||||
@ -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]]), [
|
||||
|
||||
# moderation panel
|
||||
('/', admin.login, ['HEAD', 'GET', 'POST']),
|
||||
('/admin/', admin.index, ['HEAD', 'GET', 'POST']),
|
||||
|
||||
# assets
|
||||
@ -86,7 +89,6 @@ class Isso(object):
|
||||
('/1.0/<(.+?):path>/<(int):id>', comment.get, ['HEAD', 'GET']),
|
||||
('/1.0/<(.+?):path>/<(int):id>', comment.modify, ['PUT', 'DELETE']),
|
||||
('/1.0/<(.+?):path>/<(int):id>/approve', comment.approve, 'PUT'),
|
||||
|
||||
('/1.0/<(.+?):path>', comment.get, 'GET'),
|
||||
])
|
||||
|
||||
@ -174,8 +176,6 @@ def main():
|
||||
make_option("--sqlite", dest="sqlite", metavar='FILE', default="/tmp/sqlite.db",
|
||||
help="use SQLite3 database"),
|
||||
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)
|
||||
@ -185,7 +185,7 @@ def main():
|
||||
print 'isso', __version__
|
||||
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) < 2:
|
||||
@ -195,7 +195,8 @@ def main():
|
||||
with io.open(args[1], encoding='utf-8') as fp:
|
||||
migrate.disqus(app.db, fp.read())
|
||||
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
from wsgiref.simple_server import make_server
|
||||
httpd = make_server('127.0.0.1', 8080, app, server_class=wsgi.ThreadedWSGIServer)
|
||||
httpd.serve_forever()
|
||||
|
@ -15,25 +15,20 @@ mako = TemplateLookup(directories=[join(dirname(__file__), 'templates')], input_
|
||||
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.form.getfirst('secret') == app.SECRET:
|
||||
return 301, '', {
|
||||
'Location': '/admin/',
|
||||
'Set-Cookie': setcookie('admin', app.signer.dumps('*'),
|
||||
max_age=app.MAX_AGE, path='/')
|
||||
}
|
||||
|
||||
return 200, render('login.mako').encode('utf-8'), {'Content-Type': 'text/html'}
|
||||
|
||||
|
||||
def index(app, environ, request):
|
||||
|
||||
max_age=app.MAX_AGE, path='/')}
|
||||
return 403, '', {}
|
||||
else:
|
||||
try:
|
||||
app.unsign(request.cookies.get('admin', ''))
|
||||
except (SignatureExpired, BadSignature):
|
||||
return 301, '', {'Location': '/'}
|
||||
return 200, render('login.mako').encode('utf-8'), {'Content-Type': 'text/html'}
|
||||
|
||||
ctx = {'app': app, 'request': request}
|
||||
return 200, render('admin.mako', **ctx).encode('utf-8'), {'Content-Type': 'text/html'}
|
||||
|
@ -14,7 +14,6 @@ from isso import json, models, utils, wsgi
|
||||
def create(app, environ, request, path):
|
||||
|
||||
if app.PRODUCTION and not utils.urlexists(app.HOST, path):
|
||||
print app.HOST, path
|
||||
return 400, 'URL does not exist', {}
|
||||
|
||||
try:
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
<!-- login form -->
|
||||
<div id="login">
|
||||
<form action="/" method="post">
|
||||
<form action="/admin/" method="post">
|
||||
<input name="secret" placeholder="secret" type="password" />
|
||||
<input type="submit" name="submit" value="Login" class="button" />
|
||||
</form>
|
||||
|
16
isso/wsgi.py
16
isso/wsgi.py
@ -21,6 +21,8 @@ from wsgiref.simple_server import WSGIServer
|
||||
|
||||
|
||||
class Request(object):
|
||||
"""A ``werkzeug.wrappers.Request``-like object but much less powerful.
|
||||
Fits exactly the needs of Isso."""
|
||||
|
||||
def __init__(self, environ):
|
||||
|
||||
@ -66,6 +68,14 @@ class Request(object):
|
||||
|
||||
|
||||
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]+'}
|
||||
|
||||
@ -94,7 +104,7 @@ class Rule(str):
|
||||
|
||||
kwargs = match.groupdict()
|
||||
for key, value in kwargs.items():
|
||||
for type in int, float:
|
||||
for type in int, float: # may convert false-positives
|
||||
try:
|
||||
kwargs[key] = type(value)
|
||||
break
|
||||
@ -105,6 +115,9 @@ class Rule(str):
|
||||
|
||||
|
||||
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 = {}
|
||||
root = abspath(root) + os.sep
|
||||
@ -133,6 +146,7 @@ def sendfile(filename, root, environ):
|
||||
|
||||
|
||||
def static(app, environ, request, directory, path):
|
||||
"""A view that returns the requested path from directory."""
|
||||
|
||||
try:
|
||||
return sendfile(path, join(dirname(__file__), directory), environ)
|
||||
|
Loading…
Reference in New Issue
Block a user