better admin 'theme', 304 Not Modified support and minor improvements

pull/16/head
posativ 12 years ago
parent 5968a977ab
commit 65c2fce636

@ -1,7 +1,7 @@
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
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

@ -54,7 +54,8 @@ class Isso(object):
MAX_AGE = 15 * 60
HTTP_STATUS_CODES = {
200: 'Ok', 201: 'Created', 202: 'Accepted', 301: 'Moved Permanently',
200: 'Ok', 201: 'Created', 202: 'Accepted',
301: 'Moved Permanently', 304: 'Not Modified',
400: 'Bad Request', 404: 'Not Found', 403: 'Forbidden',
500: 'Internal Server Error',
}
@ -73,13 +74,16 @@ 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, ['GET', 'POST']),
('/admin/', admin.index, ['GET', 'POST']),
('/', admin.login, ['HEAD', 'GET', 'POST']),
('/admin/', admin.index, ['HEAD', 'GET', 'POST']),
# assets
('/<(static|js):directory>/<(.+?):path>', wsgi.static, ['HEAD', 'GET']),
# comment API, note that the client side quotes the URL, but this is
# actually unnecessary. PEP 333 aka WSGI always unquotes PATH_INFO.
('/1.0/<(.+?):path>/new', comment.create, 'POST'),
('/1.0/<(.+?):path>/<(int):id>', comment.get, 'GET'),
('/1.0/<(.+?):path>/new', comment.create,'POST'),
('/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'),
@ -117,11 +121,11 @@ class Isso(object):
if code == 404:
try:
code, body, headers = wsgi.sendfile(environ['PATH_INFO'], os.getcwd())
code, body, headers = wsgi.sendfile(environ['PATH_INFO'], os.getcwd(), environ)
except (IOError, OSError):
try:
path = environ['PATH_INFO'].rstrip('/') + '/index.html'
code, body, headers = wsgi.sendfile(path, os.getcwd())
code, body, headers = wsgi.sendfile(path, os.getcwd(), environ)
except (IOError, OSError):
pass
@ -181,7 +185,7 @@ def main():
print 'isso', __version__
sys.exit(0)
app = Isso({'SQLITE': options.sqlite, 'PRODUCTION': options.production, 'MODERATION': False})
app = Isso({'SQLITE': options.sqlite, 'PRODUCTION': options.production, 'MODERATION': True})
if len(args) > 0 and args[0] == 'import':
if len(args) < 2:

@ -3,13 +3,15 @@
# Copyright 2012, Martin Zimmermann <info@posativ.org>. All rights reserved.
# License: BSD Style, 2 clauses. see isso/__init__.py
from os.path import join, dirname
from mako.lookup import TemplateLookup
from itsdangerous import SignatureExpired, BadSignature
from isso.wsgi import setcookie
mako = TemplateLookup(directories=['isso/templates'], input_encoding='utf-8')
mako = TemplateLookup(directories=[join(dirname(__file__), 'templates')], input_encoding='utf-8')
render = lambda template, **context: mako.get_template(template).render_unicode(**context)
@ -19,7 +21,7 @@ def login(app, environ, request):
if request.form.getfirst('secret') == app.SECRET:
return 301, '', {
'Location': '/admin/',
'Set-Cookie': setcookie('session-admin', app.signer.dumps('*'),
'Set-Cookie': setcookie('admin', app.signer.dumps('*'),
max_age=app.MAX_AGE, path='/')
}
@ -29,7 +31,7 @@ def login(app, environ, request):
def index(app, environ, request):
try:
app.unsign(request.cookies.get('session-admin', ''))
app.unsign(request.cookies.get('admin', ''))
except (SignatureExpired, BadSignature):
return 301, '', {'Location': '/'}

@ -1,9 +1,9 @@
function initialize() {
$('article > footer > a').forEach(function(item) {
$('div.buttons > a').forEach(function(item) {
var node = $(item).parent().parent()[0]
var node = $(item).parent().parent().parent().parent()[0]
var path = node.getAttribute("data-path");
var id = node.getAttribute("data-id");

@ -27,58 +27,83 @@
<%def name="make(comment)">
<article class="isso" data-path="${quote(comment.path)}" data-id="${comment.id}">
<header>
<!-- <span class="title"><a href="${comment.path}">${comment.path}</a></span> -->
<span class="created">${strftime('%a %d %B %Y', gmtime(comment.created))}</span>
<span class="author">
% if comment.website:
<a href="${comment.website}">${comment.author}</a>
% else:
${comment.author}
% endif
</span>
<span class="email">${comment.email}</span>
</header>
<div class="text">
${app.markup.convert(comment.text)}
<article class="isso column grid_12" data-path="${quote(comment.path)}" data-id="${comment.id}">
<div class="row">
<div class="column grid_9">
<header>
<span class="title"><a href="${comment.path}">${comment.path}</a></span>
</header>
<div class="text">
${app.markup.convert(comment.text)}
</div>
</div>
<div class="column grid_3 options">
<ul>
<li>${strftime('%d. %B %Y um %H:%M', gmtime(comment.created))}</li>
<li>von <em>${comment.author}</em></li>
% if comment.website:
<li><a href="${comment.website}">${comment.website}</a></li>
% endif
</ul>
<div class="row buttons">
<a href="#" class="red button column grid_1">Delete</a>
% if comment.pending:
<a href="#" class="green button column grid_1">Approve</a>
% endif
</div>
</div>
</div>
<footer>
% if comment.pending:
<a href="#">Approve</a> |
% endif
<a href="#">Delete</a>
</footer>
</article>
</%def>
<h1>Dashboard</h1>
<div class="row pending red">
<div class="column grid_9">
<h2>Pending</h2>
</div>
<div class="column grid_3">
<span class="limit">
[ <a href="?${query(pendinglimit=10)}">10</a>
| <a href="?${query(pendinglimit=20)}">20</a>
| <a href="?${query(pendinglimit=100000)}">All</a> ]
</span>
</div>
<div>
<h2>Pending</h2>
<span class="limit">
[ <a href="?${query(pendinglimit=10)}">10</a>
| <a href="?${query(pendinglimit=20)}">20</a>
| <a href="?${query(pendinglimit=100000)}">All</a> ]
</span>
</div>
<div class="row">
% for comment in app.db.recent(limit=get('pendinglimit', int), mode=2):
${make(comment)}
% endfor
</div>
<div>
<h2 class="recent">Recent</h2>
<span class="limit">
[<a href="?${query(recentlimit=10)}">10</a>
| <a href="?${query(recentlimit=20)}">20</a>
| <a href="?${query(recentlimit=100000)}">All</a>]
</span>
<div class="row recent green">
<div class="column grid_9">
<h2>Recent</h2>
</div>
<div class="column grid_3">
<span class="limit">
[ <a href="?${query(recentlimit=10)}">10</a>
| <a href="?${query(recentlimit=20)}">20</a>
| <a href="?${query(recentlimit=100000)}">All</a> ]
</span>
</div>
</div>
<div class="row">
% for comment in app.db.recent(limit=get('recentlimit', int) or 20, mode=5):
${make(comment)}
% endfor
</div>
<footer class="row">
<p><a href="https://github.com/posativ/isso">Isso</a> Ich schrei sonst!</p>
</footer>

@ -8,16 +8,116 @@
</script>
<link rel="stylesheet" type="text/css" href="/static/style.css" />
<style>
body {
/* ================ */
/* = The 1Kb Grid = */ /* 12 columns, 60 pixels each, with 20 pixel gutter */
/* ================ */
.grid_1 { width: 60px; }
.grid_2 { width: 140px; }
.grid_3 { width: 220px; }
.grid_4 { width: 300px; }
.grid_5 { width: 380px; }
.grid_6 { width: 460px; }
.grid_7 { width: 540px; }
.grid_8 { width: 620px; }
.grid_9 { width: 700px; }
.grid_10 { width: 780px; }
.grid_11 { width: 860px; }
.grid_12 { width: 940px; }
.column {
margin: 0 10px;
overflow: hidden;
float: left;
display: inline;
}
.row {
width: 960px;
margin: 0 auto;
overflow: hidden;
}
.row .row {
margin: 0 -10px;
width: auto;
display: inline-block;
}
* {
margin: 0;
padding: 0;
}
body {
font-size: 1em;
line-height: 1.4;
margin: 0 auto;
width: 960px;
margin: 20px 0 0 0;
background: url(/static/simple-clouds.jpg) no-repeat center center fixed;
}
body > h1 {
text-align: center;
padding: 8px 0 8px 0;
background-color: yellowgreen;
}
article {
background-color: rgb(245, 245, 245);
box-shadow: 0px 0px 4px 0px;
border-radius: 2px;
}
article header {
font-size: 1.1em;
}
article .options {
margin: 8px;
padding-bottom: 40px
}
article .text, article header {
padding: 8px 16px 8px 16px;
}
.text p {
margin-bottom: 10px;
text-align: justify;
}
.recent, .pending {
padding: 10px 40px 10px 40px;
box-shadow: 0px 0px 2px 0px;
border-radius: 4px;
margin: 2px auto 2px auto;
}
.green {
background-color: #B7D798;
}
.red {
background-color: #D79998;
}
body > footer {
border-top: 1px solid #ccc;
padding-top: 8px;
text-align: center;
}
.button {
padding-top: 10px;
padding: 5px;
border: 1px solid #666;
color: #000;
text-decoration: none;
}
.buttons {
padding-top: 10px;
}
<%block name="style" />

@ -9,7 +9,8 @@
#login {
margin: 280px 270px 0 270px;
padding: 30px;
background-color: #DDD;
background-color: rgb(245, 245, 245);
box-shadow: 0px 0px 4px 0px;
border-radius: 8px;
text-align: center;
}

@ -10,6 +10,10 @@ import tempfile
import urlparse
import mimetypes
from time import gmtime, mktime, strftime, strptime, timezone
from email.utils import parsedate
from os.path import join, dirname, abspath
from urllib import quote
from Cookie import SimpleCookie
from SocketServer import ThreadingMixIn
@ -100,11 +104,11 @@ class Rule(str):
return kwargs
def sendfile(filename, root):
def sendfile(filename, root, environ):
headers = {}
root = os.path.abspath(root) + os.sep
filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
root = abspath(root) + os.sep
filename = abspath(join(root, filename.strip('/\\')))
if not filename.startswith(root):
return 403, '', headers
@ -115,10 +119,27 @@ def sendfile(filename, root):
stats = os.stat(filename)
headers['Content-Length'] = str(stats.st_size)
headers['Last-Modified'] = strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime(stats.st_mtime))
ims = environ.get('HTTP_IF_MODIFIED_SINCE')
if ims:
ims = parsedate(ims)
if ims is not None and mktime(ims) - timezone >= stats.st_mtime:
headers['Date'] = strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime())
return 304, '', headers
return 200, io.open(filename, 'rb'), headers
def static(app, environ, request, directory, path):
try:
return sendfile(path, join(dirname(__file__), directory), environ)
except (OSError, IOError):
return 404, '', {}
def setcookie(name, value, **kwargs):
return '; '.join([quote(name, '') + '=' + quote(value, '')] +
[k.replace('_', '-') + '=' + str(v) for k, v in kwargs.iteritems()])

@ -12,9 +12,10 @@ version = re.search("__version__ = '([^']+)'",
setup(
name='isso',
version=version,
author='posativ',
author='Martin Zimmermann',
author_email='info@posativ.org',
packages=find_packages(),
include_package_data=True,
zip_safe=True,
url='https://github.com/posativ/isso/',
license='BSD revised',

Loading…
Cancel
Save