Advertisement
simbha

gae script app

Jul 2nd, 2016
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 23.56 KB | None | 0 0
  1.  
  2.  
  3.  
  4.  
  5. Gae python post form tutorial
  6.  
  7.  
  8.  
  9.  
  10.  
  11. http://www.rayfallan.com/webapp2jinja.html
  12.  
  13.  
  14. How-To Guide
  15.  
  16. webapp2 / jinja2 (in Google App Engine)
  17.  
  18.  
  19. level: beginner / intermediate Prerequisites: some Python, general basic knowledge of Google App Engine, must be able to install dependencies (webapp2, jinja2, see instructions below and online)
  20. Motivation for this tutorial document:
  21.  
  22. Using a web framework was a foreign concept for me. I searched through Google's tutorial documentation on Google App Engine and found that it was generally great, but when it came to webapp2 and jinja2 the authors mostly assumed I knew what I was doing.
  23. I did not.
  24. I went to the internet and spent some time looking for a starter tutorial that might let someone who had never programmed in the context of a framework before get started. Most of the tutorials I found assumed prior knowledge of the most popular Python web framework, Django. There wasn't much on getting started with webapp2. And even less on how to start on Jinja2.
  25.  
  26. So:
  27.  
  28. To start from the very beginning: what is a web framework?
  29. "A web framework is a collection of packages or modules which allows developers to write web applications or services without having to handle such low level details as protocols, sockets or process/thread management."
  30. --From the Python documentation at wiki.python.org/moin/WebFrameworks
  31.  
  32. MVC
  33.  
  34. Most of these web frameworks take as their base the MVC architectural pattern. This pattern divides applications into 3 interconnected parts: model, view, controller. The model is the 'brain', essentially the store of all the data and state information. The view is the presentation of this data to the user. The controller is what accepts the input from either the model or the view and converts its between the two, sort of a translator.
  35.  
  36. Web applications generally have one purpose: to save data and display data. The data is taken from the user via a view and stored in a database or other structure (the model). The data is manipulated and piped all over, through various manipulations according to the user's requests (the controller). Finally the data is presented back to the user in a view or series of views.
  37.  
  38. Intro to webApp2
  39.  
  40. webApp2 is bundled with the google app engine sdk. As I said in the beginning, the google app engine python tutorial has a high level explanation of the framework, but this seems to assume some prior knowledge. This tutorial is meant to take the beginner (like me) from nothing to a working understanding of webApp2 and jinja2.
  41.  
  42. First, a quick look at the helloWorld files from google's own GAE Python tutorials:
  43. app.yaml contains the following code:
  44.  
  45. application: [your-app-id]
  46. version: 1
  47. runtime: python27
  48. api_version: 1
  49. threadsafe: true
  50.  
  51. handlers:
  52. -url: /.*
  53. script: helloWorld.application
  54. helloWorld.py contains the following code:
  55.  
  56. import webapp2
  57.  
  58. class MainPage(webapp2.RequestHandler):
  59. def get(self):
  60. self.response.headers['Content-Type] = 'text/plain'
  61. self.response.write("Hello, World!")
  62.  
  63. application = webapp2.WSGIApplication([('/', MainPage),], debug=True)
  64. Take a look at penultimate line in the app.yaml file: -url: /.*. This is a regular expression sending all URLs to the application object in helloWorld.
  65. Also, look at the last line in helloWorld.py: ('/', MainPage). This is a mapping of particular URLs following the initial ./* leadin. So the web app's top level URL will map to '/' which calls the "MainPage" class and executes its 'get' method. Keep that in mind as things get more complicated.
  66.  
  67. WGSI
  68.  
  69.  
  70. What is this WSGIApplication()? From wikipedia (Web Server Gateway Interface article) WSGI is a low-level interface and standard between web servers and web frameworks. Before WGSI, new Python users had a difficult time because the choice of web framework limited the choice of useable servers.
  71. WGSI gives common ground to both servers and web frameworks and makes code generally more portable.
  72.  
  73. -----------------
  74.  
  75. So we have a portable interface, we have a web-framework. We know that the reason we want to use the web-framework is to hide all those low-level details that take us away from coding the functionality of our application. So what does our application (pretty much every web application) need to do? We want to get data from a user, do stuff to it and then send it back. Let's start with getting data from the user--that means we need to give them a form, some sort of a web page, and for that we need...
  76.  
  77. URL Routing
  78.  
  79. In helloWorld.py above the line:
  80.  
  81. application = webapp2.WSGIApplication([('/', MainPage),], debug=True)
  82.  
  83. this is called the Router; it can be expanded to:
  84.  
  85. application = webapp2.WSGIApplication()
  86. application.router.add((r'/', 'handlers.MainPage'))
  87.  
  88. Using this same syntax you can add more URL routes to the list. You could alternately just add them all into the array in the first example. They all take the form (regex, handler). In the expanded format you'll notice the 'r' before the '/'; this is the python regex signifier. Each add needs this.
  89. One final note: the syntax 'handlers.MainPage' is called a "Lazy Handler" which don't need to be initialized in the app, thus speeding up the startup.
  90.  
  91. Another example of more complicated URL routing comes from webapp-improved.appspt.com/guide/routing.html:
  92.  
  93. class HomeHandler(webapp2.RequestHandler):
  94. def get(self):
  95. self.response.write("this is the HomeHandler")
  96.  
  97. class ProductListHandler(webapp2.RequestHandler):
  98. def get(self):
  99. self.response.write("This is the ProductListHandler")
  100.  
  101. class ProductHandler(webapp2.RequestHandler):
  102. def get(self):
  103. self.response.write("This is the ProductHandler")
  104.  
  105. application = webapp2.WSGIApplication([
  106. (r'/', HomeHandler),
  107. (r'/products', ProductListHandler),
  108. (r'/products/(\d+)', ProductHandler),
  109. ])
  110. This defines three request handler objects and then sets the URL routes in the final application variable definition. Each of the regular expressions, r'/', r'/products', and r'/products/(\d+)' (this last one is a definition of a group so the handler will receive one or more digits) takes the user's URL string as input then routes it through one of these handlers or controllers (remember MVC?).
  111.  
  112. You can see that this URL routing is at the very heart of the control of the data coming in from the user. The page I got this last example code from (above) has an extensive list of possible variations and contexts for more specific URL routing. It's definitely worth a look.
  113.  
  114. Request Handlers (controllers)
  115.  
  116.  
  117. Request handlers are the pipes and gears in your machine. These are what you use to take the requests from the users and manipulate it according to your application's logic.
  118.  
  119. Request handlers can handle 'get' and 'post' requests. These come into the handlers by defining get(self, ...) or post(self, ...) the '...' are for the various inputs. If you need them, Request Handlers also support 'head', 'options', 'put', 'delete' and 'trace' requests. You probably won't need them right away, this is a beginner tutorial after all.
  120.  
  121. I personally find it easiest to store the inputs to my handlers as variables, so that I can use them as I would any other variable. say you have in your URL of a GET request:
  122. (the following is actually my own demo code...tada!)
  123. /test/name_demo.asp?fname=Ray&lname=Allan
  124.  
  125. class FullName(webapp2.RequestHandler):
  126. def get(self):
  127. try:
  128. fName = str(self.request.get('fname'))
  129. lName = str(self.request.get('lname'))
  130. #manipulation code follows...
  131. Now that you have those get values stored in strings, you can manipulate them and send them back out to the user via a view. The manipulation is up to you.
  132.  
  133. Now onto the user facing side of this tutorial. We'll be dipping back in and out of the webapp2 framework, but we need to decide how to present the information that our Request Handlers have produced.
  134.  
  135. You might just define some html for your page like the Google app engine tutorial does in its guestbook.py tutorial. This has a variable MAIN_PAGE_HTML which is a string of all the page's html. But this gets ugly and unwieldy fast. It seems like it would be better to just offload all those html pages into their own html files and stick all those files neatly into a folder. That's where jinja comes in.
  136.  
  137. Jinja2
  138.  
  139. First, I'll say that jinja2's got fine documentation. They don't really have any walkthoughs, just a laundry list of the API, but it is useful to check out this documentation here. There's also a very useful link to using jinja2 in GAE in the app engine documentation under the templates tab on the GAE python tutorial. Just a word of warning, just like the description of webapp2 in the GAE pages, the tutorial takes a very high-level look at things, and doesn't explain much. Also, you'll notice their approach is different from the one we'll be taking. I tried both and like mine better, (obviously).
  140.  
  141. You need to install jinja2 just like you would any other python egg/tarball. Just use pip or easy_install and follow the instructions on the jinja2 pages. This isn't a tutorial on jinja2, it's about how jinja2 can make your webapp2 work better and have neater code. And GAE loves jinja2.
  142.  
  143. First for google app engine, you should create a folder in your main app directory (where you have your app.yaml file and your [application-name].py file. Name this folder 'templates' and use it to keep all your jinja templates in. More on this folder in a bit...
  144.  
  145. These 'templates' are little more than html pages. I made a contacts app using jinja2 and my basic html page was simple html with a few jinja specific statements sprinkled in. I would start by just creating the html you'd like to see then jinj-ifying it.
  146.  
  147. Jinja2 in your python file
  148. in your [application-name].py code add the following:
  149. from jinja2 import Environment, PackageLoader
  150.  
  151. template_dir = os.path.join(os.path.dirname(__file__), 'templates')
  152. jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir),
  153. autoescape=True)
  154. The second line, template_dir, tells your app where to find that templates folder we just created (you know the one where you're conveniently going to store all your jinj-ified html files). The third line creates a 'template environment'. This is the magic of jinja2.
  155. To load a template from this environment you just have to call the get_template() method which then returns the loaded Template:
  156.  
  157. template = env.get_template('mytemplate.html')
  158.  
  159. to load a template with variables added, you just call:
  160.  
  161. print template.render('mytemplate.html', var1, var2, var3, ...)
  162.  
  163. The variables var1, var2, etc are magically loaded into the jinja2 spots saved for them in the jinj-ified html, 'mytemplate.html'. We will look at one of these shortly. Just remember when you render, you put the html page first, then the variables all in one by one after it.
  164. Now you might want to add the following class with its functions in your main python file. I found these in Udacity.com's great web development course. These functions come from the teacher of that course, Steve Huffman.
  165. class Handler(webapp2.RequestHandler):
  166. def write(self, *a, **kw):
  167. self.response.out.write(*a, **kw)
  168.  
  169. def render_str(self, template, **params):
  170. t = jinja_env.get_template(template)
  171. return t.render(params)
  172.  
  173. def render(self, template, **kw):
  174. self.write(self.render_str(template, **kw))
  175. These will make it easier for you to render your pages and get at the various templates you might create. Jinja2 html templates Here is a simplified template for a simple contacts list:
  176.  
  177. <!DOCTYPE html>
  178. {% autoescape true %}
  179. <html><head><title>/contact list/</title></head>
  180. <body><h1>/contact list</h1>
  181. <form method="post" enctype="multipart/form-data">
  182. <div>name:</div>
  183. <input type="text" name="name" value="{{name}}">
  184. <div>phone number:</div>
  185. <input type="text" name="phone" value="{{phone}}">
  186. </form>
  187. <hr>
  188. {% for contact in contacts %}
  189. <div class="contacts">
  190. <div class="contact-name">{{contact.name}}</div>
  191. {% if contact.phone %}
  192. <div class="contact-phone">{{contact-phone}}</div>
  193. {% else %}
  194. <div class="contact-phone">Phone Unknown</div>
  195. {% endif %}
  196. </div>
  197. {% endfor %}
  198. </body></html>
  199. {% end autoescape %}
  200.  
  201. There are some important points to notice. As I've said many times, this looks just like an html document with a little bit of jinja2 sprinkled in.
  202. In each of the form fields, you'll notice the {{}} with a variable name inside. When this template is rendered, remember all those variables you tossed into the args right after the template name? This is where they go. This particular example will autofill the last value entered, or leave it blank if no value yet exists for this variable. This is a great way of giving the user feedback when they are filling out forms.
  203. Also, after the <hr> you can see what looks like python code injected into the html. This takes an array that might be fed to the contacts variable in the render() function. For each of the elements in the array this will execute just as you might expect. You can feed as many contact objects in and the jinja template will render them in the format here, then enclose all of them in the "contacts" div. As you'd expect, the if statement will populate the contact-phone div only if the contact object has data in its phone field, otherwise (else) it will just fill this div with "Phone Unknown".
  204. Finally, one of the best parts of this jinja2 template is the simplicity of its autoescape functionality. Remember all the way back when we were setting up the jinja_environment and we set "autoescape=True"… here's where that comes in. This will do all of your html escaping for you. No need to guard against html/javaScript injection.
  205.  
  206. the last thing you'll need to do in order to be sure GAE is happy with your jinja2 templates, and code is to open up your app.yaml file and add:
  207.  
  208. libraries:
  209. -name: webapp2
  210. version: latest
  211. -name: jinja2
  212. version: latest
  213. (You might have already done this, just be sure to add both of these to your app.yaml.)
  214. All Together Now
  215.  
  216. Now we have a basic webapp2 file and know how to write (control) Request Handlers for each of our URLs. We know that we want to render our views with the jinja2 templates we will create. Learning how to store the data in the Google Datastore is beyond the scope of this tutorial, but is well covered in the GAE python tutorial. That Datastore will end up being your model from which you'll be drawing all your stored data. Now we just need to pull everything together.
  217. Here is a simple main.py (with an obvious hole).
  218.  
  219. import os
  220. import webapp2
  221. import jinja2
  222. from google.appengine.ext import db
  223.  
  224. template_dir = os.path.join(os.path.dirname(__file__), 'templates')
  225. jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir),
  226. autoescape = True)
  227.  
  228. class Handler(webapp2.RequestHandler):
  229. def write(self, *a, **kw):
  230. self.response.out.write(*a, **kw)
  231.  
  232. def render_str(self, template, **params):
  233. t = jinja_env.get_template(template)
  234. return t.render(params)
  235.  
  236. def render(self, template, **kw):
  237. self.write(self.render_str(template, **kw))
  238.  
  239. #the Contact class is for the datastore
  240. class Contact(db.Model):
  241. name = db.StringProperty()
  242. phone = db.PhoneNumberProperty()
  243.  
  244. class MainPage(Handler):
  245. #THIS IS HOMEWORK
  246. #
  247. #
  248.  
  249. app = webapp2.WSGIApplication([('/', MainPage)], debug=True)
  250. Homework For your homework, you'll get going with Google App Engine. Follow the python tutorial through helloWorld. Once you have a basic project set up, including app.yaml, and a main.py, as well as your added 'templates' folder, you should take the 'contacts' jinja2 template above, name it something like "mainPage.html" and put it in the templates folder. You should then try to add to this MainPage handler. Try to get the two inputs from the form as discussed in the Request Handlers section above. Use these given helper functions to render the page.
  251.  
  252. A couple of hints:
  253.  
  254. time.sleep(1) # sleep for 1 second
  255. self.redirect("/")
  256. This code will refresh your page. You want to add the time.sleep() in because sometimes GAE is finicky with the instant self.redirect("/") back to the page you're on.
  257.  
  258. Also, you might try using the post() Request Handler to handle your incoming data. This tutorial had an example of "get" but you'll notice that in the contacts html jinja template, the form data is being sent "post".
  259.  
  260. ####################@@@@##############$%##$&5#%%$$%
  261.  
  262.  
  263. http://www.vogella.com/tutorials/GoogleAppEngine/article.html
  264.  
  265.  
  266. #app.yaml
  267.  
  268. application: googleappengine01
  269. version: 1
  270. runtime: python
  271. api_version: 1
  272.  
  273. handlers:
  274. - url: /css
  275. static_dir: css
  276. - url: /images
  277. static_dir: images
  278. - url: /.*
  279. script: todo.py
  280.  
  281.  
  282. #todo.py
  283.  
  284. from google.appengine.api import users
  285. from google.appengine.ext import webapp
  286. from google.appengine.ext.webapp.util import run_wsgi_app
  287. from google.appengine.ext import db
  288. from google.appengine.ext.webapp import template
  289. from google.appengine.api import mail
  290.  
  291. # Todo defines the data model for the Todos
  292. # as it extends db.model the content of the class will automatically stored
  293. class TodoModel(db.Model):
  294. author = db.UserProperty(required=True)
  295. shortDescription = db.StringProperty(required=True)
  296. longDescription = db.StringProperty(multiline=True)
  297. url = db.StringProperty()
  298. created = db.DateTimeProperty(auto_now_add=True)
  299. updated = db.DateTimeProperty(auto_now=True)
  300. dueDate = db.StringProperty(required=True)
  301. finished = db.BooleanProperty()
  302.  
  303.  
  304. # The main page where the user can login and logout
  305. # MainPage is a subclass of webapp.RequestHandler and overwrites the get method
  306. class MainPage(webapp.RequestHandler):
  307. def get(self):
  308. user = users.get_current_user()
  309. url = users.create_login_url(self.request.uri)
  310. url_linktext = 'Login'
  311.  
  312. if user:
  313. url = users.create_logout_url(self.request.uri)
  314. url_linktext = 'Logout'
  315. # GQL is similar to SQL
  316. todos = TodoModel.gql("WHERE author = :author and finished=false",
  317. author=users.get_current_user())
  318.  
  319. values = {
  320. 'todos': todos,
  321. 'numbertodos' : todos.count(),
  322. 'user': user,
  323. 'url': url,
  324. 'url_linktext': url_linktext,
  325. }
  326. self.response.out.write(template.render('index.html', values))
  327.  
  328.  
  329.  
  330. # This class creates a new Todo item
  331. class New(webapp.RequestHandler):
  332. def post(self):
  333. user = users.get_current_user()
  334. if user:
  335. testurl = self.request.get('url')
  336. if not testurl.startswith("http://") and testurl:
  337. testurl = "http://"+ testurl
  338. todo = TodoModel(author = users.get_current_user(),
  339. shortDescription = self.request.get('shortDescription'),
  340. longDescription = self.request.get('longDescription'),
  341. dueDate = self.request.get('dueDate'),
  342. url = testurl,
  343. finished = False)
  344. todo.put();
  345.  
  346. self.redirect('/')
  347.  
  348. # This class deletes the selected Todo
  349. class Done(webapp.RequestHandler):
  350. def get(self):
  351. user = users.get_current_user()
  352. if user:
  353. raw_id = self.request.get('id')
  354. id = int(raw_id)
  355. todo = TodoModel.get_by_id(id)
  356. todo.delete()
  357. self.redirect('/')
  358.  
  359.  
  360. #This class emails the task to yourself
  361. class Email(webapp.RequestHandler):
  362. def get(self):
  363. user = users.get_current_user()
  364. if user:
  365. raw_id = self.request.get('id')
  366. id = int(raw_id)
  367. todo = TodoModel.get_by_id(id)
  368. message = mail.EmailMessage(sender=user.email(),
  369. subject=todo.shortDescription)
  370. message.to = user.email()
  371. message.body = todo.longDescription
  372. message.send()
  373. self.redirect('/')
  374.  
  375. # Register the URL with the responsible classes
  376. application = webapp.WSGIApplication([('/', MainPage),
  377. ('/new', New),
  378. ('/done', Done),
  379. ('/email', Email)],
  380. debug=True)
  381.  
  382. # Register the wsgi application to run
  383. def main():
  384. run_wsgi_app(application)
  385.  
  386. if __name__ == "__main__":
  387. main()
  388.  
  389.  
  390. #index.html
  391.  
  392.  
  393. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  394. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  395. <html>
  396. <head>
  397. <title>Todos</title>
  398. <link rel="stylesheet" type="text/css" href="css/main.css"/>
  399. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
  400. </head>
  401. <body>
  402.  
  403. <div style="width: 100%;">
  404. <div class="topLine">
  405. <div style="float: left;"><img src="images/todo.png" /></div>
  406. <div style="float: left;" class="headline">Todos</div>
  407. <div style="float: right;"><a href="{{ url }}">{{ url_linktext }}</a> {{user.nickname}}</div>
  408. </div>
  409. </div>
  410.  
  411. <div style="clear: both;"/>
  412.  
  413. {# Check if we have any todos, only in this case render the table #}
  414.  
  415. {% if numbertodos %}
  416.  
  417. You have a total number of {{numbertodos}} Todos.
  418.  
  419. <table>
  420. <tr>
  421.  
  422.  
  423. <th>Short description </th>
  424. <th>Due Date</th>
  425. <th>Long Description</th>
  426. <th>URL</th>
  427. <th>Created</th>
  428. <th>Updated</th>
  429. <th>Done</th>
  430. <th>Send Email reminder</th>
  431. </tr>
  432.  
  433. {% for todo in todos %}
  434. <tr>
  435. <td>
  436. {{todo.shortDescription}}
  437. </td>
  438. <td>
  439. {{todo.dueDate}}
  440. </td>
  441. <td>
  442. {{todo.longDescription}}
  443. </td>
  444. <td>
  445. {% if todo.url %}
  446. <a href="{{todo.url}}" > {{todo.url}}</a>
  447.  
  448. {% endif %}
  449. </td>
  450. <td>
  451. {{todo.created|date:"d.m.Y"}}
  452. </td>
  453. <td>
  454. {{todo.updated|date:"d.m.Y"}}
  455. </td>
  456. <td>
  457. <a class="done" href="/done?id={{todo.key.id}}" >Done</a>
  458. </td>
  459. <td>
  460. <a class="email" href="/email?id={{todo.key.id}}" >Email</a>
  461. </td>
  462. </tr>
  463. {% endfor %}
  464. </table>
  465.  
  466. {% endif %}
  467.  
  468.  
  469. <hr />
  470.  
  471.  
  472.  
  473. <div class="headline">Create new Todo?</div>
  474.  
  475. {% if user %}
  476.  
  477. <form action="/new" method="post">
  478. <table>
  479. <tr>
  480. <td><label for="shortDescription">Summary</label></td>
  481. <td><input type="text" name="shortDescription" id="shortDescriptions" size="80"/></td>
  482. </tr>
  483. <tr>
  484. <td><label for="dueDate">Due Date</label></td>
  485. <td><input type="dueDate" name="dueDate" id="dueDate"/></td>
  486. </tr>
  487. <tr>
  488. <td valign="top"><label for="longDescription">Description</label></td>
  489. <td><textarea rows="4" cols="80" name="longDescription" id="longDescription"></textarea></td>
  490. </tr>
  491. <tr>
  492. <td><label for="url">URL</label></td>
  493. <td><input type="text" name="url" id="url" size="80"/></td>
  494. </tr>
  495. <tr>
  496. <td colspan="2" align="right"><input type="submit" value="Create"/></td>
  497. </tr>
  498. </table>
  499. </form>
  500.  
  501. {% else %}
  502. Login with your Google Account to create and review todos.
  503. {% endif%}
  504.  
  505.  
  506. </body>
  507. </html>
  508.  
  509.  
  510. #main.css
  511. body {
  512. margin: 5px;
  513. font-family: Arial, Helvetica, sans-serif;
  514. }
  515.  
  516. hr {
  517. border: 0;
  518. background-color: #DDDDDD;
  519. height: 1px;
  520. margin: 5px;
  521. }
  522.  
  523.  
  524. table th {
  525. background:#EFEFEF none repeat scroll 0 0;
  526. border-top:1px solid #CCCCCC;
  527. font-size:small;
  528. padding-left:5px;
  529. padding-right:4px;
  530. padding-top:4px;
  531. vertical-align:top;
  532. text-align:left;
  533. }
  534.  
  535. table tr {
  536. background-color: #e5ecf9;
  537. font-size:small;
  538. }
  539.  
  540.  
  541. .topLine {
  542. height: 1.25em;
  543. background-color: #e5ecf9;
  544. padding-left: 4px;
  545. padding-right: 4px;
  546. padding-bottom: 3px;
  547. padding-top: 2px;
  548. }
  549.  
  550. .headline {
  551. font-weight: bold;
  552. color: #3366cc;
  553. }
  554.  
  555.  
  556.  
  557. .done {
  558. font-size: x-small;
  559. vertical-align: top;
  560. }
  561.  
  562.  
  563. .email {
  564. font-size: x-small;
  565. vertical-align: top;
  566. }
  567.  
  568. form td {
  569. font-size: smaller;
  570. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement