Skip to content
Browse files

started to worki

  • Loading branch information...
0 parents commit c71b5253cfcd08ace7bc6cd3d464cea7d900dbcf Benjamin Golub committed Sep 14, 2009
2 .gitignore
@@ -0,0 +1,2 @@
+tornado
+app.yaml
171 blog.py
@@ -0,0 +1,171 @@
+import functools
+import os.path
+import re
+import tornado.web
+import tornado.wsgi
+import unicodedata
+import wsgiref.handlers
+
+from google.appengine.api import users
+from google.appengine.ext import db
+
+class Entry(db.Model):
+ author = db.UserProperty()
+ title = db.StringProperty(required=True)
+ slug = db.StringProperty(required=True)
+ body = db.TextProperty(required=True)
+ published = db.DateTimeProperty(auto_now_add=True)
+ updated = db.DateTimeProperty(auto_now=True)
+ tags = db.ListProperty(db.Category)
+
+def administrator(method):
+ @functools.wraps(method)
+ def wrapper(self, *args, **kwargs):
+ user = users.get_current_user()
+ if not user:
+ if self.request.method == "GET":
+ self.redirect(users.create_login_url(self.request.uri))
+ return
+ raise tornado.web.HTTPError(403)
+ elif not users.is_current_user_admin():
+ raise tornado.web.HTTPError(403)
+ else:
+ return method(self, *args, **kwargs)
+ return wrapper
+
+
+class BaseHandler(tornado.web.RequestHandler):
+ def get_current_user(self):
+ user = users.get_current_user()
+ if user:
+ user.administrator = users.is_current_user_admin()
+ return user
+
+ def get_integer_argument(self, name, default):
+ try:
+ return int(self.get_argument(name, default))
+ except (TypeError, ValueError):
+ return default
+
+ def render_string(self, template_name, **kwargs):
+ return tornado.web.RequestHandler.render_string(self, template_name,
+ users=users, **kwargs)
+
+
+class HomeHandler(BaseHandler):
+ def get(self):
+ start = self.get_integer_argument("start", 0)
+ entries = db.Query(Entry).order("-published").fetch(limit=5,
+ offset=start)
+ if not entries and start > 0:
+ self.redirect("/")
+ return
+ next = max(start - 5, 0)
+ previous = start + 5 if len(entries) == 5 else None
+ self.render("home.html", entries=entries, start=start, next=next,
+ previous=previous)
+
+
+class ArchiveHandler(BaseHandler):
+ def get(self):
+ entries = db.Query(Entry).order("-published")
+ self.render("archive.html", entries=entries)
+
+
+class ComposeHandler(BaseHandler):
+ @administrator
+ def get(self):
+ key = self.get_argument("key", None)
+ try:
+ entry = Entry.get(key) if key else None
+ except db.BadKeyError:
+ entry = None
+ self.render("compose.html", entry=entry)
+
+ @administrator
+ def post(self):
+ key = self.get_argument("key", None)
+ if key:
+ try:
+ entry = Entry.get(key)
+ except db.BadKeyError:
+ self.redirect("/")
+ return
+ entry.title = self.get_argument("title")
+ entry.body = self.get_argument("body")
+ else:
+ title = self.get_argument("title")
+ slug = unicodedata.normalize("NFKD", title).encode(
+ "ascii", "ignore")
+ slug = re.sub(r"[^\w]+", " ", slug)
+ slug = "-".join(slug.lower().strip().split())
+ if not slug:
+ slug = "entry"
+ original_slug = slug
+ while db.Query(Entry).filter("slug = ", slug).get():
+ slug = original_slug + "-" + uuid.uuid4().hex[:2]
+ entry = Entry(
+ author=self.current_user,
+ title=title,
+ slug=slug,
+ body=self.get_argument("body"),
+ )
+ entry.put()
+ self.redirect("/e/" + entry.slug)
+
+
+class EntryHandler(BaseHandler):
+ def get(self, slug):
+ entry = db.Query(Entry).filter("slug =", slug).get()
+ if not entry:
+ raise tornado.web.HTTPError(404)
+ self.render("entry.html", entry=entry)
+
+
+class EntryModule(tornado.web.UIModule):
+ def render(self, entry, show_comments=False):
+ self.show_comments = show_comments
+ self.show_count = not show_comments
+ return self.render_string("modules/entry.html", entry=entry,
+ show_comments=show_comments)
+
+ def embedded_javascript(self):
+ if self.show_count:
+ return self.render_string("disquscount.js")
+ return None
+
+ def javascript_files(self):
+ if self.show_comments:
+ return ["http://disqus.com/forums/benjamingolub/embed.js"]
+ return None
+
+
+class RecentEntriesModule(tornado.web.UIModule):
+ def render(self):
+ entries = db.Query(Entry).order("-published").fetch(limit=5)
+ return self.render_string("modules/recententries.html", entries=entries)
+
+
+settings = {
+ "blog_title": "Benjamin Golub's Blog",
+ "debug": os.environ.get("SERVER_SOFTWARE", "").startswith("Development/"),
+ "template_path": os.path.join(os.path.dirname(__file__), "templates"),
+ "ui_modules": {
+ "Entry": EntryModule,
+ "RecentEntries": RecentEntriesModule,
+ },
+ "xsrf_cookies": True,
+}
+
+application = tornado.wsgi.WSGIApplication([
+ (r"/", HomeHandler),
+ (r"/archive", ArchiveHandler),
+ (r"/compose", ComposeHandler),
+ (r"/e/([\w-]+)", EntryHandler),
+], **settings)
+
+def main():
+ wsgiref.handlers.CGIHandler().run(application)
+
+if __name__ == "__main__":
+ main()
12 index.yaml
@@ -0,0 +1,12 @@
+indexes:
+
+- kind: Entry
+ properties:
+ - name: published
+ direction: desc
+
+- kind: Entry
+ properties:
+ - name: tags
+ - name: published
+ direction: desc
22 static/css/admin.css
@@ -0,0 +1,22 @@
+.compose .field {
+ margin-bottom: 5px;
+}
+
+.compose .title,
+.compose .submit {
+ font-weight: bold;
+}
+
+.compose .title {
+ font-size: 20pt;
+}
+
+.compose .title,
+.compose .body {
+ width: 100%;
+}
+
+.compose .body {
+ height: 500px;
+ line-height: 16pt;
+}
112 static/css/base.css
@@ -0,0 +1,112 @@
+body {
+ background-color: white;
+ margin: 0;
+}
+
+body,
+input,
+textarea {
+ font-family: Arial, sans-serif;
+ font-size: 12pt;
+}
+
+h2,
+h3,
+h4 {
+ font-size: 12pt;
+ margin: 0;
+}
+
+td {
+ vertical-align: top;
+}
+
+img {
+ border: 0;
+}
+
+a {
+ color: #00c;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+ul {
+ list-style: none;
+ padding: 0;
+}
+
+#header {
+ background-color: #3b5998;
+ padding: 8px;
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+#header a {
+ color: white;
+}
+
+#content {
+ width: 100%;
+}
+
+#sidebar {
+ display: block;
+ width: 250px;
+}
+
+#sidebar .box {
+ margin-bottom: 10px;
+}
+
+#sidebar h3 {
+ margin-bottom: 3px;
+}
+
+#sidebar ul {
+ margin: 0;
+}
+
+#content,
+#sidebar,
+#footer {
+ padding: 30px;
+}
+
+.entry h1 {
+ margin-top: 0;
+}
+
+.entry h1 a {
+ color: black;
+ text-decoration: none;
+}
+
+.entry {
+ margin-bottom: 10px;
+}
+
+.entry .date {
+ margin-top: 3px;
+}
+
+.entry .body {
+ margin-top: 10px;
+}
+
+pre,
+blockquote {
+ border: 1px solid silver;
+ background-color: #f5f5f5;
+ padding: 0.5em;
+ margin: 2em;
+}
+
+pre {
+ overflow: auto;
+ color: #007000;
+}
14 templates/archive.html
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+
+{% block title %}{{ _("Archive") }} - {{ escape(handler.settings["blog_title"]) }}{% end %}
+
+{% block content %}
+ <h2>{{ _("Archive") }}</h2>
+ <ul>
+ {% for entry in entries %}
+ <li>
+ <a href="/e/{{ entry.slug }}">{{ escape(entry.title) }}</a>
+ </li>
+ {% end %}
+ </ul>
+{% end %}
62 templates/base.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title>{% block title %}{{ escape(handler.settings["blog_title"]) }}{% end %}</title>
+ <link rel="stylesheet" href="/static/css/base.css" type="text/css"/>
+ {% block head %}{% end %}
+ </head>
+ <body>
+ <div id="header">
+ <div class="title">
+ <a href="/">{{ escape(handler.settings["blog_title"]) }}</a>
+ </div>
+ </div>
+ <table>
+ <tr>
+ <td id="content">
+ {% block content %}{% end %}
+ </td>
+ <td id="sidebar">
+ <div class="box">
+ <h3>{{ _("Links") }}</h3>
+ <ul>
+ <li><a href="/archive">{{ _("Archive") }}</a></li>
+ <li><a href="mailto:benjamin.golub@gmail.com">{{ _("Email") }}</a>
+ <li><a href="http://facebook.com/bgolub">{{ _("Facebook") }}</a>
+ <li><a href="http://friendfeed.com/bgolub">{{ _("FriendFeed") }}</a>
+ </ul>
+ </div>
+ {{ modules.RecentEntries() }}
+ </td>
+ </tr>
+ </table>
+ <div id="footer">
+ &copy;{{ datetime.datetime.utcnow().year }} Benjamin Golub
+ - <a href="/about">About me</a>
+ - <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons license</a>
+ -
+ {% if current_user %}
+ <a href="{{ users.create_logout_url(request.uri) }}">{{ _("Sign out") }}</a>
+ {% else %}
+ <a href="{{ users.create_login_url(request.uri) }}">{{ _("Sign in") }}</a>
+ {% end %}
+ </div>
+ {% block bottom %}{% end %}
+ {% if not handler.settings.get("debug") %}
+ <script type="text/javascript">
+ document.write(unescape("%3Cscript src='" + ((document.location.protocol == "https:") ? "https://ssl." : "http://www.") + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+ </script>
+ <script type="text/javascript">
+ //<![CDATA[
+
+ try {
+ var pageTracker = _gat._getTracker("UA-1337900-1");
+ pageTracker._trackPageview();
+ } catch(e) {}
+
+ //]]>
+ </script>
+ {% end %}
+ </body>
+</html>
24 templates/compose.html
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+
+{% block head %}
+ <link rel="stylesheet" href="/static/css/admin.css" type="text/css"/>
+{% end %}
+
+{% block content %}
+ <form action="{{ request.path }}" method="post" class="compose">
+ {{ xsrf_form_html() }}
+ {% if entry %}
+ <input type="hidden" name="key" value="{{ str(entry.key()) }}"/>
+ {% end %}
+ <div class="field">
+ <input name="title" type="text" class="title" value="{{ escape(entry.title) if entry else "" }}"/>
+ </div>
+ <div class="field">
+ <textarea name="body" class="body">{{ escape(entry.body) if entry else "" }}</textarea>
+ </div>
+ <div>
+ <input type="submit" class="submit" value="{{ _("Save changes") if entry else _("Publish") }}"/>
+ <a href="{{ "/e/" + entry.slug if entry else "/" }}">{{ _("Cancel") }}</a>
+ </div>
+ </form>
+{% end %}
10 templates/disquscount.js
@@ -0,0 +1,10 @@
+(function() {
+ var links = document.getElementsByTagName('a');
+ var query = '?';
+ for (var i = 0; i < links.length; i++) {
+ if (links[i].href.indexOf('#disqus_thread') >= 0) {
+ query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&';
+ }
+ }
+ document.write('<' + 'script type="text/javascript" src="http://disqus.com/forums/benjamingolub/get_num_replies.js' + query + '"></' + 'script>');
+})();
7 templates/entry.html
@@ -0,0 +1,7 @@
+{% extends "base.html" %}
+
+{% block title %}{{ escape(entry.title) }} - {{ escape(handler.settings["blog_title"]) }}{% end %}
+
+{% block content %}
+ {{ modules.Entry(entry, show_comments=True) }}
+{% end %}
7 templates/home.html
@@ -0,0 +1,7 @@
+{% extends base.html %}
+
+{% block content %}
+ {% for entry in entries %}
+ {{ modules.Entry(entry) }}
+ {% end %}
+{% end %}
17 templates/modules/entry.html
@@ -0,0 +1,17 @@
+<div class="entry">
+ <h1><a href="/e/{{ entry.slug }}">{{ escape(entry.title) }}</a></h1>
+ <div class="date">{{ locale.format_date(entry.published, full_format=True, shorter=True) }}</div>
+ <div class="body">{{ entry.body }}</div>
+ {% if not show_comments %}
+ <div>
+ <a href="/entry/{{ entry.slug }}#disqus_thread">Comments</a>
+ </div>
+ {% else %}
+ <div id="disqus_thread"></div>
+ {% end %}
+ {% if current_user and current_user.administrator %}
+ <div style="margin-top: 10px;">
+ <a href="/compose?key={{ str(entry.key()) }}">{{ _("Edit") }}</a>
+ </div>
+ {% end %}
+</div>
10 templates/modules/recententries.html
@@ -0,0 +1,10 @@
+<div class="box">
+ <h3>{{ _("Recent Entries") }}</h3>
+ <ul>
+ {% for entry in entries %}
+ <li>
+ <a href="/e/{{ entry.slug }}">{{ escape(entry.title) }}</a>
+ </li>
+ {% end %}
+ </ul>
+</div>

0 comments on commit c71b525

Please sign in to comment.
Something went wrong with that request. Please try again.