gae ables you to use frameworks such as django, tornado, web.py but i've been using "pure gae". if you've used django, or any other similar web framework you won't have any problems with sql. so in that project i could able to create someting similar to this;
class Entry(db.Model): user = db.UserProperty() title = db.StringProperty(required=True) slug = db.StringProperty(required=True) content = db.TextProperty(required=True) content_html = db.TextProperty(required=True) image = db.BlobProperty() imgth = db.BlobProperty() created = db.DateTimeProperty(auto_now_add=True) active = db.BooleanProperty(default=True)
thankfully gae's support for wsgi takes no effort.
app = webapp.WSGIApplication(
[
(r'^/about', AboutPage),
(r'^/img', ImageHandler),
(r'^/search', SearchPage),
(r'^/entry/([^/]+)', EntryPage),
(r'^/', Index),
],
debug=False
)
def main():
run_wsgi_app(app)
if __name__ == '__main__':
main()
to generate the pages i've created a BaseHandler class is similar to this.
PROJECTDIR = os.path.join(os.path.dirname(__file__), 'templates/')
class BaseHandler(webapp.RequestHandler):
def render(self, template_name, template_vals=None):
if not template_vals:
template_values = {}
template_name = os.path.join(PROJECTDIR, template_name)
self.response.out.write(template.render(template_name, template_values))
after this, generating the index is easy.
class Index(BaseHandler):
def get(self):
offset = int(self.request.get('offset', 0))
count = int(self.request.get('count', 25))
entries = Entry.all().filter('active = ', True).order('-created').fetch(count, offset)
self.response.headers['Cache-Control'] = 300
user = users.get_current_user()
template_vals = {
'user' : user,
'offset' : offset,
'count' : count,
'last_post' : offset+len(entries)-1,
'prev_offset' : max(0, offset-count),
'next_offset' : offset+count,
'entries' : entries
}
self.render('index.html', template_vals)
in the project we need image library to resize the images, and to generate thumbnails. that's why i've coded the following ImageHandler class.
HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
class ImageHandler(webapp.RequestHandler):
def get(self):
if self.request.get('t'):
entry = db.get(self.request.get('t'))
if entry.imgth:
last_modified = entry.created.strftime(HTTP_DATE_FORMAT)
self.response.headers['Last-Modified'] = last_modified
self.response.headers['Cache-Control'] = 86400
self.response.headers['Content-Type'] = 'image/png'
self.response.out.write(entry.imgth)
else:
self.response.headers['Content-Type'] = 'text/plain'
self.error(404)
self.response.out.write('404: Not Found')
elif self.request.get('i'):
entry = db.get(self.request.get('i'))
if entry.image:
last_modified = entry.created.strftime(HTTP_DATE_FORMAT)
self.response.headers['Last-Modified'] = last_modified
self.response.headers['Cache-Control'] = 86400
self.response.headers['Content-Type'] = 'image/png'
self.response.out.write(entry.image)
else:
self.response.headers['Content-Type'] = 'text/plain'
self.error(404)
self.response.out.write('404: Not Found')
else:
self.response.headers['Content-Type'] = 'text/plain'
self.error(404)
self.response.out.write('404: Not Found')
i must say that it looks pretty bad but it does the job for now. the thumbnails are created by looking at the key of an entry. i've also defined
last-modified and cache-controls headers. the entry urls are slugified but they can't be unique so unique keys are also used in generated urls. here's a simple url: http://application_name.appspot.com/entry/ag53YXJjcmFmdG5/slugified-title. so the EntryPage class is defined as follows;
class EntryPage(BaseHandler):
def get(self, key):
if not key:
self.redirect('/')
entry = db.Query(Entry).filter('active = ', True).filter('key = ', key).get()
if not entry:
self.error(404)
self.render('404.html')
else:
last_modified = entry.created.strftime(HTTP_DATE_FORMAT)
self.response.headers['Last-Modified'] = last_modified
self.response.headers['Cache-Control'] = 3600
comments = db.Query(Comment).filter('ekey = ', entry.eid).fetch(50, 0)
user = users.get_current_user()
template_vals = {
'user' : user,
'entry' : entry,
'comments' : comments,
}
self.render('entry.html', template_vals)
and of course there's an admin panel. it's pretty similar to IndexPage so i don't want to write it again but there's a slight difference which is
admin_required decorator which is defined as
def admin_required(func):
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))
else:
self.error(403)
self.response.out.write('403: Forbidden')
elif not users.is_current_user_admin():
self.error(403)
self.response.out.write('403: Forbidden')
else:
return func(self, *args, **kwargs)
return wrapper
i didn't mention anything that's stick to the project but this is the basic setup of a gae application.it's easy and quite amazing what you can with gae. there's no
/etc/init.d/whatever restart or anything else. just click the deploy button and that's it. impressive and very useful for python web developers(oh and java developers as well). however, i'd really wish they'd use mako templates by default.