├── requirements.txt ├── .gitignore ├── hello_lulu.py ├── templates ├── end_lulu.html ├── userinfo_lulu.html └── layout_lulu.html ├── README.rst ├── 8_wrapup.rst ├── static └── style_lulu.css ├── application_lulu2.py ├── notes.rst ├── application_lulu.py ├── 2_settingup.rst ├── 4_requests.rst ├── 1_start.rst ├── 6_nextquestion.rst ├── 3_connecting.rst ├── 5_linking.rst └── 7_finale.rst /requirements.txt: -------------------------------------------------------------------------------- 1 | flask>=0.12.3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env-flask 2 | *.txt 3 | .Python 4 | bin/ 5 | include/ 6 | lib/ 7 | pip-selfcheck.json 8 | .idea -------------------------------------------------------------------------------- /hello_lulu.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app_lulu = Flask(__name__) 3 | 4 | @app_lulu.route('/hello_lulu2') 5 | def hello_lulu(): 6 | return 'Hello World' 7 | 8 | 9 | if __name__ == '__main__': 10 | app_lulu.run(port=5001, debug=True) 11 | 12 | -------------------------------------------------------------------------------- /templates/end_lulu.html: -------------------------------------------------------------------------------- 1 | 2 | A short quiz 3 | 4 |
5 |

A short quiz

6 |
7 | 8 |

9 | Thank you for participating! 10 |

11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | This is a Flask tutorial for people who don't know any web programming. 2 | 3 | I started learning Flask on June 4th, 2012. I was unhappy with the Flaskr tutorial, probably because I don't have a web programming background. I am writing this tutorial for people like me. 4 | 5 | You should go through the tutorial in the following order: 6 | 7 | - start.rst 8 | - settingup.rst 9 | - connecting.rst 10 | - requests.rst 11 | - linking.rst 12 | - nextquestion.rst 13 | - finale.rst 14 | - wrapup.rst 15 | -------------------------------------------------------------------------------- /8_wrapup.rst: -------------------------------------------------------------------------------- 1 | Wrap-up 2 | ======= 3 | 4 | That's the end of the tutorial! I hope you feel good about building things in Flask. 5 | There's a lot of room to grow from here, but this is a good foundation to start. 6 | 7 | You should understand: 8 | 9 | - When functions get called in ``application_lulu.py``. 10 | - When HTML templates are used, where they are stored, and how to pass variables into them. 11 | - How to use HTML forms, particularly labeling the fields and referring to them in the application 12 | - When a request method is GET and when a method is POST. 13 | - How to use the ``redirect`` Flask function, which allows you to return different HTML templates, 14 | based on the state of the system. 15 | 16 | Hopefully you aren't too frustrated with the tutorial or with me for writing it. :) 17 | 18 | <3 -------------------------------------------------------------------------------- /templates/userinfo_lulu.html: -------------------------------------------------------------------------------- 1 | 2 | A short quiz 3 | 4 |
5 |

A short quiz

6 |
7 | 8 |

9 | You will be asked {{num}} questions. 10 | Please answer them to the best of your ability. 11 |

12 | 13 |
14 |

15 | Name: 16 |

17 |

18 | Age: 19 |

20 |

21 | 22 |

23 |
24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /templates/layout_lulu.html: -------------------------------------------------------------------------------- 1 | 2 | A short quiz 3 | 4 |
5 |

Question #{{num}}

6 | 7 |
8 | 9 | {{question}} 10 | 11 |

12 |
13 |

14 | Answer:
15 | {{ans1}}
16 | {{ans2}}
17 | {{ans3}}
18 |

19 | 20 |

21 | 22 |

23 |
24 |

25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /static/style_lulu.css: -------------------------------------------------------------------------------- 1 | body { font-family: sans-serif; background: #eee; } 2 | a, h1, h2 { color: #377BA8; } 3 | h1, h2 { font-family: 'Georgia', serif; margin: 0; } 4 | h1 { border-bottom: 2px solid #eee; } 5 | h2 { font-size: 1.2em; } 6 | 7 | .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; 8 | padding: 0.8em; background: white; } 9 | .entries { list-style: none; margin: 0; padding: 0; } 10 | .entries li { margin: 0.8em 1.2em; } 11 | .entries li h2 { margin-left: -1em; } 12 | .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } 13 | .add-entry dl { font-weight: bold; } 14 | .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; 15 | margin-bottom: 1em; background: #fafafa; } 16 | .flash { background: #CEE5F5; padding: 0.5em; 17 | border: 1px solid #AACBE2; } 18 | .error { background: #F0D6D6; padding: 0.5em; } -------------------------------------------------------------------------------- /application_lulu2.py: -------------------------------------------------------------------------------- 1 | from flask import Flask,render_template,request,redirect 2 | app_lulu = Flask(__name__) 3 | 4 | app_lulu.vars={} 5 | 6 | 7 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 8 | def index_lulu(): 9 | nquestions=5 10 | if request.method == 'GET': 11 | return render_template('userinfo_lulu.html',num=nquestions) 12 | else: 13 | #request was a POST 14 | app_lulu.vars['name'] = request.form['name_lulu'] 15 | app_lulu.vars['age'] = request.form['age_lulu'] 16 | 17 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 18 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 19 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 20 | f.close() 21 | 22 | return render_template('layout_lulu.html',num=1,question='How many eyes do you have?',ans1='1',ans2='2',ans3='3') 23 | 24 | 25 | @app_lulu.route('/next_lulu',methods=['POST']) 26 | def next_lulu(): 27 | return redirect('/usefulfunction_lulu') 28 | 29 | 30 | @app_lulu.route('/usefulfunction_lulu',methods=['GET','POST']) 31 | def usefulfunction_lulu(): 32 | return render_template('layout_lulu.html',num=1,question='Which fruit do you like best?',ans1='banana',ans2='mango',ans3='pineapple') 33 | 34 | 35 | if __name__ == "__main__": 36 | app_lulu.run(port=5001, debug=True) 37 | -------------------------------------------------------------------------------- /notes.rst: -------------------------------------------------------------------------------- 1 | Notes 2 | ===== 3 | 4 | Since any application is nothing other than a way to represent data one should note that there is there is subtle difference between a web application and a desktop application, a desktop application normally is designed to be used by a single person, a web application could be used by millions of people at that same time. 5 | 6 | A Desktop application has either a GUI interface or the commandline interface. Web applications provide access ideally on port 80, in that way you don't need a 'client' to access the service that is running on port 80. With flask however you have the freedom to choose the port number, I never had done web programming using a framework before Flask, so it was difficult to understand the basic ideas, in PHP we save the .php files in the 'www' folder, but modern frameworks do not need to place all the projects in one root folder (the www one), the main difference between modern frameworks is there is a modular approach in designing the application, normally in PHP you write code in .php files and link them to each other from various files. 7 | 8 | But there is a problem in PHP, giving sexy links (the ones like Quora.com), it is quite difficult to give cute links that are easy to remember, with Flask you get a wonderful way to program web applications. 9 | 10 | You create 'links' and then associate with the links a function, you can associate python functions with each 'link' and that function is executed when the link is requested [with GET or POST, for now the difference shouldn't matter at all], there are static assets like HTML pages, CSS or JS scripts. The HTML pages have python markup inside them. 11 | -------------------------------------------------------------------------------- /application_lulu.py: -------------------------------------------------------------------------------- 1 | from flask import Flask,render_template,request,redirect 2 | app_lulu = Flask(__name__) 3 | 4 | app_lulu.vars={} 5 | 6 | app_lulu.questions={} 7 | app_lulu.questions['How many eyes do you have?']=('1','2','3') 8 | app_lulu.questions['Which fruit do you like best?']=('banana','mango','pineapple') 9 | app_lulu.questions['Do you like cupcakes?']=('yes','no','maybe') 10 | 11 | app_lulu.nquestions=len(app_lulu.questions) 12 | #should be 3 13 | 14 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 15 | def index_lulu(): 16 | nquestions=app_lulu.nquestions 17 | if request.method == 'GET': 18 | return render_template('userinfo_lulu.html',num=nquestions) 19 | else: 20 | #request was a POST 21 | app_lulu.vars['name'] = request.form['name_lulu'] 22 | app_lulu.vars['age'] = request.form['age_lulu'] 23 | 24 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 25 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 26 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 27 | f.close() 28 | 29 | return redirect('/main_lulu') 30 | 31 | 32 | @app_lulu.route('/main_lulu') 33 | def main_lulu2(): 34 | if len(app_lulu.questions)==0 : return render_template('end_lulu.html') 35 | return redirect('/next_lulu') 36 | 37 | ##################################### 38 | ## IMPORTANT: I have separated /next_lulu INTO GET AND POST 39 | ## You can also do this in one function, with If and Else. 40 | 41 | 42 | @app_lulu.route('/next_lulu',methods=['GET']) 43 | def next_lulu(): #remember the function name does not need to match the URL 44 | # for clarity (temp variables): 45 | n=app_lulu.nquestions-len(app_lulu.questions)+1 46 | q = list(app_lulu.questions.keys())[0] #python indexes at 0 47 | a1=app_lulu.questions[q][0] 48 | a2=app_lulu.questions[q][1] 49 | a3=app_lulu.questions[q][2] 50 | 51 | # save current question 52 | app_lulu.currentq=q 53 | 54 | return render_template('layout_lulu.html',num=n,question=q,ans1=a1,ans2=a2,ans3=a3) 55 | 56 | 57 | @app_lulu.route('/next_lulu',methods=['POST']) 58 | def next_lulu2(): #can't have two functions with the same name 59 | # Here, we will collect data from the user. 60 | # Then, we return to the main function, so it can tell us whether to 61 | # display another question page, or to show the end page. 62 | 63 | f=open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'a') #a is for append 64 | f.write('%s\n'%(app_lulu.currentq)) 65 | f.write('%s\n\n'%(request.form['answer_from_layout_lulu'])) #do you know where answer_lulu comes from? 66 | f.close() 67 | 68 | app_lulu.questions.pop(app_lulu.currentq) 69 | 70 | return redirect('/main_lulu') 71 | 72 | 73 | if __name__ == "__main__": 74 | app_lulu.run(port=5001, debug=True) 75 | -------------------------------------------------------------------------------- /2_settingup.rst: -------------------------------------------------------------------------------- 1 | Setting up the quiz application 2 | =============================== 3 | 4 | To set up the application, we will make the directory structure, copy over a CSS style sheet, and build an HTML form. 5 | 6 | Setting up the directory structure 7 | ---------------------------------- 8 | 9 | Set up your directories like this (put it somewhere, like your home directory):: 10 | 11 | ~/MyFlaskTutorial_lulu 12 | /static 13 | /templates 14 | 15 | Copy the file ``style_lulu.css`` (find it in the Github repository, in the static directory) and put it in the ``static`` directory. We will not 16 | discuss CSS style sheets in this tutorial. It will just make your app look pretty. Like magic. [Note: don't copy any of the other files from the GitHub repo to your computer yet --- trust me, the tutorial will be better this way.] 17 | 18 | The ``templates`` directory will hold HTML files that we will use to construct pages that 19 | will depend on user input. 20 | 21 | In the ``MyFlaskTutorial_lulu`` directory, we will place the main Python program that 22 | will process requests (on the server side) and return html files to send to the client. 23 | 24 | 25 | An HTML form 26 | ------------ 27 | 28 | None of this web app stuff means anything without HTML code. Let's make a 29 | simple page, then add a form. We will use that form to demonstrate HTTP 30 | methods, including `GET` and `POST`. Don't worry about what they are now. 31 | Let's get some HTML code working first. 32 | 33 | Open a file called ``userinfo_lulu.html``, and save it to the ``templates`` directory:: 34 | 35 | 36 | A short quiz 37 | 38 |
39 |

A short quiz

40 |
41 | 42 |

43 | You will be asked a series of questions. 44 | Please answer them to the best of your ability. 45 |

46 | 47 |
48 |

49 | Name: 50 |

51 |

52 | Age: 53 |

54 |

55 | 56 |

57 |
58 | 59 |
60 |
61 | 62 | 63 | Once the HTML file is written, you can view it in a web browser. You can do this from command line. For example, on a Mac:: 64 | 65 | open -a /Applications/Google\ Chrome.app/ userinfo_lulu.html 66 | 67 | For funsies, you can try entering data to the form, then clicking on Submit. You might get an error that looks like this:: 68 | 69 | No webpage was found for the web address: file://localhost/Users/administrator/MyFlaskTutorial/templates/index_lulu 70 | Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found. 71 | 72 | In the next part of the tutorial, we will build the backend that will generate the page that cannot currently be found. 73 | 74 | An aside: Take a look at the url_for method in the link tag. The url_for method will help figure out the css filepath. (Alternately, you can type the URL directly instead of using url_for.):: 75 | 76 | url_for('static',filename='style_lulu.css') 77 | 78 | 79 | -------------------------------------------------------------------------------- /4_requests.rst: -------------------------------------------------------------------------------- 1 | All about requests 2 | ================== 3 | 4 | A request is made when a user visits a URL. There are a few different types (also called methods) of requests. 5 | Here, we will only discuss two types: ``GET`` and ``POST`` requests. 6 | 7 | The ``request`` module must be imported from flask. You can update the import line in ``application_lulu.py``:: 8 | 9 | from flask import Flask,render_template,request 10 | 11 | The ``request`` module has an attribute called ``method``, which only has a value if a request is made. When 12 | the request is made, it takes the value of ``GET`` or ``POST`` (or a few other possible values, but we are 13 | ignoring them, remember?). 14 | 15 | There is some amount of philosophical argument about when to use ``GET`` and when to use ``POST``. From what I 16 | can tell, ``GET`` definitely should be used when no user-information is being used to produce the page. (Think 17 | Web 1.0.) And, ``POST`` should be used when the user is sending information to the server before a page is 18 | returned. 19 | 20 | (Technically, one can receive user input through the ``GET`` request, but it would appear only in 21 | the URL string. For example, do a Google search and see the URL you are visiting when your search result 22 | returns. There are question marks, key/value pairs, and other things that define the search.) 23 | 24 | GET requests 25 | ------------ 26 | 27 | In the application we have written so far, we have just done a ``GET`` request. We have asked for a webpage 28 | at ``127.0.0.1:5001/index_lulu``. Then ``application_lulu.py`` found the correct function to call, and it 29 | returned a webpage (``~/MyFlaskTutorial/templates/userinfo_lulu.html``). 30 | 31 | In the function ``index_lulu`` (inside ``application_lulu.py``), the attribute ``request.method`` exists, 32 | and it is equal to ``GET``. 33 | 34 | Edit ``application_lulu.py`` and see that this does not change what we have done so far:: 35 | 36 | from flask import Flask,render_template,request 37 | app_lulu = Flask(__name__) 38 | 39 | @app_lulu.route('/index_lulu',methods=['GET']) 40 | def index_lulu(): 41 | nquestions=5 42 | if request.method == 'GET': 43 | return render_template('userinfo_lulu.html',num=nquestions) 44 | else: 45 | return 'request.method was not a GET!' 46 | 47 | if __name__ == "__main__": 48 | app_lulu.run(port=5001, debug=True) 49 | 50 | Note that we instruct the application which types of requests to accept in the line:: 51 | 52 | @app_lulu.route('/index_lulu',methods=['GET']) 53 | 54 | I hope that worked for you just as well as it worked for me! CELEBRATE! We're almost there! 55 | 56 | POST requests 57 | ------------ 58 | 59 | Try entering your name and age in the fields, then click on Submit. It didn't work, did it? 60 | That's because we did not allow POST requests for the destination ``/index_lulu``. This can be 61 | remedied by editing the line in ``application_lulu.py`` to:: 62 | 63 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 64 | 65 | If you try running the application again, when you try to Submit your name and age, you should 66 | get the text we specified above:: 67 | 68 | 'request.method was not a GET!' 69 | 70 | GOOD! We know what's getting called when. (At least, I think you should know, based on our 71 | tests so far!) 72 | 73 | But, it's not interesting to have the screen tell you it was not a GET request. We'd rather 74 | the application CALCULATE SOME STUFF or at least RETURN A DIFFERENT PAGE when a POST request 75 | is received. 76 | 77 | Let's have it return a page with questions on it. First, we will need to make the HTML 78 | template, then we will have to instruct the application to use that template. The next part of 79 | the tutorial will discuss this. 80 | -------------------------------------------------------------------------------- /1_start.rst: -------------------------------------------------------------------------------- 1 | This is Beverly's Flask tutorial 2 | ================================ 3 | 4 | This tutorial is meant for people who have a good understanding of Python, 5 | but who have never done web programming before. You should know static HTML, 6 | but it is not necessary to know about forms, requests, and methods. 7 | 8 | I finally "understood" Flask when I realized 9 | 10 | - how the different functions are called based on "GET" and "POST" requests 11 | - what a "request" was 12 | - how HTML pages are rendered and returned 13 | - how to use Python as the machinery to decide which function to call (and thus 14 | which HTML template to return) 15 | 16 | After a short visit with a simple "Hello World" application, we will build a quiz 17 | application that will demonstrate "GET" and "POST" requests, rendering HTML templates, 18 | and redirecting to other functions that will render the appropriate HTML 19 | templates. User input will determine the HTML pages that are returned, and 20 | the data will be written to a text file on the server side. 21 | 22 | Some housekeeping 23 | ----------------- 24 | 25 | I hate programming tutorials that use function and instance names that look 26 | like offical names. There are so many function names and instances in 27 | tutorials that are totally user-defined. Basically:: 28 | 29 | Name it whatever-you-want, and it will still work! 30 | 31 | Instead of using willy-nilly names like "lulu", I will append "_lulu" to 32 | the end of logical names, so it will be entirely obvious which names can be 33 | changed with no penalty. And, it will give you an idea of what "real" 34 | programmers might use to call a certain function (hint: just drop the "_lulu"). 35 | Naming things well is certainly a skill to be desired. 36 | 37 | Just realize, if you change the name of the file from hello_lulu.py 38 | to something else, like flufflepuff.py, make sure to mentally make that 39 | change for the rest of the tutorial. 40 | 41 | A Quick Note about Debug Mode 42 | ----------------------------- 43 | In this tutorial, you'll be running your app in Debug Mode. This is very helpful 44 | for you - it means that if the server hits an error, it will give you a detailed 45 | message instead of just "500 - Internal Server Error" or something similarly 46 | non-obvious. However, it's important to note that debug mode is HIGHLY 47 | INSECURE, and should absolutely never be used on production machines. If that 48 | doesn't mean much to you, don't worry about it - you won't be running on 49 | production machines in this tutorial. 50 | 51 | Hello world 52 | ----------- 53 | 54 | Open a file called hello_lulu.py. Type the following:: 55 | 56 | from flask import Flask 57 | app_lulu = Flask(__name__) 58 | 59 | @app_lulu.route('/hello_page_lulu') 60 | def hello_world_lulu(): 61 | # this is a comment, just like in Python 62 | # note that the function name and the route argument 63 | # do not need to be the same. 64 | return 'Hello World!' 65 | 66 | if __name__ == '__main__': 67 | app_lulu.run(port=5001, debug=True) 68 | 69 | Now, run the code from command line using:: 70 | 71 | >> python hello_lulu.py 72 | 73 | Running this code will start a server at: 127.0.0.1:5001/hello_page_lulu. If you 74 | visit that address in a browser window, you should see a blank screen with 75 | text at the top left corner, which says "Hello World!" 76 | 77 | Note that the ``@app_lulu.route('/hello_page_lulu')`` is showing which code to run (in this 78 | case, ``hello_world_lulu``) when the URL ``/hello_page_lulu`` is requested. (A request is made 79 | when the URL is visited.) 80 | 81 | When the URL is visited, the code is run, and a string is returned. The 82 | string is the HTML code for the page. Since HTML does not parse white space, 83 | one long string will translate to an HTML page easily. 84 | 85 | Short note on browsers 86 | ---------------------- 87 | 88 | 127.0.0.1 is Home, or your own computer. 5001 is the port number. Flask 89 | puts the server on Port 5000 as the default. MacOS Monterey started using 90 | Port 5000 for AirPlay, so let's use 5001. I'm not going to talk a lot about 91 | this (actually I'm not going to talk at all about this), because you can 92 | play with this on your own later. 93 | -------------------------------------------------------------------------------- /6_nextquestion.rst: -------------------------------------------------------------------------------- 1 | Linking to the next question 2 | ============================ 3 | 4 | To link one question to the next, we need to tell the server what to do when ``/next_lulu`` is 5 | requested. This will be a POST request, because we want to take data from the user. 6 | 7 | Open ``application_lulu.py`` and add this decorated function:: 8 | 9 | @app_lulu.route('/next_lulu',methods=['POST']) 10 | def next_lulu(): #remember the function name does not need to match the URL 11 | return render_template('layout_lulu.html',num=1,question='Which fruit do you like best?',ans1='banana',\ 12 | ans2='mango',ans3='pineapple') 13 | 14 | In case it isn't clear, the whole file ``application_lulu.py`` should now look like:: 15 | 16 | from flask import Flask,render_template,request 17 | app_lulu = Flask(__name__) 18 | 19 | app_lulu.vars={} 20 | 21 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 22 | def index_lulu(): 23 | nquestions=5 24 | if request.method == 'GET': 25 | return render_template('userinfo_lulu.html',num=nquestions) 26 | else: 27 | #request was a POST 28 | app_lulu.vars['name'] = request.form['name_lulu'] 29 | app_lulu.vars['age'] = request.form['age_lulu'] 30 | 31 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 32 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 33 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 34 | f.close() 35 | 36 | return render_template('layout_lulu.html',num=1,question='How many eyes do you have?',ans1='1',\ 37 | ans2='2',ans3='3') 38 | 39 | @app_lulu.route('/next_lulu',methods=['POST']) 40 | def next_lulu(): #remember the function name does not need to match the URL 41 | return render_template('layout_lulu.html',num=1,question='Which fruit do you like best?',ans1='banana',\ 42 | ans2='mango',ans3='pineapple') 43 | 44 | if __name__ == "__main__": 45 | app_lulu.run(port=5001, debug=True) 46 | 47 | Try running it. IT WORKS, RIGHT? But, when you click on Next after the fruit question, what do you get? IT'S THE FRUIT QUESTION AGAIN. 48 | How can we fix this? We need to somehow tell the application to keep track of the questions being asked. We can make a list of 49 | questions at the beginning, then iterate through them. 50 | 51 | What will come in handy now is to learn about the ``redirect`` function in Flask. There's also ``url_for``, which you can 52 | Google yourself. It's not necessary for now. 53 | 54 | Redirecting 55 | ----------- 56 | 57 | The ``redirect`` function in Flask allows a decorated function (a function with ``@app_lulu.route('/index_lulu')`` or similar 58 | preceding the function) to return the HTML template that another function will produce. Instead of calling ``render_template`` 59 | to make an HTML page, we call ``redirect`` and insert the URL for another decorator function, which will call that decorated 60 | function and return the associated HTML code. 61 | 62 | Here's an example. We take the previous code and we just add one more step, which redirects to another function 63 | to give the rendered template:: 64 | 65 | from flask import Flask,render_template,request,redirect 66 | app_lulu = Flask(__name__) 67 | 68 | app_lulu.vars={} 69 | 70 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 71 | def index_lulu(): 72 | nquestions=5 73 | if request.method == 'GET': 74 | return render_template('userinfo_lulu.html',num=nquestions) 75 | else: 76 | #request was a POST 77 | app_lulu.vars['name'] = request.form['name_lulu'] 78 | app_lulu.vars['age'] = request.form['age_lulu'] 79 | 80 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 81 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 82 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 83 | f.close() 84 | 85 | return render_template('layout_lulu.html',num=1,question='How many eyes do you have?',ans1='1',\ 86 | ans2='2',ans3='3') 87 | 88 | @app_lulu.route('/next_lulu',methods=['POST']) 89 | def next_lulu(): 90 | return redirect('/usefulfunction_lulu') 91 | @app_lulu.route('/usefulfunction_lulu',methods=['GET','POST']) 92 | def usefulfunction_lulu(): 93 | return render_template('layout_lulu.html',num=1,question='Which fruit do you like best?',ans1='banana',\ 94 | ans2='mango',ans3='pineapple') 95 | 96 | if __name__ == "__main__": 97 | app_lulu.run(port=5001, debug=True) 98 | 99 | I think that's most of the FLASK-ESQUE stuff. The rest is all PYTHON. In the next section, we'll put some of this stuff together, 100 | using Python dictionaries to make the question-asking scalable (easier to add more questions), and to automate the process 101 | for many questions. 102 | -------------------------------------------------------------------------------- /3_connecting.rst: -------------------------------------------------------------------------------- 1 | Connecting an application with HTML 2 | =================================== 3 | 4 | Copy ``hello_lulu.py``. Call the new file ``application_lulu.py``. We will edit this file 5 | to produce a web application that will display the HTML form when the specified URL is 6 | accessed. 7 | 8 | 9 | Making the application 10 | ---------------------- 11 | 12 | Inside ``application_lulu.py``, edit the file to look like this:: 13 | 14 | from flask import Flask,render_template 15 | app_lulu = Flask(__name__) 16 | 17 | @app_lulu.route('/index_lulu') 18 | def index_lulu(): 19 | return render_template('userinfo_lulu.html') 20 | 21 | if __name__ == "__main__": 22 | app_lulu.run(port=5001, debug=True) 23 | 24 | You can try running it now with:: 25 | 26 | python application_lulu.py 27 | 28 | Open a browser window and go to:: 29 | 30 | 127.0.0.1:5001/index_lulu 31 | 32 | WOW! It looks PRETTY! Yes, that's the ``style_lulu.css`` that we added to the 33 | ``~/MyFlaskTutorial/static directory``. Don't worry about it for now. 34 | 35 | You should see the form we just made, with the user information and Submit button. 36 | ``Render_template`` will look for that HTML template in the ``~/MyFlaskTutorial/templates`` 37 | directory. That's why we put it there! 38 | 39 | In the ``hello world`` example, we saw that these functions must return text 40 | (HTML code). The ``render_template`` function will return text, and 41 | HTML text in fact! 42 | 43 | Passing variables to HTML files 44 | ------------------------------- 45 | 46 | You can pass variables to the output HTML through the ``render_template`` function. 47 | For example, we may want to tell the user how many questions we're going to ask. 48 | 49 | To do this, edit the ``application_lulu.py`` file:: 50 | 51 | from flask import Flask,render_template 52 | app_lulu = Flask(__name__) 53 | 54 | @app_lulu.route('/index_lulu') 55 | def index_lulu(): 56 | nquestions=5 57 | return render_template('userinfo_lulu.html',num=nquestions) 58 | 59 | if __name__ == "__main__": 60 | app_lulu.run(port=5001, debug=True) 61 | 62 | You also need to edit the HTML file to tell it to expect the variable 63 | ``num`` and to tell it what to do with that information. 64 | 65 | Open and edit the file ``~/MyFlaskTutorial/templates/userinfo_lulu.html``:: 66 | 67 | 68 | A short quiz 69 | 70 |
71 |

A short quiz

72 |
73 |

74 | You will be asked {{num}} questions. 75 | Please answer them to the best of your ability. 76 |

77 | 78 |
79 |

80 | Name: 81 |

82 |

83 | Age: 84 |

85 |

86 | 87 |

88 |
89 | 90 |
91 |
92 | 93 | Isn't that awesome? We can pass variables from our backend to the rendered HTML page. 94 | 95 | While we're here, take a look at the option called ``action`` in the ``form`` tag. Do you remember 96 | the error we got when we tried to click Submit? It told us it couldn't find a page called ``index_lulu``, 97 | right? Here's where we told it to look. If you change this to ``index_tutu``, the error will say:: 98 | 99 | No webpage was found for the web address: file://localhost/Users/administrator/MyFlaskTutorial/templates/index_tutu 100 | Error 6 (net::ERR_FILE_NOT_FOUND): The file or directory could not be found. 101 | 102 | Where is it looking for this file? It's actually looking in application_lulu.py, trying to find a URL 103 | among the arguments to the ``@app_lulu.route()`` decorators. If the URL is found, it will carry out the 104 | function that the decorator decorates (the function immediately below the ``@app_lulu.route()`` line). 105 | That function will return text (HTML code), and that is the page that will be loaded. 106 | 107 | For the time being, in ``application_lulu.py``, we have created a decorated function:: 108 | 109 | @app_lulu.route('/index_lulu') 110 | def index_lulu(): 111 | ... 112 | 113 | So, the webpage CAN be found at ``127.0.0.1:5001/index_lulu``, and we are telling the client that the 114 | webpage to be returned (when Submit is clicked) is ``userinfo_lulu.html``, which can be found in 115 | the ``~/MyFlaskTutorial/templates`` 116 | 117 | It works so far! 118 | 119 | Preparing to learn about POST requests 120 | ------------------------------------------ 121 | 122 | You might also be curious about these options called ``name`` in two of the ``input`` tags in the ``form``. 123 | These are the identifiers for their respective user-input fields in the form (which we have also named, 124 | ``userinfoform_lulu``). Here, we have called the ``name`` field ``name_lulu`` and we have called the ``age`` 125 | field ``age_lulu``. 126 | 127 | We will need these identifiers as we progress to the next step: understanding ``POST`` requests. 128 | -------------------------------------------------------------------------------- /5_linking.rst: -------------------------------------------------------------------------------- 1 | Linking pages 2 | ============= 3 | 4 | The next step is to have the POST request 5 | 6 | 1. store the data 7 | 2. take us to the next page 8 | 9 | We will pretend like there is only one question for now. We will add Python magic 10 | later, to tell the application how to know when to stop displaying questions. 11 | 12 | Store the data 13 | -------------- 14 | 15 | The name and age information will be used to write a file called name_age.txt, 16 | which will store responses to the questions in the quiz. Edit the file called 17 | ``application_lulu.py`` to do this:: 18 | 19 | from flask import Flask,render_template,request 20 | app_lulu = Flask(__name__) 21 | 22 | app_lulu.vars={} 23 | 24 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 25 | def index_lulu(): 26 | nquestions=5 27 | if request.method == 'GET': 28 | return render_template('userinfo_lulu.html',num=nquestions) 29 | else: 30 | #request was a POST 31 | app_lulu.vars['name'] = request.form['name_lulu'] 32 | app_lulu.vars['age'] = request.form['age_lulu'] 33 | 34 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 35 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 36 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 37 | f.close() 38 | 39 | return 'request.method was not a GET!' 40 | 41 | if __name__ == "__main__": 42 | app_lulu.run(port=5001, debug=True) 43 | 44 | You can try running this on the server. Enter your name and age, click on Submit. You 45 | should find a new file saved to your ``~/MyFlaskTutorial`` directory called name_age.txt. 46 | 47 | Note that we're making a dictionary called ``vars`` as an attribute of ``app_lulu``. 48 | This will allow us to access those variables in other functions inside ``application_lulu.py``. 49 | In the future, we can store other variables here. 50 | 51 | You will also notice that we are accessing a dictionary called ``form`` that is an attribute 52 | of ``request``. I mentioned this earlier: these names are identifiers for the form fields 53 | from the HTML template files. You can check out ``~/MyFlaskTutorial/templates/userinfo_lulu.html`` 54 | to see that we named the name and age fields ``name_lulu`` and ``age_lulu``, respectively. 55 | 56 | Writing files is a Python skill. You can read about this elsewhere, but it's pretty straightforward. 57 | 58 | Next, we will turn our attention to making the HTML template to return. 59 | 60 | Create the HTML template for questions 61 | -------------------------------------- 62 | 63 | Open a file called ``layout_lulu.html`` inside the ``~/MyFlaskTutorial/templates/`` directory. 64 | Edit it to look like this:: 65 | 66 | 67 | A short quiz 68 | 69 |
70 |

Question #{{num}}

71 | 72 |
73 | 74 | {{question}} 75 | 76 |

77 |
78 |

79 | Answer:
80 | {{ans1}}
81 | {{ans2}}
82 | {{ans3}}
83 |

84 | 85 |

86 | 87 |

88 |
89 |

90 |
91 |
92 | 93 | This is using some HTML skills about forms. You can look up different types of HTML forms online. 94 | I'm sure Google will tell you. Some types are: text, password, radio, and checkbox. 95 | 96 | Now, you probably already realize that we need to refer to this HTML template with two variables, 97 | ``num`` (which will be the question number) and ``question`` (which will be the question asked). 98 | The radio button form has a number of choices, but the user can only select one answer. The 99 | user data can be referred to as ``request.form['answer_from_layout_lulu']`` in the ``application_lulu.py`` 100 | file. 101 | 102 | Update the application program 103 | ------------------------------ 104 | 105 | Next, we want to make the ``application_lulu.py`` program render this new HTML template. Let's do 106 | this for one question first, then we can add more questions. 107 | 108 | Open ``application_lulu.py`` and replace the appropriate return line with this. Try to do it yourself. 109 | If you can't figure it out, the full text is right below:: 110 | 111 | return render_template('layout_lulu.html',num=1,question='How many eyes do you have?',ans1='1',\ 112 | ans2='2',ans3='3') 113 | 114 | The full ``application_lulu.py`` should look like:: 115 | 116 | from flask import Flask,render_template,request 117 | app_lulu = Flask(__name__) 118 | 119 | app_lulu.vars={} 120 | 121 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 122 | def index_lulu(): 123 | nquestions=5 124 | if request.method == 'GET': 125 | return render_template('userinfo_lulu.html',num=nquestions) 126 | else: 127 | #request was a POST 128 | app_lulu.vars['name'] = request.form['name_lulu'] 129 | app_lulu.vars['age'] = request.form['age_lulu'] 130 | 131 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 132 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 133 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 134 | f.close() 135 | 136 | return render_template('layout_lulu.html',num=1,question='How many eyes do you have?',ans1='1',\ 137 | ans2='2',ans3='3') 138 | 139 | if __name__ == "__main__": 140 | app_lulu.run(port=5001, debug=True) 141 | 142 | Try running the application again. To remind you, navigate to ``~/MyFlaskTutorial`` and type:: 143 | 144 | python application_lulu.py 145 | 146 | Then go to ``127.0.0.1:5001/index_lulu``. Enter your information, then click on Submit.. 147 | 148 | DID YOU GET TO YOUR QUESTION PAGE? I HOPE SO! 149 | 150 | Answer the question, then click on Next. You should be at the address ``127.0.0.1:5001/next_lulu`` 151 | and you should get an error that says:: 152 | 153 | Not Found 154 | The requested URL was not found on the server. 155 | If you entered the URL manually please check your spelling and try again. 156 | 157 | That's because we HAVEN'T TOLD THE SERVER what to do when the URL ``/next_lulu`` is requested. We 158 | told it to go to ``/next_lulu`` in the HTML template (``layout_lulu.html``), where we instructed 159 | the form to take the ``action`` equal to ``/next_lulu``. The server goes looking for a function 160 | inside ``application_lulu.py`` that is wrapped by the decorator that has ``/next_lulu`` as the 161 | argument. Look over the code to see that it all links together. 162 | 163 | There's no ``@app_lulu.route('/next_lulu`)``, right? LET'S FIX THAT NEXT. -------------------------------------------------------------------------------- /7_finale.rst: -------------------------------------------------------------------------------- 1 | The Grand Finale 2 | ================ 3 | 4 | We want to keep track of questions, and we want the function to show the ``/next_lulu`` to be general. To do this, let's make a 5 | dictionary with questions (as keys) and tuples of answers (as corresponding values). 6 | 7 | We will have a main function that will determine whether we should go to 1) the next question or 2) the end page (if there are 8 | no more questions). 9 | 10 | We will request ``/next_lulu``with 1) the correct question number and 2) the correct question. After each question is answered, 11 | we will "pop" that key/value pair from the dictionary. 12 | 13 | We will need to have different processing for GET and POST requests for ``/next_lulu``. For GET requests, we want to 14 | generate the correct question/answer form, and for the POST requests, we want to write data to file, then redirect 15 | the user to either the next question or the end page. 16 | 17 | I hope I haven't lost you! Let's do it step-by-step, but you won't be able to run anything till the end of this page. 18 | 19 | Make the end HTML file 20 | ---------------------- 21 | 22 | Create a file called ``end_lulu.html`` in the templates directory. Make it look like this:: 23 | 24 | 25 | A short quiz 26 | 27 |
28 |

A short quiz

29 |
30 |

31 | Thank you for participating! 32 |

33 |
34 |
35 | 36 | It's really pretty straightforward. Nothing fancy here. But, we need to link to it. 37 | 38 | Link to the end page 39 | -------------------- 40 | 41 | If you don't believe me, you can try out linking to the end page:: 42 | 43 | from flask import Flask,render_template,request,redirect 44 | app_lulu = Flask(__name__) 45 | 46 | app_lulu.vars={} 47 | 48 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 49 | def index_lulu(): 50 | nquestions=5 51 | if request.method == 'GET': 52 | return render_template('userinfo_lulu.html',num=nquestions) 53 | else: 54 | #request was a POST 55 | app_lulu.vars['name'] = request.form['name_lulu'] 56 | app_lulu.vars['age'] = request.form['age_lulu'] 57 | 58 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 59 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 60 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 61 | f.close() 62 | 63 | return render_template('layout_lulu.html',num=1,question='How many eyes do you have?',ans1='1',\ 64 | ans2='2',ans3='3') 65 | 66 | @app_lulu.route('/next_lulu',methods=['POST']) 67 | def next_lulu(): 68 | return redirect('/usefulfunction_lulu') 69 | 70 | @app_lulu.route('/usefulfunction_lulu',methods=['GET','POST']) 71 | def usefulfunction_lulu(): 72 | return render_template('end_lulu.html') 73 | 74 | if __name__ == "__main__": 75 | app_lulu.run(port=5001, debug=True) 76 | 77 | You can try running this. It will ask you the first question, and when you click Next, it will take you to the End page 78 | we just made. 79 | 80 | Now the final program, for real 81 | ------------------------------- 82 | 83 | Open ``application_lulu.py`` one last time. Change the contents to look like:: 84 | 85 | from flask import Flask,render_template,request,redirect 86 | app_lulu = Flask(__name__) 87 | 88 | app_lulu.vars={} 89 | 90 | app_lulu.questions={} 91 | app_lulu.questions['How many eyes do you have?']=('1','2','3') 92 | app_lulu.questions['Which fruit do you like best?']=('banana','mango','pineapple') 93 | app_lulu.questions['Do you like cupcakes?']=('yes','no','maybe') 94 | 95 | app_lulu.nquestions=len(app_lulu.questions) 96 | # should be 3 97 | 98 | @app_lulu.route('/index_lulu',methods=['GET','POST']) 99 | def index_lulu(): 100 | nquestions=app_lulu.nquestions 101 | if request.method == 'GET': 102 | return render_template('userinfo_lulu.html',num=nquestions) 103 | else: 104 | # request was a POST 105 | app_lulu.vars['name'] = request.form['name_lulu'] 106 | app_lulu.vars['age'] = request.form['age_lulu'] 107 | 108 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'w') 109 | f.write('Name: %s\n'%(app_lulu.vars['name'])) 110 | f.write('Age: %s\n\n'%(app_lulu.vars['age'])) 111 | f.close() 112 | 113 | return redirect('/main_lulu') 114 | 115 | @app_lulu.route('/main_lulu') 116 | def main_lulu2(): 117 | if len(app_lulu.questions)==0 : return render_template('end_lulu.html') 118 | return redirect('/next_lulu') 119 | 120 | ##################################### 121 | ## IMPORTANT: I have separated /next_lulu INTO GET AND POST 122 | ## You can also do this in one function, with If and Else 123 | ## The attribute that contains GET and POST is: request.method 124 | ##################################### 125 | 126 | @app_lulu.route('/next_lulu',methods=['GET']) 127 | def next_lulu(): #remember the function name does not need to match the URL 128 | # for clarity (temp variables) 129 | n = app_lulu.nquestions - len(app_lulu.questions) + 1 130 | q = list(app_lulu.questions.keys())[0] #python indexes at 0 131 | a1, a2, a3 = list(app_lulu.questions.values())[0] #this will return the answers corresponding to q 132 | 133 | # save the current question key 134 | app_lulu.currentq = q 135 | 136 | return render_template('layout_lulu.html',num=n,question=q,ans1=a1,ans2=a2,ans3=a3) 137 | 138 | @app_lulu.route('/next_lulu',methods=['POST']) 139 | def next_lulu2(): #can't have two functions with the same name 140 | # Here, we will collect data from the user. 141 | # Then, we return to the main function, so it can tell us whether to 142 | # display another question page, or to show the end page. 143 | 144 | f = open('%s_%s.txt'%(app_lulu.vars['name'],app_lulu.vars['age']),'a') #a is for append 145 | f.write('%s\n'%(app_lulu.currentq)) 146 | f.write('%s\n\n'%(request.form['answer_from_layout_lulu'])) #this was the 'name' on layout.html! 147 | f.close() 148 | 149 | # Remove question from dictionary 150 | del app_lulu.questions[app_lulu.currentq] 151 | 152 | return redirect('/main_lulu') 153 | 154 | if __name__ == "__main__": 155 | app_lulu.run(port=5001, debug=True) 156 | 157 | Try running it. I HOPE IT WORKS FOR YOU, TOO! If you look at the code here, you can hopefully follow which 158 | functions are being called as you click through the web application. 159 | 160 | The questions are stored as a dictionary. The questions are deleted after they are used, and the question 161 | number is determined by the number of key/value pairs in the dictionary. 162 | 163 | We have made a ``main_lulu`` function, which determined whether there are any questions left to ask. If 164 | there are, it calls ``next_lulu`` and displays the form. If there are no more questions, it shows the 165 | end page. 166 | 167 | When a question form page is shown, the user enters information and clicks on ``Next``. The request is 168 | a POST method type, which calls the appropriate ``next_lulu`` function (``next_lulu2``) (that writes data 169 | to file). That function DOES NOT return automatically to another ``next_lulu`` HTML form page. Instead, 170 | it ``redirects`` to the ``main_lulu`` function, which will tell it whether to return 1) ``end_lulu.html`` 171 | or 2) another ``next_lulu`` HTML form page. 172 | --------------------------------------------------------------------------------