Tornado framework blog module analysis and use
- 2020-04-02 13:16:35
- OfStack
#!/usr/bin/env python
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import markdown
import os.path
import re
import torndb
import tornado.auth
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import unicodedata
from tornado.options import define, options
# Define some general configuration information, such as database connection information, port information
define("port", default=8888, help="run on the given port", type=int)
define("mysql_host", default="127.0.0.1:3306", help="blog database host")
define("mysql_database", default="blog", help="blog database name")
define("mysql_user", default="root", help="blog database user")
define("mysql_password", default="sa123", help="blog database password")
# define Application Information, it's inheritance tornado.web.Application the
class Application(tornado.web.Application):
# __init__ Automatic function call
def __init__(self):
# Here is the url The corresponding controller, and then the corresponding class, handles the logic inside
handlers = [
(r"/", HomeHandler),
(r"/archive", ArchiveHandler),
(r"/feed", FeedHandler),
(r"/entry/([^/]+)", EntryHandler),
(r"/compose", ComposeHandler),
(r"/auth/login", AuthLoginHandler),
(r"/auth/logout", AuthLogoutHandler),
]
# Settings such as blog title, template directory, static file directory, xsrf , whether to debug or not
settings = dict(
blog_title=u"Tornado Blog",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
ui_modules={"Entry": EntryModule},
xsrf_cookies=True,
cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
login_url="/auth/login",
debug=True,
)
# And then call tornado.web.Application Of the class __init__ Function load in
tornado.web.Application.__init__(self, handlers, **settings)
# Have one global connection to the blog DB across all handlers
# Database connection information
self.db = torndb.Connection(
host=options.mysql_host, database=options.mysql_database,
user=options.mysql_user, password=options.mysql_password)
# Base class, inherited from tornado.web.RequestHandler All subsequent classes inherit from this class
class BaseHandler(tornado.web.RequestHandler):
# Attribute decorator, so db The function becomes a property for later use
@property
def db(self):
return self.application.db
# Gets the current user
def get_current_user(self):
user_id = self.get_secure_cookie("blogdemo_user")
if not user_id: return None
return self.db.get("SELECT * FROM authors WHERE id = %s", int(user_id))
# Home page
class HomeHandler(BaseHandler):
def get(self):
#query Query many columns
entries = self.db.query("SELECT * FROM entries ORDER BY published "
"DESC LIMIT 5")
if not entries:
#redirect Redirect to one url
self.redirect("/compose")
return
#render Render a template followed by parameters
self.render("home.html", entries=entries)
class EntryHandler(BaseHandler):
def get(self, slug):
#get I get a value
entry = self.db.get("SELECT * FROM entries WHERE slug = %s", slug)
#raise Triggers an error message that must be followed by a type
if not entry: raise tornado.web.HTTPError(404)
self.render("entry.html", entry=entry)
class ArchiveHandler(BaseHandler):
def get(self):
entries = self.db.query("SELECT * FROM entries ORDER BY published "
"DESC")
self.render("archive.html", entries=entries)
class FeedHandler(BaseHandler):
def get(self):
entries = self.db.query("SELECT * FROM entries ORDER BY published "
"DESC LIMIT 10")
self.set_header("Content-Type", "application/atom+xml")
self.render("feed.xml", entries=entries)
class ComposeHandler(BaseHandler):
# A decorator
@tornado.web.authenticated
def get(self):
id = self.get_argument("id", None)
entry = None
if id:
entry = self.db.get("SELECT * FROM entries WHERE id = %s", int(id))
self.render("compose.html", entry=entry)
@tornado.web.authenticated
def post(self):
id = self.get_argument("id", None)
title = self.get_argument("title")
text = self.get_argument("markdown")
html = markdown.markdown(text)
if id:
entry = self.db.get("SELECT * FROM entries WHERE id = %s", int(id))
if not entry: raise tornado.web.HTTPError(404)
slug = entry.slug
#execute It means to execute
self.db.execute(
"UPDATE entries SET title = %s, markdown = %s, html = %s "
"WHERE id = %s", title, text, html, int(id))
else:
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"
while True:
e = self.db.get("SELECT * FROM entries WHERE slug = %s", slug)
if not e: break
slug += "-2"
self.db.execute(
"INSERT INTO entries (author_id,title,slug,markdown,html,"
"published) VALUES (%s,%s,%s,%s,%s,UTC_TIMESTAMP())",
self.current_user.id, title, slug, text, html)
self.redirect("/entry/" + slug)
class AuthLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
@tornado.web.asynchronous
def get(self):
if self.get_argument("openid.mode", None):
self.get_authenticated_user(self.async_callback(self._on_auth))
return
self.authenticate_redirect()
# Here we define a function to call from above
def _on_auth(self, user):
if not user:
raise tornado.web.HTTPError(500, "Google auth failed")
author = self.db.get("SELECT * FROM authors WHERE email = %s",
user["email"])
if not author:
# Auto-create first author
any_author = self.db.get("SELECT * FROM authors LIMIT 1")
if not any_author:
author_id = self.db.execute(
"INSERT INTO authors (email,name) VALUES (%s,%s)",
user["email"], user["name"])
else:
self.redirect("/")
return
else:
author_id = author["id"]
self.set_secure_cookie("blogdemo_user", str(author_id))
self.redirect(self.get_argument("next", "/"))
class AuthLogoutHandler(BaseHandler):
def get(self):
self.clear_cookie("blogdemo_user")
#get_argument In order to obtain next The value of the parameter, which defaults to "/"
self.redirect(self.get_argument("next", "/"))
class EntryModule(tornado.web.UIModule):
def render(self, entry):
return self.render_string("modules/entry.html", entry=entry)
# The entry function
def main():
tornado.options.parse_command_line()
# Create a server
http_server = tornado.httpserver.HTTPServer(Application())
# Listen on port
http_server.listen(options.port)
# Start the service
tornado.ioloop.IOLoop.instance().start()
# Call entry
if __name__ == "__main__":
main()
To conclude:
1) several demos are provided in tornado framework to create an application in this form
2) for each controller function, either, there can only be 2 external functions, one is get and the other is post
3) the database has 3 invocation modes, query, get and exec
4) use the get_argument function to get the value of the argument
5) redirect with the redirect function
6) all functions are properties of this class, all are called with self
7) render the template with the render function