Google AppEngine From The Beginning: The Controller
We are moving forward with the speed of a jet engine, and while the hara-humans are reading and comprehending the first and second parts of the article, I am writing a sequel with the speed of a machine gun. This time it’s about the heart of any web application -
Some time ago, we already identified several URLs in
As we can see, three types of URLs are defined
It is rather concise. That is, it is extremely concise, but at the same time it is very important - all it does is import the content
As can be seen from the code above, this handler will be used for all requests that came to the "main page" of the application - namely, "/". Its code is below:
Fundamentally, this code is no different from the code
Let me remind you the picture with the structure of our application: As follows from the description of the page , the user will be redirected to it automatically and also automatically redirected to the page . What is happening on this page? To do this, we need to consider two classes at once.
This may not be the best architectural solution, however, for illustrative purposes, we declare a class and inherit it from
The described approach uses the Datastore Query , which starts with “all” objects of this type, and then gradually “refines” the final data set by calling
Now that we have such a useful class, we can easily describe our handler:
As you can see from the code, the handler receives the Visitor instance associated with the current user (perhaps creates it), increases the number of visits by one, and saves the instance. After that, the user is redirected to the main page without unnecessary words. You no longer need to check the user, since the URL is
The code of the last handler is extremely simple. It is so simple that it suggests that something obviously should be there:
Indeed, there is something else. This something is called Presentation, it uses Django templates and will be described in the next part of our article ;-) I
remind you that the full source code can be viewed in SVN , and I'm waiting for your questions and comments!
PS Think, please, how to upload source codes with backlight in Habr - otherwise I'll fill in all the pictures!
Controller
Some time ago, we already identified several URLs in
app.yaml
- it’s time to figure out how to make the application correctly "respond" to them. This is how our mappings look like:# $ Id: app.yaml 4 2010-01-25 12: 14: 48Z sigizmund $ application: helloworld version: 1 runtime: python api_version: 1 handlers: - url: / (stats | login) script: main.py login: required - url:. * script: main.py
As we can see, three types of URLs are defined
/stats
, /login
and "everything else." All three, which is typical, will be processed by the same script main.py
, however, the settings are different - /stats
and /login
require an active user session, while for the rest this is not necessary. Let's look at the contents of the script main.py
:#! / usr / bin / env python '' ' $ Id: main.py 4 2010-01-25 12: 14: 48Z sigizmund $ '' ' import controller from google.appengine.ext import webapp from google.appengine.ext.webapp import util from google.appengine.api import users def main (): application = webapp.WSGIApplication ([('/', controller.DefaultRequestHandler), ('/ stats', controller.StatsRequestController), ('/ login', controller.LoginController)], debug = True) util.run_wsgi_app (application) if __name__ == '__main__': main ()
It is rather concise. That is, it is extremely concise, but at the same time it is very important - all it does is import the content
controllers
and create an instance webapp.WSGIApplication
that matches the request URLs and the corresponding handlers. These handlers are usually convenient to put in a separate package in order to separate the service code from the real code, which, in fact, determines the behavior of the application. Consider these handlers in turn.DefaultRequestHandler - default handler
As can be seen from the code above, this handler will be used for all requests that came to the "main page" of the application - namely, "/". Its code is below:
class DefaultRequestHandler (webapp.RequestHandler): '' ' Handles default requests - checks whether user is logged in; if it is - saves an information about his visit in the database. '' ' def get (self): user = users.get_current_user () page = None if not user: page = view.StartPage (self.request) else: page = view.WelcomePage (self.request) page.render (self.response.out)
Fundamentally, this code is no different from the code
MainHandler
in the first part , except that it does not generate the contents of the page on its own, but uses some auxiliary classes. At this stage, we will not consider what they are doing - for us it is enough to know that they generate HTML that meets our requirements - that is, it offers users who have not done so to log in and welcomes those who successfully coped with it.Login Handler - Between Two Pages
Let me remind you the picture with the structure of our application: As follows from the description of the page , the user will be redirected to it automatically and also automatically redirected to the page . What is happening on this page? To do this, we need to consider two classes at once.
/login
/
class LoggedInRequestHandler (webapp.RequestHandler): def currentVisitor (self): user = users.get_current_user () # we shouldn't check user, as / login and / stats specifies # login: required in app.yaml q = model.Visitor.all () q.filter ('user =', user) qr = q.fetch (2) if len (qr) == 0: u = model.Visitor () elif len (qr)> 1: # something is horribly wrong here, it shouldn't happen # but it still could logging.error ("Duplicating user% s in datastore"% user.nickname ()) raise Exception ("Duplicating user% s in datastore"% user.nickname ()) else: u = qr [0] self.currentVisitor = u return u
This may not be the best architectural solution, however, for illustrative purposes, we declare a class and inherit it from
webapp.RequestHandler
. As you can see, the described class does not define a method get
- that is, as an independent developer, it will be pretty useless. All he does is provide a method currentVisitor()
that either retrieves from the Datastore or creates a new instance Visitor
and returns it. Consider this code in more detail. The described approach uses the Datastore Query , which starts with “all” objects of this type, and then gradually “refines” the final data set by calling
filter()
andancestor()
. The above example is very simple, but it shows almost everything that is needed to extract the necessary record from Datastore; Of course, in real applications this query will probably be much more complicated. Now that we have such a useful class, we can easily describe our handler:
class LoginController (LoggedInRequestHandler): '' ' We use this controller just for handling the login event '' ' def get (self): u = self.currentVisitor () u.hits = u.hits + 1 u.put () self.redirect ('/')
As you can see from the code, the handler receives the Visitor instance associated with the current user (perhaps creates it), increases the number of visits by one, and saves the instance. After that, the user is redirected to the main page without unnecessary words. You no longer need to check the user, since the URL is
/login
marked as requiring a mandatory login (that is, if the user tries to log in without authentication, he will be automatically redirected to the login page).StatsRequestController - statistics on request
The code of the last handler is extremely simple. It is so simple that it suggests that something obviously should be there:
class StatsRequestController (LoggedInRequestHandler): def get (self): u = self.currentVisitor () page = view.StatsPage (self.request, u) page.render (self.response.out)
Indeed, there is something else. This something is called Presentation, it uses Django templates and will be described in the next part of our article ;-) I
remind you that the full source code can be viewed in SVN , and I'm waiting for your questions and comments!
PS Think, please, how to upload source codes with backlight in Habr - otherwise I'll fill in all the pictures!