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 -

    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, /loginand "everything else." All three, which is typical, will be processed by the same script main.py, however, the settings are different - /statsand /loginrequire 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 controllersand create an instance webapp.WSGIApplicationthat 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 MainHandlerin 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.

    gaehabr

    /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 Visitorand 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 /loginmarked 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!

    Also popular now: