Rhys ! avatar Rhys ! committed 719c947

moved app into postr directory

Comments (0)

Files changed (36)

 
 handlers:
 - url: /site.css
-  static_files: static/site.css
-  upload: static/site.css
+  static_files: postr/static/site.css
+  upload: postr/static/site.css
 - url: /create.*
-  script: run.py
+  script: postr/run.py
   login: required
 - url: /.*
-  script: run.py
+  script: postr/run.py
Add a comment to this file

controllers/__init__.py

Empty file removed.

controllers/create.py

-from rgframe.handler import errors
-
-from handler import Handler
-from templates import templates
-from models import Post as PostModel
-from users import current_user
-
-class Post(Handler):
-    def get(self):
-        template = templates.create
-        template.error = self.arg('error')
-        self.render(template)
-    def post(self):
-        content = self.arg('content')
-        topic = self.arg('topic')
-        user = current_user(self.request)
-        if content and topic:
-            post = PostModel(author=user, topic=topic, content=content)
-            post.put()
-            raise errors.redirect('/post/%s' % post.key().id())
-        else:
-            raise errors.redirect('/create?error=empty')

controllers/home.py

-from handler import Handler
-from templates import templates
-
-class Home(Handler):
-    def get(self):
-        template = templates.home
-        self.render(template)

controllers/not_found.py

-from handler import Handler
-from templates import templates
-
-class FourOhFour(Handler):
-    def get(self):
-        raise self.nice404()

controllers/view.py

-from urllib import unquote
-
-from rgframe.handler import errors
-
-from handler import Handler
-from templates import templates
-from models import Post as PostModel
-from models import Author as AuthorModel
-
-recent_posts = PostModel.gql('ORDER BY post_time DESC LIMIT 5')
-posts_by_topic = PostModel.gql('WHERE lowertopic = :topic ORDER BY post_time DESC LIMIT 50')
-
-class Recent(Handler):
-    def get(self):
-        template = templates.list
-        template.of = "Recent posts"
-        template.query = recent_posts
-        self.render(template)
-
-class Topic(Handler):
-    def get(self, *groups):
-        g = unquote(groups[0]).lower()
-        posts_by_topic.bind(topic=g)
-        
-        template = templates.list
-        template.of = "Posts about %s" % g
-        template.query = posts_by_topic
-        self.render(template)
-
-class Author(Handler):
-    def get(self, *groups):
-        g = unquote(groups[0])
-        author = AuthorModel.get_by_key_name(g)
-        if not author:
-            author = AuthorModel.get_by_name(g)
-            if not author:
-                raise self.nice_404()
-
-        template = templates.list
-        template.of = "Posts by %s" % author.name
-        template.query = author.posts
-        self.render(template)
-
-class Post(Handler):
-    def get(self, *groups):
-        if groups[0].isdigit():
-            post = PostModel.get_by_id(int(groups[0]))
-            if not post:
-                raise self.nice_404()
-        else:
-            raise self.nice_404()
-
-        template = templates.view_post
-        template.post = post
-        self.render(template)

handler.py

-from rgframe.handler.http import Handler as HttpHandler
-from rgframe.handler import errors
-
-from users import current_user
-from templates import templates
-
-class Handler(HttpHandler):
-    @property
-    def client_is_ie(self):
-        return 'MSIE' in self.request.headers['User-Agent']
-
-    def render_template(self, template):
-        if not hasattr(self, 'user'):
-            self.user = current_user(self.request)
-        
-        template.user = self.user
-        template.client_ie = self.client_is_ie
-        
-        return template.substitute_attrs()
-
-    def render(self, template, type='application/xhtml+xml', cache=True, code=200):
-        if type == 'application/xhtml+xml' and self.client_is_ie:
-            type = 'text/html'
-        self.reply(code=code, data=self.render_template(template), headers={'Content-Type': type}, cache=cache)
-
-    def nice_404(self):
-        return errors.not_found(mimetype='text/html' if self.client_is_ie else 'application/xhtml+xml', message=self.render_template(templates.not_found))

models.py

-from google.appengine.ext import db
-
-from rgframe.datastore.model import Model
-
-class Author(Model):
-    name = db.StringProperty(required=True)
-    email = db.StringProperty()
-
-    @property
-    def posts(self):
-        posts_by_author.bind(author=self)
-        return posts_by_author
-    
-    @classmethod
-    def get_by_email(cls, email):
-        author_by_email.bind(email=email)
-        return author_by_email.get()
-
-    @classmethod
-    def get_by_name(cls, name):
-        author_by_name.bind(name=name)
-        return author_by_name.get()
-    
-#    @property
-#    def comments(self):
-#        comments_by_author.bind(author=self)
-#        return comments_by_author
-
-class Post(Model):
-    post_time = db.DateTimeProperty(auto_now_add=True)
-    content = db.TextProperty(required=True)
-    author = db.ReferenceProperty(required=True, reference_class=Author)
-    topic = db.StringProperty(required=True)
-    lowertopic = db.StringProperty() # Automanaged
-    
-    def put(self):
-        self.lowertopic = self.topic.lower()
-        super(Post, self).put()
-    
-#    @property
-#    def comments(self):
-#        comments_by_post.bind(post=self)
-#        return comments_by_post
-
-#class Comment(Model):
-#    post_time = db.DateTimeProperty(auto_now_add=True)
-#    content = db.TextProperty(required=True)
-#    author = db.ReferenceProperty(required=True, reference_class=Author)
-#    post = db.ReferenceProperty(required=True, reference_class=Post)
-
-#
-# QUERIES
-#
-
-posts_by_author = Post.gql('WHERE author = :author')
-author_by_email = Author.gql('WHERE email = :email')
-author_by_name = Author.gql('WHERE name = :name')
-#comments_by_author = Comment.gql('WHERE author = :author')
-#comments_by_post = Comment.gql('WHERE post = :post')

Empty file added.

Add a comment to this file

postr/controllers/__init__.py

Empty file added.

postr/controllers/create.py

+from rgframe.handler import errors
+
+from ..handler import Handler
+from ..templates import templates
+from ..models import Post as PostModel
+from ..users import current_user
+
+class Post(Handler):
+    def get(self):
+        template = templates.create
+        template.error = self.arg('error')
+        self.render(template)
+    def post(self):
+        content = self.arg('content')
+        topic = self.arg('topic')
+        user = current_user(self.request)
+        if content and topic:
+            post = PostModel(author=user, topic=topic, content=content)
+            post.put()
+            raise errors.redirect('/post/%s' % post.key().id())
+        else:
+            raise errors.redirect('/create?error=empty')

postr/controllers/home.py

+from ..handler import Handler
+from ..templates import templates
+
+class Home(Handler):
+    def get(self):
+        template = templates.home
+        self.render(template)

postr/controllers/not_found.py

+from ..handler import Handler
+from ..templates import templates
+
+class FourOhFour(Handler):
+    def get(self):
+        raise self.nice404()

postr/controllers/view.py

+from urllib import unquote
+
+from rgframe.handler import errors
+
+from ..handler import Handler
+from ..templates import templates
+from ..models import Post as PostModel
+from ..models import Author as AuthorModel
+
+recent_posts = PostModel.gql('ORDER BY post_time DESC LIMIT 5')
+posts_by_topic = PostModel.gql('WHERE lowertopic = :topic ORDER BY post_time DESC LIMIT 50')
+
+class Recent(Handler):
+    def get(self):
+        template = templates.list
+        template.of = "Recent posts"
+        template.query = recent_posts
+        self.render(template)
+
+class Topic(Handler):
+    def get(self, *groups):
+        g = unquote(groups[0]).lower()
+        posts_by_topic.bind(topic=g)
+        
+        template = templates.list
+        template.of = "Posts about %s" % g
+        template.query = posts_by_topic
+        self.render(template)
+
+class Author(Handler):
+    def get(self, *groups):
+        g = unquote(groups[0])
+        author = AuthorModel.get_by_key_name(g)
+        if not author:
+            author = AuthorModel.get_by_name(g)
+            if not author:
+                raise self.nice_404()
+
+        template = templates.list
+        template.of = "Posts by %s" % author.name
+        template.query = author.posts
+        self.render(template)
+
+class Post(Handler):
+    def get(self, *groups):
+        if groups[0].isdigit():
+            post = PostModel.get_by_id(int(groups[0]))
+            if not post:
+                raise self.nice_404()
+        else:
+            raise self.nice_404()
+
+        template = templates.view_post
+        template.post = post
+        self.render(template)
+from rgframe.handler.http import Handler as HttpHandler
+from rgframe.handler import errors
+
+from users import current_user
+from templates import templates
+
+class Handler(HttpHandler):
+    @property
+    def client_is_ie(self):
+        return 'MSIE' in self.request.headers['User-Agent']
+
+    def render_template(self, template):
+        if not hasattr(self, 'user'):
+            self.user = current_user(self.request)
+        
+        template.user = self.user
+        template.client_ie = self.client_is_ie
+        
+        return template.substitute_attrs()
+
+    def render(self, template, type='application/xhtml+xml', cache=True, code=200):
+        if type == 'application/xhtml+xml' and self.client_is_ie:
+            type = 'text/html'
+        self.reply(code=code, data=self.render_template(template), headers={'Content-Type': type}, cache=cache)
+
+    def nice_404(self):
+        return errors.not_found(mimetype='text/html' if self.client_is_ie else 'application/xhtml+xml', message=self.render_template(templates.not_found))
+from google.appengine.ext import db
+
+from rgframe.datastore.model import Model
+
+class Author(Model):
+    name = db.StringProperty(required=True)
+    email = db.StringProperty()
+
+    @property
+    def posts(self):
+        posts_by_author.bind(author=self)
+        return posts_by_author
+    
+    @classmethod
+    def get_by_email(cls, email):
+        author_by_email.bind(email=email)
+        return author_by_email.get()
+
+    @classmethod
+    def get_by_name(cls, name):
+        author_by_name.bind(name=name)
+        return author_by_name.get()
+    
+#    @property
+#    def comments(self):
+#        comments_by_author.bind(author=self)
+#        return comments_by_author
+
+class Post(Model):
+    post_time = db.DateTimeProperty(auto_now_add=True)
+    content = db.TextProperty(required=True)
+    author = db.ReferenceProperty(required=True, reference_class=Author)
+    topic = db.StringProperty(required=True)
+    lowertopic = db.StringProperty() # Automanaged
+    
+    def put(self):
+        self.lowertopic = self.topic.lower()
+        super(Post, self).put()
+    
+#    @property
+#    def comments(self):
+#        comments_by_post.bind(post=self)
+#        return comments_by_post
+
+#class Comment(Model):
+#    post_time = db.DateTimeProperty(auto_now_add=True)
+#    content = db.TextProperty(required=True)
+#    author = db.ReferenceProperty(required=True, reference_class=Author)
+#    post = db.ReferenceProperty(required=True, reference_class=Post)
+
+#
+# QUERIES
+#
+
+posts_by_author = Post.gql('WHERE author = :author')
+author_by_email = Author.gql('WHERE email = :email')
+author_by_name = Author.gql('WHERE name = :name')
+#comments_by_author = Comment.gql('WHERE author = :author')
+#comments_by_post = Comment.gql('WHERE post = :post')
+from rgframe.application import WebApp, run
+
+app = WebApp([('/', 'postr.controllers.home:Home'),
+              ('/recent', 'postr.controllers.view:Recent'),
+              ('/author/(.+)', 'postr.controllers.view:Author'),
+              ('/topic/(.+)', 'postr.controllers.view:Topic'),
+              ('/post/(.+)', 'postr.controllers.view:Post'),
+              ('/create', 'postr.controllers.create:Post'),
+              ('/.*', 'postr.controllers.not_found:FourOhFour')])
+
+def main():
+    run(app)
+
+if __name__ == '__main__':
+    main()

postr/static/site.css

+/* HTML 5 */
+article, section, header, nav, footer {
+    display: block;
+}
+
+/* GENERAL */
+
+html, body {
+    font-family: sans-serif;
+    width: 100%;
+    margin: 0;
+    padding: 0;
+    height: 100%;
+}
+#container {
+    min-height: 100%;
+    position: relative;
+}
+a {
+    color: blue;
+    text-decoration: none;
+}
+a:hover {
+    text-decoration: underline;
+}
+pre {
+    font-family: inherit;
+    margin: 0;
+    padding: 0;
+    border: 0;
+    white-space: -moz-pre-wrap; /* Mozilla, supported since 1999 */
+    white-space: -pre-wrap; /* Opera 4 - 6 */
+    white-space: -o-pre-wrap; /* Opera 7 */
+    white-space: pre-wrap; /* CSS3 - Text module (Candidate Recommendation) http://www.w3.org/TR/css3-text/#white-space */
+    word-wrap: break-word; /* IE 5.5+ */
+}
+.clear {
+    /* I hate IE. */
+    background: none;
+    border: 0;
+    clear: both;
+    display: block;
+    float: none;
+    font-size: 0;
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+    visibility: hidden;
+    width: 0;
+    height: 0;
+}
+
+/* HEADER */
+
+#container > header {
+    color: black;
+    border: 0;
+    margin: 0;
+    padding: 0;
+    border-bottom: 3px solid black;
+    background-color: #CCC;
+}
+h1, h2 {
+    text-align: left;
+    margin: 0;
+    margin-left: 6px;
+}
+h1 {
+    font-family: serif;
+    font-size: 1.5em;
+}
+#container > header h1 {
+    font-size: 2em;
+    display: inline;
+}
+#container > header a {
+    color: black;
+}
+nav {
+    float: right;
+    margin-right: 1em;
+    margin-top: 0.1em;
+    font-size: 90%;
+    text-align: right;
+}
+#bar {
+    float: right;
+    margin-top: 0.6em;
+    margin-right: 1em;
+}
+
+/* CONTENT */
+
+#main {
+    padding-bottom: 2em;
+}
+#main header {
+    margin-bottom: 3em;
+}
+#main > section, #main > div {
+    width: 80%;
+    margin: 0 auto;
+}
+cite {
+    font-weight: bold;
+}
+.topic {
+    font-weight: bold;
+}
+.post a {
+    color: black;
+}
+
+/* CREATE */
+textarea {
+    width: 100%;
+    min-height: 30em;
+    font-family: sans-serif;
+    border: 1px solid black;
+    margin: 0 0 1em;
+    padding: 0.2em;
+    display: block;
+}
+input[name="topic"] {
+    width: 30em;
+    font-family: sans-serif;
+    border: 1px solid black;
+    margin-left: 1em;
+}
+input[value="Post"] {
+    font-weight: bold;
+    float: right;
+    position: relative;
+    right: -0.6em;
+    margin: 0;
+    border: 1px solid black;
+}
+#error {
+    text-align: center;
+}
+#error span {
+    background-color: #CCC;
+}
+
+/* FOOTER */
+
+footer {
+    display: block;
+    position: absolute;
+    bottom: 0;
+    height: 1.75em;
+    border: 0;
+    border-top: 1px solid #999999;
+    padding: 0;
+    margin-top: 1em;
+    width: 100%;
+    font-size: 75%;
+    text-align: center;
+}

postr/templates/__init__.py

+from rgframe.template import TemplateDir
+
+templates = TemplateDir()

postr/templates/create.tmpl

+{{inherit "wrapper.tmpl"}}
+{{def title}}New post{{enddef}}
+            <header>
+                <h1>New post</h1>
+            </header>
+            <div>
+{{if error}}
+                <p id="error">
+    {{if error == 'empty'}}
+                    <span>Cannot have empty topic or content.</span>
+   {{endif}}
+                </p>
+{{endif}}
+                <form method="post" action="/create">
+                    <p><strong>Topic</strong>: <input type="text" name="topic" /></p>
+                    <p><textarea name="content" /></p>
+                    <p><input type="submit" value="Post" /></p>
+                </form>
+            </div>

postr/templates/home.tmpl

+{{inherit "wrapper.tmpl"}}
+{{def title}}Welcome{{enddef}}
+            <header>
+                <h1>Welcome</h1>
+            </header>
+            <div>
+                <p>Welcome to postr!</p>
+                <p>This project was created to provide a simple example of the use of <a href="http://bitbucket.org/rimmington/rgframe">rgframe</a> and <a href="http://code.google.com/appengine/">Google App Engine's</a> BigTable database.</p>
+                <p>The code is available under the GPLv3 license; grab it from <a href="http://bitbucket.org/rimmington/rgframe-example">Bitbucket</a>.</p>
+            </div>

postr/templates/list.tmpl

+{{inherit "wrapper.tmpl"}}
+{{def title}}{{of}}{{enddef}}
+            <header>
+                <h1>{{of}}</h1>
+            </header>
+{{for post in query}}
+            <div class="post">
+                <p><a href="/author/{{post.author.key().id_or_name() | url}}"><cite>{{post.author.name}}</cite></a> <a href="/post/{{post.key().id_or_name() | url}}">posted</a> about <a href="/topic/{{post.topic | url}}"><span class="topic">{{post.topic}}</span></a>:</p>
+                <blockquote>
+                    <pre>{{post.content}}</pre>
+                </blockquote>
+            </div>
+{{endfor}}

postr/templates/not_found.tmpl

+{{inherit "wrapper.tmpl"}}
+{{def title}}404{{enddef}}
+            <header>
+                <h1>404 Not Found</h1>
+            </header>
+            <div>
+                <p>Whatever you were looking for, it ain't here.</p>
+            </div>

postr/templates/view_post.tmpl

+{{inherit "wrapper.tmpl"}}
+{{def title}}Single post{{enddef}}
+            <header>
+                <h1>Single post</h1>
+            </header>
+            <div class="post">
+                <p><a href="/author/{{post.author.key().id_or_name() | url}}"><cite>{{post.author.name}}</cite></a> posted about <a href="/topic/{{post.topic | url}}"><span class="topic">{{post.topic}}</span></a>:</p>
+                <blockquote>
+                    <pre>{{post.content}}</pre>
+                </blockquote>
+            </div>

postr/templates/wrapper.tmpl

+{{if client_ie}}
+<!DOCTYPE html>
+<html lang="en">
+{{else}}
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+{{endif}}
+
+    <head>
+        <title>postr // {{title | html}}</title>
+        <meta charset="utf-8" />
+        <link rel="stylesheet"{{if client_ie}} type="text/css"{{endif}} href="/site.css" />
+{{if client_ie}}
+        <!--[if lt IE 9]>
+            <script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+        <![endif]-->
+{{endif}}
+    </head>
+
+    <body>
+    <div id="container">
+    	<header>
+            <h1><a href="/">postr</a></h1>
+            <nav>
+{{if user.logged_in}}
+                Salutations, <a href="{{user.uri}}">{{user.name}}</a>.<br />
+                <a href="{{user.logout_uri}}">Logout</a>
+{{else}}
+                <br />
+                <a href="{{user.login_uri}}">Login</a>
+{{endif}}
+            </nav>
+            <hr class="clear" />
+            <nav id="bar">
+                <a href="/recent">Recent posts</a> |
+                <a href="/create">New post</a>
+            </nav>
+        </header>
+        <article id="main">
+{{self.body.strip() | html}}
+        </article>
+        <footer>
+            &#169; Rhys ! 2011. Copyrights to posts and comments are held by their owners.
+        </footer>
+    </div>
+    </body>
+
+</html>
+from google.appengine.api import users
+
+from models import Author
+
+class Guest(object):
+    pass
+
+def current_user(request):
+    user = users.get_current_user()
+    if user:
+        author = Author.get_by_key_name(user.user_id())
+        if not author:
+            author = Author(key_name=user.user_id(), name=user.nickname(), email=user.email())
+            author.put()
+        
+        if author.name != user.nickname():
+            author.name = user.nickname()
+            author.put()
+        author.uri = '/author/%s' % author.key().name()
+        author.logged_in = True
+        author.logout_uri = users.create_logout_url(request.uri)
+
+    else:
+        author = Guest()
+        author.logged_in = False
+        author.login_uri = users.create_login_url(request.uri)
+
+    return author

run.py

-from rgframe.application import WebApp, run
-
-app = WebApp([('/', 'controllers.home:Home'),
-              ('/recent', 'controllers.view:Recent'),
-              ('/author/(.+)', 'controllers.view:Author'),
-              ('/topic/(.+)', 'controllers.view:Topic'),
-              ('/post/(.+)', 'controllers.view:Post'),
-              ('/create', 'controllers.create:Post'),
-              ('/.*', 'controllers.not_found:FourOhFour')])
-
-def main():
-    run(app)
-
-if __name__ == '__main__':
-    main()

static/site.css

-/* HTML 5 */
-article, section, header, nav, footer {
-    display: block;
-}
-
-/* GENERAL */
-
-html, body {
-    font-family: sans-serif;
-    width: 100%;
-    margin: 0;
-    padding: 0;
-    height: 100%;
-}
-#container {
-    min-height: 100%;
-    position: relative;
-}
-a {
-    color: blue;
-    text-decoration: none;
-}
-a:hover {
-    text-decoration: underline;
-}
-pre {
-    font-family: inherit;
-    margin: 0;
-    padding: 0;
-    border: 0;
-    white-space: -moz-pre-wrap; /* Mozilla, supported since 1999 */
-    white-space: -pre-wrap; /* Opera 4 - 6 */
-    white-space: -o-pre-wrap; /* Opera 7 */
-    white-space: pre-wrap; /* CSS3 - Text module (Candidate Recommendation) http://www.w3.org/TR/css3-text/#white-space */
-    word-wrap: break-word; /* IE 5.5+ */
-}
-.clear {
-    /* I hate IE. */
-    background: none;
-    border: 0;
-    clear: both;
-    display: block;
-    float: none;
-    font-size: 0;
-    margin: 0;
-    padding: 0;
-    overflow: hidden;
-    visibility: hidden;
-    width: 0;
-    height: 0;
-}
-
-/* HEADER */
-
-#container > header {
-    color: black;
-    border: 0;
-    margin: 0;
-    padding: 0;
-    border-bottom: 3px solid black;
-    background-color: #CCC;
-}
-h1, h2 {
-    text-align: left;
-    margin: 0;
-    margin-left: 6px;
-}
-h1 {
-    font-family: serif;
-    font-size: 1.5em;
-}
-#container > header h1 {
-    font-size: 2em;
-    display: inline;
-}
-#container > header a {
-    color: black;
-}
-nav {
-    float: right;
-    margin-right: 1em;
-    margin-top: 0.1em;
-    font-size: 90%;
-    text-align: right;
-}
-#bar {
-    float: right;
-    margin-top: 0.6em;
-    margin-right: 1em;
-}
-
-/* CONTENT */
-
-#main {
-    padding-bottom: 2em;
-}
-#main header {
-    margin-bottom: 3em;
-}
-#main > section, #main > div {
-    width: 80%;
-    margin: 0 auto;
-}
-cite {
-    font-weight: bold;
-}
-.topic {
-    font-weight: bold;
-}
-.post a {
-    color: black;
-}
-
-/* CREATE */
-textarea {
-    width: 100%;
-    min-height: 30em;
-    font-family: sans-serif;
-    border: 1px solid black;
-    margin: 0 0 1em;
-    padding: 0.2em;
-    display: block;
-}
-input[name="topic"] {
-    width: 30em;
-    font-family: sans-serif;
-    border: 1px solid black;
-    margin-left: 1em;
-}
-input[value="Post"] {
-    font-weight: bold;
-    float: right;
-    position: relative;
-    right: -0.6em;
-    margin: 0;
-    border: 1px solid black;
-}
-#error {
-    text-align: center;
-}
-#error span {
-    background-color: #CCC;
-}
-
-/* FOOTER */
-
-footer {
-    display: block;
-    position: absolute;
-    bottom: 0;
-    height: 1.75em;
-    border: 0;
-    border-top: 1px solid #999999;
-    padding: 0;
-    margin-top: 1em;
-    width: 100%;
-    font-size: 75%;
-    text-align: center;
-}

templates/__init__.py

-from rgframe.template import TemplateDir
-
-templates = TemplateDir()

templates/create.tmpl

-{{inherit "wrapper.tmpl"}}
-{{def title}}New post{{enddef}}
-            <header>
-                <h1>New post</h1>
-            </header>
-            <div>
-{{if error}}
-                <p id="error">
-    {{if error == 'empty'}}
-                    <span>Cannot have empty topic or content.</span>
-   {{endif}}
-                </p>
-{{endif}}
-                <form method="post" action="/create">
-                    <p><strong>Topic</strong>: <input type="text" name="topic" /></p>
-                    <p><textarea name="content" /></p>
-                    <p><input type="submit" value="Post" /></p>
-                </form>
-            </div>

templates/home.tmpl

-{{inherit "wrapper.tmpl"}}
-{{def title}}Welcome{{enddef}}
-            <header>
-                <h1>Welcome</h1>
-            </header>
-            <div>
-                <p>Welcome to postr!</p>
-                <p>This project was created to provide a simple example of the use of <a href="http://bitbucket.org/rimmington/rgframe">rgframe</a> and <a href="http://code.google.com/appengine/">Google App Engine's</a> BigTable database.</p>
-                <p>The code is available under the GPLv3 license; grab it from <a href="http://bitbucket.org/rimmington/rgframe-example">Bitbucket</a>.</p>
-            </div>

templates/list.tmpl

-{{inherit "wrapper.tmpl"}}
-{{def title}}{{of}}{{enddef}}
-            <header>
-                <h1>{{of}}</h1>
-            </header>
-{{for post in query}}
-            <div class="post">
-                <p><a href="/author/{{post.author.key().id_or_name() | url}}"><cite>{{post.author.name}}</cite></a> <a href="/post/{{post.key().id_or_name() | url}}">posted</a> about <a href="/topic/{{post.topic | url}}"><span class="topic">{{post.topic}}</span></a>:</p>
-                <blockquote>
-                    <pre>{{post.content}}</pre>
-                </blockquote>
-            </div>
-{{endfor}}

templates/not_found.tmpl

-{{inherit "wrapper.tmpl"}}
-{{def title}}404{{enddef}}
-            <header>
-                <h1>404 Not Found</h1>
-            </header>
-            <div>
-                <p>Whatever you were looking for, it ain't here.</p>
-            </div>

templates/view_post.tmpl

-{{inherit "wrapper.tmpl"}}
-{{def title}}Single post{{enddef}}
-            <header>
-                <h1>Single post</h1>
-            </header>
-            <div class="post">
-                <p><a href="/author/{{post.author.key().id_or_name() | url}}"><cite>{{post.author.name}}</cite></a> posted about <a href="/topic/{{post.topic | url}}"><span class="topic">{{post.topic}}</span></a>:</p>
-                <blockquote>
-                    <pre>{{post.content}}</pre>
-                </blockquote>
-            </div>

templates/wrapper.tmpl

-{{if client_ie}}
-<!DOCTYPE html>
-<html lang="en">
-{{else}}
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
-{{endif}}
-
-    <head>
-        <title>postr // {{title | html}}</title>
-        <meta charset="utf-8" />
-        <link rel="stylesheet"{{if client_ie}} type="text/css"{{endif}} href="/site.css" />
-{{if client_ie}}
-        <!--[if lt IE 9]>
-            <script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
-        <![endif]-->
-{{endif}}
-    </head>
-
-    <body>
-    <div id="container">
-    	<header>
-            <h1><a href="/">postr</a></h1>
-            <nav>
-{{if user.logged_in}}
-                Salutations, <a href="{{user.uri}}">{{user.name}}</a>.<br />
-                <a href="{{user.logout_uri}}">Logout</a>
-{{else}}
-                <br />
-                <a href="{{user.login_uri}}">Login</a>
-{{endif}}
-            </nav>
-            <hr class="clear" />
-            <nav id="bar">
-                <a href="/recent">Recent posts</a> |
-                <a href="/create">New post</a>
-            </nav>
-        </header>
-        <article id="main">
-{{self.body.strip() | html}}
-        </article>
-        <footer>
-            &#169; Rhys ! 2011. Copyrights to posts and comments are held by their owners.
-        </footer>
-    </div>
-    </body>
-
-</html>

users.py

-from google.appengine.api import users
-
-from models import Author
-
-class Guest(object):
-    pass
-
-def current_user(request):
-    user = users.get_current_user()
-    if user:
-        author = Author.get_by_key_name(user.user_id())
-        if not author:
-            author = Author(key_name=user.user_id(), name=user.nickname(), email=user.email())
-            author.put()
-        
-        if author.name != user.nickname():
-            author.name = user.nickname()
-            author.put()
-        author.uri = '/author/%s' % author.key().name()
-        author.logged_in = True
-        author.logout_uri = users.create_logout_url(request.uri)
-
-    else:
-        author = Guest()
-        author.logged_in = False
-        author.login_uri = users.create_login_url(request.uri)
-
-    return author
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.