├── .gitignore ├── README.md ├── README_EN.md ├── build.sh ├── build_ebook.sh ├── en ├── .buildinfo ├── .doctrees │ ├── blueprints.doctree │ ├── conclusion.doctree │ ├── configuration.doctree │ ├── conventions.doctree │ ├── deployment.doctree │ ├── environment.doctree │ ├── environment.pickle │ ├── forms.doctree │ ├── index.doctree │ ├── organizing.doctree │ ├── preface.doctree │ ├── static.doctree │ ├── storing.doctree │ ├── templates.doctree │ ├── users.doctree │ └── views.doctree ├── .nojekyll ├── CNAME ├── _images │ ├── balanced-logo.png │ ├── blueprints.png │ ├── configuration.png │ ├── conventions.png │ ├── deployment.png │ ├── environment.png │ ├── forms.png │ ├── organizing.png │ ├── static.png │ ├── storing.png │ ├── templates.png │ ├── users.png │ └── views.png ├── _sources │ ├── blueprints.txt │ ├── conclusion.txt │ ├── configuration.txt │ ├── conventions.txt │ ├── deployment.txt │ ├── environment.txt │ ├── forms.txt │ ├── index.txt │ ├── organizing.txt │ ├── preface.txt │ ├── static.txt │ ├── storing.txt │ ├── templates.txt │ ├── users.txt │ └── views.txt ├── _static │ ├── ajax-loader.gif │ ├── basic.css │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── css │ │ ├── badge_only.css │ │ └── theme.css │ ├── doctools.js │ ├── down-pressed.png │ ├── down.png │ ├── file.png │ ├── fonts │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── images │ │ ├── balanced-logo.png │ │ ├── blueprints.png │ │ ├── configuration.png │ │ ├── conventions.png │ │ ├── cover.png │ │ ├── deployment.png │ │ ├── environment.png │ │ ├── forms.png │ │ ├── me-box.png │ │ ├── organizing.png │ │ ├── static.png │ │ ├── storing.png │ │ ├── templates.png │ │ ├── users.png │ │ └── views.png │ ├── jquery.js │ ├── js │ │ └── theme.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── underscore.js │ ├── up-pressed.png │ ├── up.png │ └── websupport.js ├── blueprints.html ├── conclusion.html ├── configuration.html ├── conventions.html ├── deployment.html ├── environment.html ├── forms.html ├── genindex.html ├── index.html ├── objects.inv ├── organizing.html ├── preface.html ├── search.html ├── searchindex.js ├── static.html ├── storing.html ├── templates.html ├── users.html └── views.html └── zh ├── 1-introduction.md ├── 10-storing-data.md ├── 11-handling_forms.md ├── 12-handling_users.md ├── 13-deployment.md ├── 2-coding_conventions.md ├── 3-environment.md ├── 4-organizing_your_project.md ├── 5-configuration.md ├── 6-advanced_patterns_for_views_and_routing.md ├── 7-blueprints.md ├── 8-templates.md ├── 9-static_files.md ├── README.md ├── SUMMARY.md ├── book.json ├── cover.md └── images ├── .gitkeep ├── balanced-logo.png ├── blueprints.png ├── configuration.png ├── conventions.png ├── cover.png ├── deployment.png ├── environment.png ├── forms.png ├── me-box.png ├── organizing.png ├── static.png ├── storing.png ├── templates.png ├── users.png └── views.png /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | ebook/* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask之旅 2 | 3 | 《explore flask》中文翻译 4 | 5 | 欢迎来到该译本的生产地点!任何bug report和别的pull request都会受到热烈欢迎!不要犹豫! 6 | 7 | 如果你想购买PDF格式的英文原本,前往。 8 | 9 | ## 想参与? 10 | 11 | 如果你对本书有些建议,你可以在issue面板新开一个issue或提交一个pull request。如果你准备进行较大的改动,最好先在issue中提及,这样我们好能事先讨论这么做有没有必要。 12 | 13 | 译者:如果是关于翻译方面的问题,欢迎提issue或pull request。如果是关乎内容的问题,请移步 14 | 15 | ## 协议 16 | 17 | 中文译本的协议同英文原本,使用[Create Commons Attribution-NonCommercial 3.0 Unported license](http://creativecommons.org/licenses/by-nc/3.0/)。 18 | 你可以自由地分发并修改本书,前提是保证不把它重新销售并保留原作者(我指的是英文原本作者)的作品归属权。 19 | 20 | ## 感谢 21 | 22 | 由于本人水平有限,翻译过程中难免有错漏之处。感谢帮忙校正的各位网友:https://github.com/spacewander/explore-flask-zh/graphs/contributors 23 | 24 | ## 下载 25 | 26 | 见 27 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Explore Flask 2 | 3 | Hello and welcome to the source material of *Explore Flask*! The book is available for sale in PDF form at http://exploreflask.com. 4 | 5 | The book is currently in development. I've decided to release things in this unfinished state and finish the development of *Explore Flask* out in the open. I'm hoping that early feedback from the community will help make this an amazing book by version 1.0. 6 | 7 | ## Building 8 | 9 | The book is built using pandoc and Make. Once you have those set-up on your system you can just run `make` in the _explore-flask/_ repository. That should produce a _build.pdf_ build of the book. 10 | 11 | The master branch is the source of the latest release. Each release is also tagged with the version number. The progress branch is where you'll find the changes meant for future releases. 12 | 13 | ## License 14 | The book is licensed under the [Creative Commons Attribution-NonCommercial 3.0 Unported License](http://creativecommons.org/licenses/by-nc/3.0/). You are free to share and remix the book, as long as you provide attribution and do not attempt to re-sell. 15 | 16 | I absolutely welcome bug reports and pull requests. If you want me to merge your changes from a pull request, it implies that you're granting me a full, transferable license to modify, distribute, sell, and otherwise use those changes in any capacity. You'll retain the copyright, but I can do whatever I need/want to with the work and I can grant others the same license. 17 | 18 | ## Feedback 19 | 20 | The whole point of putting the unfinished book out is to get feedback from readers like you. You can submit feedback by opening an issue in the [issue tracker](https://github.com/rpicard/explore-flask/issues) or by emailing me at mail+efgithub@robert.io. 21 | 22 | ## Changelog 23 | 24 | * 0.01 - This is the first release. It contains the first 7 chapters in a rough state. Other chapters are in a rougher state so they aren't included. 25 | 26 | 27 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x 4 | curDir=$(pwd) 5 | tmpDir=$(mktemp -d -u) 6 | gitbook build ./zh --config ./zh/book.json 7 | mv ./zh/_book "$tmpDir" 8 | git checkout gh-pages 9 | git pull origin gh-pages 10 | cp -r -t "$tmpDir" .git 11 | cd "$tmpDir" && git commit -a -m "$(date)" && git push origin gh-pages 12 | cd "$curDir" && git checkout master 13 | -------------------------------------------------------------------------------- /build_ebook.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -d ./ebook ] 4 | then 5 | mkdir ebook 6 | fi 7 | gitbook pdf ./zh --config ./zh/book.json ebook/Flask之旅.pdf 8 | gitbook epub ./zh --config ./zh/book.json ebook/Flask之旅.epub 9 | gitbook mobi ./zh --config ./zh/book.json ebook/Flask之旅.mobi 10 | -------------------------------------------------------------------------------- /en/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 660dfb7e8dce29f718cee8fccd60d055 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /en/.doctrees/blueprints.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/blueprints.doctree -------------------------------------------------------------------------------- /en/.doctrees/conclusion.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/conclusion.doctree -------------------------------------------------------------------------------- /en/.doctrees/configuration.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/configuration.doctree -------------------------------------------------------------------------------- /en/.doctrees/conventions.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/conventions.doctree -------------------------------------------------------------------------------- /en/.doctrees/deployment.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/deployment.doctree -------------------------------------------------------------------------------- /en/.doctrees/environment.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/environment.doctree -------------------------------------------------------------------------------- /en/.doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/environment.pickle -------------------------------------------------------------------------------- /en/.doctrees/forms.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/forms.doctree -------------------------------------------------------------------------------- /en/.doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/index.doctree -------------------------------------------------------------------------------- /en/.doctrees/organizing.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/organizing.doctree -------------------------------------------------------------------------------- /en/.doctrees/preface.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/preface.doctree -------------------------------------------------------------------------------- /en/.doctrees/static.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/static.doctree -------------------------------------------------------------------------------- /en/.doctrees/storing.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/storing.doctree -------------------------------------------------------------------------------- /en/.doctrees/templates.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/templates.doctree -------------------------------------------------------------------------------- /en/.doctrees/users.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/users.doctree -------------------------------------------------------------------------------- /en/.doctrees/views.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.doctrees/views.doctree -------------------------------------------------------------------------------- /en/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/.nojekyll -------------------------------------------------------------------------------- /en/CNAME: -------------------------------------------------------------------------------- 1 | exploreflask.com 2 | -------------------------------------------------------------------------------- /en/_images/balanced-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/balanced-logo.png -------------------------------------------------------------------------------- /en/_images/blueprints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/blueprints.png -------------------------------------------------------------------------------- /en/_images/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/configuration.png -------------------------------------------------------------------------------- /en/_images/conventions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/conventions.png -------------------------------------------------------------------------------- /en/_images/deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/deployment.png -------------------------------------------------------------------------------- /en/_images/environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/environment.png -------------------------------------------------------------------------------- /en/_images/forms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/forms.png -------------------------------------------------------------------------------- /en/_images/organizing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/organizing.png -------------------------------------------------------------------------------- /en/_images/static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/static.png -------------------------------------------------------------------------------- /en/_images/storing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/storing.png -------------------------------------------------------------------------------- /en/_images/templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/templates.png -------------------------------------------------------------------------------- /en/_images/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/users.png -------------------------------------------------------------------------------- /en/_images/views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_images/views.png -------------------------------------------------------------------------------- /en/_sources/conclusion.txt: -------------------------------------------------------------------------------- 1 | Conclusion 2 | ========== 3 | 4 | I don't feel like there's a lot to conclude at this point. I hope 5 | reading this book has helped you in your adventure with Flask. If that's 6 | the case, please get in touch with me! I would love to hear from people 7 | who enjoyed reading this. Feel free to let me know if you have any 8 | suggestions to improve the book as well. 9 | 10 | Thanks for reading! 11 | 12 | \- Robert 13 | -------------------------------------------------------------------------------- /en/_sources/conventions.txt: -------------------------------------------------------------------------------- 1 | Coding conventions 2 | ================== 3 | 4 | .. image:: _static/images/conventions.png 5 | :alt: Coding conventions 6 | :height: 100 pt 7 | There are a number of conventions in the Python community to guide the 8 | way you format your code. If you've been developing with Python for a 9 | while, then you might already be familiar with some of these 10 | conventions. I'll keep things brief and leave a few URLs where you can 11 | find more information if you haven't come across these topics before. 12 | 13 | Let's have a PEP rally! 14 | ----------------------- 15 | 16 | A **PEP** is a "Python Enhancement Proposal." These proposals are 17 | indexed and hosted at python.org. In the index, PEPs are grouped into a 18 | number of categories, including meta-PEPs, which are more informational 19 | than technical. The technical PEPs, on the other hand, often describe 20 | things like improvements to Python's internals. 21 | 22 | There are a few PEPs, like PEP 8 and PEP 257 that are meant to guide the 23 | way we write our code. PEP 8 contains coding style guidelines. PEP 257 24 | contains guidelines for docstrings, the generally accepted method of 25 | documenting code. 26 | 27 | PEP 8: Style Guide for Python Code 28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | PEP 8 is the official style guide for Python code. I recommend that you 31 | read it and apply its recommendations to your Flask projects (and all of 32 | your other Python code). Your code will be much more approachable when 33 | it starts growing to many files with hundreds, or thousands, of lines of 34 | code. The PEP 8 recommendations are all about having more readable code. 35 | Plus, if your project is going to be open source, potential contributors 36 | will likely expect and be comfortable working on code written with PEP 8 37 | in mind. 38 | 39 | One particularly important recommendation is to use 4 spaces per 40 | indentation level. No real tabs. If you break this convention, it'll be 41 | a burden on you and other developers when switching between projects. 42 | That sort of inconsistency is a pain in any language, but white-space is 43 | especially important in Python, so switching between real tabs and 44 | spaces could result in any number of errors that are a hassle to debug. 45 | 46 | PEP 257: Docstring Conventions 47 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | 49 | PEP 257 covers another Python standard: **docstrings**. You can read the 50 | definition and recommendations in the PEP itself, but here's an example 51 | to give you an idea of what a docstring looks like: 52 | 53 | :: 54 | 55 | def launch_rocket(): 56 | """Main launch sequence director. 57 | 58 | Locks seatbelts, initiates radio and fires engines. 59 | """ 60 | # [...] 61 | 62 | These kinds of docstrings can be used by software such as Sphinx to 63 | generate documentation files in HTML, PDF and other formats. They also 64 | make it easier to understand your code. 65 | 66 | .. note:: 67 | 68 | - `PEP 8 `_ 69 | - `PEP 257 `_ 70 | - `Sphinx `_, the documentation generator created by the same folks who brought us Flask 71 | 72 | Relative imports 73 | ---------------- 74 | 75 | Relative imports make life a little easier when developing Flask apps. 76 | The premise is simple. Let's say you want to import the ``User`` model 77 | from the module *myapp/models.py*. You might think to use the app's 78 | package name, i.e. ``myapp.models``. Using relative imports, you would 79 | indicate the location of the target module relative to the source. To do 80 | this we use a dot notation where the first dot indicates the current 81 | directory and each subsequent dot represents the next parent directory. 82 | Listing~ illustrates the diffence in syntax. 83 | 84 | :: 85 | 86 | # myapp/views.py 87 | 88 | # An absolute import gives us the User model 89 | from myapp.models import User 90 | 91 | # A relative import does the same thing 92 | from .models import User 93 | 94 | The advantage of this method is that the package becomes a heck of a lot 95 | more modular. Now you can rename your package and re-use modules from 96 | other projects without the need to update the hard-coded import 97 | statements. 98 | 99 | In my research I came across a Tweet that illustrates the benefit of 100 | relative imports. 101 | 102 | Just had to rename our whole package. Took 1 second. Package relative imports FTW! 103 | 104 | --- `David Beazley, @dabeaz `_ 105 | 106 | .. note:: 107 | 108 | You can read a little more about the syntax for relative imports from this section in `PEP 328 `_. 109 | 110 | Summary 111 | ------- 112 | 113 | - Try to follow the coding style conventions laid out in PEP 8. 114 | - Try to document your app with docstrings as defined in PEP 257. 115 | - Use relative imports to import your app's internal modules. 116 | 117 | -------------------------------------------------------------------------------- /en/_sources/deployment.txt: -------------------------------------------------------------------------------- 1 | Deployment 2 | ========== 3 | 4 | .. image:: _static/images/deployment.png 5 | :alt: Deployment 6 | 7 | We're finally ready to show our app to the world. It's time to deploy. 8 | This process can be a pain because there are so many moving parts. There 9 | are a lot of choices to make when it comes to our production stack as 10 | well. In this chapter, we're going to talk about some of the important 11 | pieces and some of the options we have with each. 12 | 13 | The Host 14 | -------- 15 | 16 | We're going to need a server somewhere. There are thousands of providers 17 | out there, but these are the three that I personally recommend. I'm not 18 | going to go over the details of how to get started with them, because 19 | that's out of the scope of this book. Instead I'll talk about their 20 | benefits with regards to hosting Flask applications. 21 | 22 | Amazon Web Services EC2 23 | ~~~~~~~~~~~~~~~~~~~~~~~ 24 | 25 | Amazon Web Services is a collection of services provided by ... Amazon! 26 | There's a good chance that you've heard of them before as they're 27 | probably the most popular choice for new startups these days. The AWS 28 | service that we're most concerned with here is EC2, or Elastic Compute 29 | Cloud. The big selling point of EC2 is that we get virtual servers - or 30 | **instances** as they're called in AWS parlance - that spin up in 31 | seconds. If we need to scale our app quickly it's just a matter of 32 | spinning up a few more EC2 instances for our app and sticking them 33 | behind a load balancer (we can even use the AWS Elastic Load Balancer). 34 | 35 | With regards to Flask, AWS is just a regular old virtual server. We can 36 | spin it up with our favorite linux distro and install our Flask app and 37 | our server stack without much overhead. It means that we're going to 38 | need a certain amount of systems administration knowledge though. 39 | 40 | Heroku 41 | ~~~~~~ 42 | 43 | Heroku is an application hosting service that is built on top of AWS 44 | services like EC2. They let us take advantage of the convenience of EC2 45 | without the requisite systems administration experience. 46 | 47 | With Heroku, we deploy our application with a ``git push`` to their 48 | server. This is really convenient when we don't want to spend our time 49 | SSHing into a server, installing and configuring software and coming up 50 | with a sane deployment procedure. This convenience comes at a price of 51 | course, though both AWS and Heroku offer a certain amount of free 52 | service. 53 | 54 | .. note:: 55 | 56 | Heroku has a `tutorial on deploying Flask `_ with their service. 57 | 58 | .. note:: 59 | 60 | Administrating your own databases can be time consuming and doing it well requires some experience. It's great to learn about database administration by doing it yourself for your side projects, but sometimes you'd like to save time and effort by outsourcing that part to professionals. 61 | 62 | Both Heroku and AWS have database management offerings. I don't have personal experience with either yet, but I've heard great things. It's worth considering if you want to make sure your data is being secured and backed-up without having to do it yourself. 63 | 64 | - `Heroku Postgres `_ 65 | - `Amazon RDS `_ 66 | 67 | Digital Ocean 68 | ~~~~~~~~~~~~~ 69 | 70 | Digital Ocean is an EC2 competitor that has recently begun to take off. 71 | Like EC2, Digital Ocean lets us spin up virtual servers - now called 72 | **droplets** - quickly. All droplets run on SSDs, which isn't something 73 | we get at the lower levels of EC2. The biggest selling point for me 74 | personally is an interface that is far simpler and easier to use than 75 | the AWS control panel. Digital Ocean is my preference for hosting and I 76 | recommend that you take a look at them. 77 | 78 | The Flask deployment experience on Digital Ocean is roughly the same as 79 | on EC2. We're starting with a clean linux distribution and installing 80 | our server stack from there. 81 | 82 | .. note:: 83 | 84 | Digital Ocean was nice enough to make a contribution to the Kickstarter campaign for *Explore Flask*. With that said, I promise that my recommendation comes from my own experience as a user. If I didn't like them, I wouldn't have asked them to pledge in the first place. 85 | 86 | The stack 87 | --------- 88 | 89 | This section will cover some of the software that we'll need to install 90 | on our server to serve our Flask application to the world. The basic 91 | stack is a front server that reverse proxies requests to an application 92 | runner that is running our Flask app. We'll usually have a database too, 93 | so we'll talk a little about those options as well. 94 | 95 | Application runner 96 | ~~~~~~~~~~~~~~~~~~ 97 | 98 | The server that we use to run Flask locally when we're developing our 99 | application isn't good at handling real requests. When we're actually 100 | serving our application to the public, we want to run it with an 101 | application runner like Gunicorn. Gunicorn handles requests and takes 102 | care of complicated things like threading. 103 | 104 | To use Gunicorn, we install the ``gunicorn`` package in our virtual 105 | environment with Pip. Running our app is a simple command away. 106 | 107 | :: 108 | 109 | # app.py 110 | 111 | from flask import Flask 112 | 113 | app = Flask(__name__) 114 | 115 | @app.route('/') 116 | def index(): 117 | return "Hello World!" 118 | 119 | A fine app indeed. Now, to serve it up with Gunicorn, we simply run the 120 | ``gunicorn`` command. 121 | 122 | :: 123 | 124 | (ourapp)$ gunicorn rocket:app 125 | 2014-03-19 16:28:54 [62924] [INFO] Starting gunicorn 18.0 126 | 2014-03-19 16:28:54 [62924] [INFO] Listening at: http://127.0.0.1:8000 (62924) 127 | 2014-03-19 16:28:54 [62924] [INFO] Using worker: sync 128 | 2014-03-19 16:28:54 [62927] [INFO] Booting worker with pid: 62927 129 | 130 | At this point, we should see "Hello World!" when we navigate our browser to *http://127.0.0.1:8000*. 131 | 132 | To run this server in the background (i.e. daemonize it), we can pass the ``-D`` option to Gunicorn. That way it'll run even after we close our current terminal session. 133 | 134 | If we daemonize Gunicorn, we might have a hard time finding the process to close later when we want to stop the server. We can tell Gunicorn to stick the process ID in a file so that we can stop or restart it later without searching through lists of running processess. We use the ``-p `` option to do that. 135 | 136 | :: 137 | 138 | (ourapp)$ gunicorn rocket:app -p rocket.pid -D 139 | (ourapp)$ cat rocket.pid 140 | 63101 141 | 142 | To restart and kill the server, we can run ``kill -HUP`` and ``kill`` respectively. 143 | 144 | :: 145 | 146 | (ourapp)$ kill -HUP `cat rocket.pid` 147 | (ourapp)$ kill `cat rocket.pid` 148 | 149 | By default Gunicorn runs on port 8000. We can change the port by adding the ``-b`` bind option. 150 | 151 | :: 152 | 153 | (ourapp)$ gunicorn rocket:app -p rocket.pid -b 127.0.0.1:7999 -D 154 | 155 | Making Gunicorn public 156 | ^^^^^^^^^^^^^^^^^^^^^^ 157 | 158 | .. warning:: 159 | 160 | Gunicorn is meant to sit behind a reverse proxy. If you tell it to listen to requests coming in from the public, it makes an easy target for denial of service attacks. It's just not meant to handle those kinds of requests. Only allow outside connections for debugging purposes and make sure to switch it back to only allowing internal connections when you're done. 161 | 162 | If we run Gunicorn like we have in the listings, we won't be able to 163 | access it from our local system. That's because Gunicorn binds to 164 | 127.0.0.1 by default. This means that it will only listen to connections 165 | coming from the server itself. This is the behavior that we want when we 166 | have a reverse proxy server that is sitting between the public and our 167 | Gunicorn server. If, however, we need to make requests from outside of 168 | the server for debugging purposes, we can tell Gunicorn to bind to 169 | 0.0.0.0. This tells it to listen for all requests. 170 | 171 | :: 172 | 173 | (ourapp)$ gunicorn rocket:app -p rocket.pid -b 0.0.0.0:8000 -D 174 | 175 | .. note:: 176 | 177 | - Read more about running and deploying Gunicorn `in the documentation `_. 178 | - `Fabric `_ is a tool that lets you run all of these deployment and management commands from the comfort of your local machine without SSHing into every server. 179 | 180 | Nginx Reverse Proxy 181 | ~~~~~~~~~~~~~~~~~~~ 182 | 183 | A reverse proxy handles public HTTP requests, sends them back to 184 | Gunicorn and gives the response back to the requesting client. Nginx can 185 | be used very effectively as a reverse proxy and Gunicorn "strongly 186 | advises" that we use it. 187 | 188 | To configure Nginx as a reverse proxy to a Gunicorn server running on 189 | 127.0.0.1:8000, we can create a file for our app: 190 | */etc/nginx/sites-available/expl-oreflask.com*. 191 | 192 | :: 193 | 194 | # /etc/nginx/sites-available/exploreflask.com 195 | 196 | # Redirect www.exploreflask.com to exploreflask.com 197 | server { 198 | server_name www.exploreflask.com; 199 | rewrite ^ http://exploreflask.com/ permanent; 200 | } 201 | 202 | # Handle requests to exploreflask.com on port 80 203 | server { 204 | listen 80; 205 | server_name exploreflask.com; 206 | 207 | # Handle all locations 208 | location / { 209 | # Pass the request to Gunicorn 210 | proxy_pass http://127.0.0.1:8000; 211 | 212 | # Set some HTTP headers so that our app knows where the 213 | # request really came from 214 | proxy_set_header Host $host; 215 | proxy_set_header X-Real-IP $remote_addr; 216 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 217 | } 218 | } 219 | 220 | Now we'll create a symlink to this file at */etc/nginx/sites-enabled* 221 | and restart Nginx. 222 | 223 | :: 224 | 225 | $ sudo ln -s \ 226 | /etc/nginx/sites-available/exploreflask.com \ 227 | /etc/nginx/sites-enabled/exploreflask.com 228 | 229 | We should now be able to make our requests to Nginx and receive the 230 | response from our app. 231 | 232 | .. note:: 233 | 234 | The `Nginx configuration section `_ in the Gunicorn docs will give you more information about setting Nginx up for this purpose. 235 | 236 | ProxyFix 237 | ^^^^^^^^ 238 | 239 | We may run into some issues with Flask not properly handling the proxied 240 | requests. It has to do with those headers we set in the Nginx 241 | configuration. We can use the Werkzeug ProxyFix to ... fix the proxy. 242 | 243 | :: 244 | 245 | # app.py 246 | 247 | from flask import Flask 248 | 249 | # Import the fixer 250 | from werkzeug.contrib.fixers import ProxyFix 251 | 252 | app = Flask(__name__) 253 | 254 | # Use the fixer 255 | app.wsgi_app = ProxyFix(app.wsgi_app) 256 | 257 | @app.route('/') 258 | def index(): 259 | return "Hello World!" 260 | 261 | .. note:: 262 | 263 | - Read more about ProxyFix in `the Werkzeug docs `_. 264 | 265 | Summary 266 | ------- 267 | 268 | - Three good choices for hosting Flask apps are AWS EC2, Heroku and 269 | Digital Ocean. 270 | - The basic deployment stack for a Flask application consists of the 271 | app, an application runner like Gunicorn and a reverse proxy like 272 | Nginx. 273 | - Gunicorn should sit behind Nginx and listen on 127.0.0.1 (internal 274 | requests) not 0.0.0.0 (external requests). 275 | - Use Werkzeug's ProxyFix to handle the appropriate proxy headers in 276 | your Flask application. 277 | 278 | -------------------------------------------------------------------------------- /en/_sources/environment.txt: -------------------------------------------------------------------------------- 1 | Environment 2 | =========== 3 | 4 | .. image:: _static/images/environment.png 5 | :alt: Environment 6 | 7 | 8 | Your application is probably going to require a lot of software to 9 | function properly. If it doesn't at least require the Flask package, you 10 | may be reading the wrong book. Your application's **environment** is 11 | essentially all of the things that need to be around when it runs. Lucky 12 | for us, there are a number of things that we can do to make managing our 13 | environment much less complicated. 14 | 15 | Use virtualenv to manage your environment 16 | ----------------------------------------- 17 | 18 | `virtualenv `_ is a tool for isolating your application in what is called a 19 | **virtual environment**. A virtual environment is a directory that 20 | contains the software on which your application depends. A virtual 21 | environment also changes your environment variables to keep your 22 | development environment contained. Instead of downloading packages, like 23 | Flask, to your system-wide — or user-wide — package directories, we can 24 | download them to an isolated directory used only for our current 25 | application. This makes it easy to specify which Python binary to use 26 | and which dependencies we want to have available on a per project basis. 27 | 28 | Virtualenv also lets you use different versions of the same package for 29 | different projects. This flexibility may be important if you're working 30 | on an older system with several projects that have different version 31 | requirements. 32 | 33 | When using virtualenv, you'll generally have only a few Python packages 34 | installed globally on your system. One of these will be virtualenv 35 | itself. You can install the ``virtualenv`` package with Pip. 36 | 37 | Once you have virtualenv on your system, you can start creating virtual 38 | environments. Navigate to your project directory and run the 39 | ``virtualenv`` command. It takes one argument, which is the destination 40 | directory of the virtual environment. Listing~ shows what this looks 41 | like. 42 | 43 | :: 44 | 45 | $ virtualenv venv 46 | New python executable in venv/bin/python 47 | Installing Setuptools...........[...].....done. 48 | Installing Pip..................[...].....done. 49 | $ 50 | 51 | virtualenv creates a new directory where the dependencies will be 52 | installed. 53 | 54 | Once the new virtual environment has been created, you must activate it 55 | by sourcing the *bin/activate* script that was created inside the 56 | virtual environment. 57 | 58 | :: 59 | 60 | $ which python 61 | /usr/local/bin/python 62 | $ source venv/bin/activate 63 | (venv)$ which python 64 | /Users/robert/Code/myapp/venv/bin/python 65 | 66 | The *bin/activate* script makes some changes to your shell's environment variables so that everything points to the new virtual environment instead of your global system. You can see the effect in code block above. After activation, the ``python`` command refers to the Python binary inside the virtual environment. When a virtual environment is active, dependencies installed with Pip will be downloaded to that virtual environment instead of the global system. 67 | 68 | You may notice that the shell prompt has been changed too. virtualenv prepends the name of the currently activated virtual environment, so you know that you're not working on the global system. 69 | 70 | You can deactivate your virtual environment by running the ``deactivate`` command. 71 | 72 | :: 73 | 74 | (venv)$ deactivate 75 | $ 76 | 77 | virtualenvwrapper 78 | ~~~~~~~~~~~~~~~~~ 79 | 80 | `virtualenvwrapper `_ is a package used to manage the virtual environments created by virtualenv. I didn't want to mention this tool until you had seen the basics of virtualenv so that you understand what it's improving upon and understand why you should use it. 81 | 82 | That virtual environment directory created in Listing~\ref{code:venv_create} adds clutter to your project repository. You only interact with it directly when activating the virtual environment and it shouldn't be in version control, so there's no need to have it in there. The solution is to use virtualenvwrapper. This package keeps all of your virtual environments out of the way in a single directory, usually _~/.virtualenvs/_ by default. 83 | 84 | To install virtualenvwrapper, follow the instructions in the documentation. See Box~\ref{aside:vwrap_docs} for that link. 85 | 86 | .. warning:: 87 | 88 | Make sure that you've deactivated all virtual environments before installing virtualenvwrapper. You want it installed globally, not in a pre-existing environment. 89 | 90 | Now, instead of running ``virtualenv`` to create an environment, you'll run ``mkvirtualenv``: 91 | 92 | :: 93 | 94 | $ mkvirtualenv rocket 95 | New python executable in rocket/bin/python 96 | Installing setuptools...........[...].....done. 97 | Installing pip..................[...].....done. 98 | (rocket)$ 99 | 100 | ``mkvirtualenv`` creates a directory in your virtual environments folder and activates it for you. Just like with plain old ``virtualenv``, ``python`` and ``pip`` now point to that virtual environment instead of the system binaries. To activate a particular environment, use the command: ``workon [environment name]``. ``deactivate`` still deactivates the environment. 101 | 102 | Keeping track of dependencies 103 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 104 | 105 | As a project grows, you'll find that the list of dependencies grows with it. It's not uncommon to need dozens of Python packages installed to run a Flask application. The easiest way to manage these is with a simple text file. Pip can generate a text file listing all installed packages. It can also read in this list to install each of them on a new system, or in a freshly minted environment. 106 | 107 | pip freeze 108 | '''''''''' 109 | 110 | *requirements.txt* is a text file used by many Flask applications to list all of the packages needed to run an application. This code block shows how to create this file and the following one shows how to use that text file to install your dependencies in a new environment. 111 | 112 | :: 113 | 114 | (rocket)$ pip freeze > requirements.txt 115 | 116 | :: 117 | 118 | $ workon fresh-env 119 | (fresh-env)$ pip install -r requirements.txt 120 | [...] 121 | Successfully installed flask Werkzeug Jinja2 itsdangerous markupsafe 122 | Cleaning up... 123 | (fresh-env)$ 124 | 125 | Manually tracking dependencies 126 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 127 | 128 | As your project grows, you may find that certain packages listed by 129 | ``pip freeze`` aren't actually needed to run the application. You'll 130 | have packages that are installed for development only. ``pip freeze`` 131 | doesn't discriminate between the two, it just lists the packages that 132 | are currently installed. As a result, you may want to manually track 133 | your dependencies as you add them. You can separate those packages needed 134 | to run your application and those needed to develop your application 135 | into *require_run.txt* and *require_dev.txt* respectively. 136 | 137 | Version control 138 | --------------- 139 | 140 | Pick a version control system and use it. I recommend Git. From what 141 | I've seen, Git is the most popular choice for new projects these days. 142 | Being able to delete code without worrying about making an irreversible 143 | mistake is invaluable. You'll be able to keep your project free of those 144 | massive blocks of commented out code, because you can delete it now and 145 | revert that change later should the need arise. Plus, you'll have backup 146 | copies of your entire project on GitHub, Bitbucket or your own Gitolite 147 | server. 148 | 149 | What to keep out of version control 150 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 151 | 152 | I usually keep a file out of version control for one of two reasons. 153 | Either it's clutter, or it's a secret. Compiled *.pyc* files and virtual 154 | environments --- if you're not using virtualenvwrapper for some reason 155 | --- are examples of clutter. They don't need to be in version control 156 | because they can be recreated from the *.py* files and your 157 | *requirements.txt* files respectively. 158 | 159 | API keys, application secret keys and database credentials are examples 160 | of secrets. They shouldn't be in version control because their exposure 161 | would be a massive breach of security. 162 | 163 | .. note:: 164 | 165 | When making security related decisions, I always like to assume that my repository will become public at some point. This means keeping secrets out and never assuming that a security hole won't be found because, "Who's going to guess that they can do that?" This kind of assumption is known as security by obscurity and it's a bad policy to rely on. 166 | 167 | When using Git, you can create a special file called *.gitignore* in 168 | your repository. In it, list regular expression patterns to match 169 | against filenames. Any filename that matches one of the patterns will be 170 | ignored by Git. I recommend using the *.gitignore* shown in Listing~ to 171 | get you started. 172 | 173 | :: 174 | 175 | *.pyc 176 | instance/ 177 | 178 | Instance folders are used to make secret configuration variables 179 | available to your application in a more secure way. We'll talk more 180 | about them later. 181 | 182 | .. note:: 183 | 184 | You can read more about *.gitignore* here: http://git-scm.com/docs/gitignore 185 | 186 | Debugging 187 | --------- 188 | 189 | Debug Mode 190 | ~~~~~~~~~~ 191 | 192 | Flask comes with a handy feature called debug mode. To turn it on, you 193 | just have to set ``debug = True`` in your development configuration. 194 | When it's on, the server will reload on code changes and errors will 195 | come with a stack trace and an interactive console. 196 | 197 | .. warning:: 198 | 199 | Take care not to enable debug mode in production. The interactive console enables arbitrary code execution and would be a massive security vulnerability if it was left on in the live site. 200 | 201 | Flask-DebugToolbar 202 | ~~~~~~~~~~~~~~~~~~ 203 | 204 | `Flask-DebugToolbar `_ is another great tool for debugging problems with 205 | your application. In debug mode, it overlays a side-bar onto every page 206 | in your application. The side bar gives you information about SQL 207 | queries, logging, versions, templates, configuration and other fun stuff 208 | that makes it easier to track down problems. 209 | 210 | .. note:: 211 | 212 | - Take a look at the quick start `section on debug mode `_. 213 | - There is some good information on handling errors, logging and working with other debuggers `in the flask docs `_. 214 | 215 | Summary 216 | ------- 217 | 218 | - Use virtualenv to keep your application's dependencies together. 219 | - Use virtualenvwrapper to keep your virtual environments together. 220 | - Keep track of dependencies with one or more text files. 221 | - Use a version control system. I recommend Git. 222 | - Use .gitignore to keep clutter and secrets out of version control. 223 | - Debug mode can give you information about problems in development. 224 | - The Flask-DebugToolbar extension will give you even more of that 225 | information. 226 | 227 | -------------------------------------------------------------------------------- /en/_sources/index.txt: -------------------------------------------------------------------------------- 1 | Explore Flask 2 | ============= 3 | 4 | *Explore Flask* is a book about best practices and patterns for developing 5 | web applications with `Flask `_. The book was funded 6 | by 426 backers `on Kickstarter `_ 7 | in July 2013. 8 | 9 | I finally released the book, after spending almost a year working on it. Almost immediately 10 | I was tired of managing distribution and limiting the book's audience by putting 11 | it behind a paywall. I didn't write a book to run a business, I wrote it to 12 | put some helpful content out there and help grow the Flask community. 13 | 14 | In June of 2014, soon after finishing the book, I reformatted 15 | it for the web and released it here for free. No payment or donation or anything 16 | required. Just enjoy! 17 | 18 | About the author 19 | ---------------- 20 | 21 | My name is Robert Picard. I'm a security consultant at Matasano Security and a Flask 22 | enthusiast. I like Flask for its simplicity in the face of frameworks like Django that 23 | try and be everything to everyone. That model works for a lot of people, but not for me. 24 | 25 | If you want to get in touch, feel free to send me an email at robert@robert.io. If you have 26 | feedback on the book, check out the `GitHub repository `_ too. 27 | 28 | Contents 29 | -------- 30 | 31 | .. toctree:: 32 | :maxdepth: 2 33 | 34 | preface 35 | conventions 36 | environment 37 | organizing 38 | configuration 39 | views 40 | blueprints 41 | templates 42 | static 43 | storing 44 | forms 45 | users 46 | deployment 47 | conclusion 48 | 49 | Thank you 50 | --------- 51 | 52 | First of all, I'd like to say thank you to my volunteer editor, Will 53 | Kahn-Greene. He was great about going over my very rough drafts and 54 | helping me decide on the scope and direction of the content. I'm looking 55 | forward to working with him to manage the project into the future. 56 | 57 | Another big thanks to everyone who took the time to talk with me about 58 | how they are using Flask. This includes Armin Ronacher (Flask creator), 59 | Mark Harviston (Elsevier), Glenn Yonemitsu (Markup Hive), Andy Parsons 60 | (Happify), Oleg Lavrovsky (Apps with love), Joel Anderson (Cloudmancer) 61 | and Mahmoud Abdelkader (Balanced). 62 | 63 | The cover and all the illustrations in this book were done by `Dominic 64 | Flask `_. 65 | 66 | *Explore Flask* wouldn't be happening if it weren't for the hundreds of 67 | people who backed the project on Kickstarter. A big thanks to a particularly 68 | generous sponsor, `Balanced Payments `_: 69 | 70 | .. image:: _static/images/balanced-logo.png 71 | :alt: Balanced Payments 72 | 73 | As promised in the Kickstarter project, here are the names of all of the generous men and women who pledged $50 or more: 74 | 75 | CodeLesson, Sam Black, Michał Bartoszkiewicz, Chad Catlett, Jacob 76 | Kaplan-Moss, John Schrom, Zach White, Dorothy L. Erwin, Brandon Brown, 77 | Fredrik Larsson, Karsten Hoffrath (khoffrath), Jonathan Chen, Mitch 78 | Wainer, John Cleaver, Paul Baines, Brandon Bennett, Gaelan Adams, Nick 79 | Charlton, Dustin Chapman and Senko Rašić. 80 | 81 | License 82 | ------- 83 | 84 | In the spirit of open source software, I'm placing all of the content in this book in the public domain. 85 | 86 | Have fun with it. 87 | 88 | Contributing 89 | ------------ 90 | 91 | The project is hosted `on GitHub `_ and pull requests are welcome! -------------------------------------------------------------------------------- /en/_sources/organizing.txt: -------------------------------------------------------------------------------- 1 | Organizing your project 2 | ======================= 3 | 4 | .. image:: _static/images/organizing.png 5 | :alt: Organizing your project 6 | 7 | Flask leaves the organization of your application up to you. This is one 8 | of the reasons I liked Flask as a beginner, but it does mean that you 9 | have to put some thought into how to structure your code. You could put 10 | your entire application in one file, or have it spread across multiple 11 | packages. There are a few organizational patterns that you can follow to 12 | make development and deployment easier. 13 | 14 | Definitions 15 | ----------- 16 | 17 | Let's define some of the terms that we'll run into in this chapter. 18 | 19 | **Repository** - This is the base folder where your applications sits. 20 | This term traditionally refers to version control systems, which you 21 | should be using. When I refer to your repository in this chapter, I'm 22 | talking about the root directory of your project. You probably won't 23 | need to leave this directory when working on your application. 24 | 25 | **Package** - This refers to a Python package that contains your 26 | application's code. I'll talk more about setting up your app as a 27 | package in this chapter, but for now just know that the package is a 28 | sub-directory of the repository. 29 | 30 | **Module** - A module is a single Python file that can be imported by 31 | other Python files. A package is essentially multiple modules packaged 32 | together. 33 | 34 | .. note:: 35 | 36 | - Read more about Python modules in `Python tutorial `_. 37 | - That same page has a `section on packages `_. 38 | 39 | Organization patterns 40 | --------------------- 41 | 42 | Single module 43 | ~~~~~~~~~~~~~ 44 | 45 | A lot of the Flask examples that you'll come across will keep all of the 46 | code in a single file, often *app.py*. This is great for quick projects 47 | (like the ones used for tutorials), where you just need to serve a few 48 | routes and you've got less than a few hundred lines of application code. 49 | 50 | :: 51 | 52 | app.py 53 | config.py 54 | requirements.txt 55 | static/ 56 | templates/ 57 | 58 | Application logic would sit in *app.py* for the example in Listing~. 59 | 60 | Package 61 | ~~~~~~~ 62 | 63 | When you're working on a project that's a little more complex, a single 64 | module can get messy. You'll need to define classes for models and 65 | forms, and they'll get mixed in with the code for your routes and 66 | configuration. All of this can frustrate development. To solve this 67 | problem, we can factor out the different components of our app into a 68 | group of inter-connected modules — a package. 69 | 70 | :: 71 | 72 | config.py 73 | requirements.txt 74 | run.py 75 | instance/ 76 | config.py 77 | yourapp/ 78 | __init__.py 79 | views.py 80 | models.py 81 | forms.py 82 | static/ 83 | templates/ 84 | 85 | The structure shown in this listing allows you to group the different 86 | components of your application in a logical way. The class definitions 87 | for models are together in *models.py*, the route definitions are in 88 | *views.py* and forms are defined in *forms.py* (we have a whole chapter 89 | for forms later). 90 | 91 | This table provides a basic rundown of the components you'll find in most 92 | Flask applications. You'll probably end up with a lot of other files in 93 | your repository, but these are common to most Flask applications. 94 | 95 | +-------------------------+-----------------------------------------------------------------+ 96 | | run.py | This is the file that is invoked to start up a development | 97 | | | server.It gets a copy of the app from your package and runs | 98 | | | it. This won't be used in production, but it will see a lot | 99 | | | of mileage in development. | 100 | +-------------------------+-----------------------------------------------------------------+ 101 | | requirements.txt | This file lists all of the Python packages that your app | 102 | | | depends on. You may have separate files for production and | 103 | | | development dependencies. | 104 | +-------------------------+-----------------------------------------------------------------+ 105 | | config.py | This file contains most of the configuration variables that | 106 | | | your app needs. | 107 | +-------------------------+-----------------------------------------------------------------+ 108 | | /instance/config.py | This file contains configuration variables that shouldn't | 109 | | | be in version control. This includes things like API keys | 110 | | | and database URIs containing passwords. This also contains | 111 | | | variables that are specific to this particular instance of | 112 | | | your application. For example, you might have ``DEBUG = False`` | 113 | | | in config.py, but set ``DEBUG = True`` in instance/config.py on | 114 | | | your local machine for development. Since this file will be | 115 | | | read in after config.py, it will override it and set | 116 | | | ``DEBUG = False``. | 117 | +-------------------------+-----------------------------------------------------------------+ 118 | | /yourapp/ | This is the package that contains your application. | 119 | +-------------------------+-----------------------------------------------------------------+ 120 | | /yourapp/\_\_init\_\_.py| This file initializes your application and brings together | 121 | | | all of the various components. | 122 | +-------------------------+-----------------------------------------------------------------+ 123 | | /yourapp/views.py | This is where the routes are defined. It may be split into | 124 | | | a package of its own (*yourapp/views/*) with related | 125 | | | views grouped together into modules. | 126 | +-------------------------+-----------------------------------------------------------------+ 127 | | /yourapp/models.py | This is where you define the models of your application. | 128 | | | This may be split into several modules in the same way as | 129 | | | views.py. | 130 | +-------------------------+-----------------------------------------------------------------+ 131 | | /yourapp/static/ | This file contains the public CSS, JavaScript, images and | 132 | | | other files that you want to make public via your app. It | 133 | | | is accessible from yourapp.com/static/ by default. | 134 | +-------------------------+-----------------------------------------------------------------+ 135 | | /yourapp/templates/ | This is where you'll put the Jinja2 templates for your app. | 136 | +-------------------------+-----------------------------------------------------------------+ 137 | 138 | Blueprints 139 | ~~~~~~~~~~ 140 | 141 | At some point you may find that you have a lot of related routes. If 142 | you're like me, your first thought will be to split *views.py* into a 143 | package and group those views into modules. When you're at this point, 144 | it may be time to factor your application into blueprints. 145 | 146 | Blueprints are essentially components of your app defined in a somewhat 147 | self-contained manner. They act as apps within your application. You 148 | might have different blueprints for the admin panel, the front-end and 149 | the user dashboard. This lets you group views, static files and 150 | templates by components, while letting you share models, forms and other 151 | aspects of your application between these components. We'll talk about 152 | using Blueprints to organize your application soon. 153 | 154 | Summary 155 | ------- 156 | 157 | - Using a single module for your application is good for quick 158 | projects. 159 | - Using a package for your application is good for projects with views, 160 | models, forms and other components. 161 | - Blueprints are a great way to organize projects with several distinct 162 | components. 163 | 164 | -------------------------------------------------------------------------------- /en/_sources/preface.txt: -------------------------------------------------------------------------------- 1 | Preface 2 | ============ 3 | 4 | This book is a collection of the best practices for using Flask. There 5 | are a lot of pieces to the average Flask application. You'll often need 6 | to interact with a database and authenticate users, for example. In the 7 | coming pages I'll do my best to explain the "right way" to do this sort 8 | of stuff. My recommendations aren't always going to apply, but I'm 9 | hoping that they'll be a good option most of the time. 10 | 11 | Assumptions 12 | ----------- 13 | 14 | In order to present you with more specific advice, I've written this 15 | book with a few fundamental assumptions. It's important to keep this in 16 | mind when you're reading and applying these recommendations to your own 17 | projects. 18 | 19 | Audience 20 | ~~~~~~~~ 21 | 22 | The content of this book builds upon the information in the official 23 | documentation. I highly recommend that you go through `the user guide `_ and 24 | follow along with `the tutorial `_. This will give you a chance to become 25 | familiar with the vocabulary of Flask. You should understand what views 26 | are, the basics of Jinja templating and other fundamental concepts 27 | defined for beginners. I've tried to avoid overlap with the information 28 | already available in the user guide, so if you read this book first, 29 | there's a good chance that you'll find yourself lost (is that an 30 | oxymoron?). 31 | 32 | With all of that said, the topics in this book aren't highly advanced. 33 | The goal is just to highlight best practices and patterns that will make 34 | development easier for you. While I'm trying to avoid too much overlap 35 | with the official documentation, you may find that I reiterate certain 36 | concepts to make sure that they're familiar. You shouldn't need to have 37 | the beginner's tutorial open while you read this. 38 | 39 | Versions 40 | ~~~~~~~~ 41 | 42 | Python 2 versus Python 3 43 | ^^^^^^^^^^^^^^^^^^^^^^^^ 44 | 45 | As I write this, the Python community is in the midst of a transition 46 | from Python 2 to Python 3. The official stance of the Python Software 47 | Foundation is as follows: 48 | 49 | Python 2.x is the status quo, Python 3.x is the present and future of the language. [1]_ 50 | 51 | As of version 0.10, Flask runs with Python 3.3. When I asked Armin 52 | Ronacher about whether new Flask apps should begin using Python 3, he 53 | said that he's not yet recommending it to people. 54 | 55 | I'm not using it myself currently, and I don't ever recommend to people things that I don't believe in myself, so I'm very cautious about recommending Python 3. 56 | 57 | --- Armin Ronacher, creator of Flask [2]_ 58 | 59 | One reason for holding off on Python 3 is that many common dependencies 60 | haven't been ported yet. You don't want to build a project around Python 61 | 3 only to realize a few months down the line that you can't use packages 62 | X, Y and Z. It's possible that eventually Flask will officially 63 | recommend Python 3 for new projects, but for now it's all about Python 64 | 2. 65 | 66 | .. note:: 67 | 68 | The `Python 3 Wall of Superpowers `_ tracks which major Python packages have been ported to Python 3. 69 | 70 | Since this book is meant to provide practical advice, I think it makes 71 | sense to write with the assumption of Python 2. Specifically, I'll be 72 | writing the book with Python 2.7 in mind. Future updates may very well 73 | change this to evolve with the Flask community, but for now 2.7 is where 74 | we stand. 75 | 76 | Flask version 0.10 77 | ^^^^^^^^^^^^^^^^^^ 78 | 79 | At the time of writing this, 0.10 is the latest version of Flask (0.10.1 80 | to be exact). Most of the lessons in this book aren't going to change 81 | with minor updates to Flask, but it's something to keep in mind 82 | nonetheless. 83 | 84 | Living document 85 | --------------- 86 | 87 | The content of this books is going to be updated on the fly, rather than with 88 | periodic releases. That is one of the benefits of putting the content out 89 | there for free, rather than putting it behind a walled garden. The web is a 90 | much more fluid distribution channel than print or even PDFs. 91 | 92 | The book's source is hosted `on GitHub `_ 93 | and that is where "development" will be happening. Contributions and ideas are 94 | always welcome! 95 | 96 | Conventions used in this book 97 | ----------------------------- 98 | 99 | Each chapter stands on its own 100 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 101 | 102 | Each chapter in this book is an isolated lesson. Many books and 103 | tutorials are written as one long lesson. Generally this means that an 104 | example program or application is created and updated throughout the 105 | book to demonstrate concepts and lessons. Instead, examples are included 106 | in each lesson to demonstrate the concepts, but the examples from 107 | different chapters aren't meant to be combined into one large project. 108 | 109 | Formatting 110 | ~~~~~~~~~~ 111 | 112 | Footnotes will be used for citations so you don't think I'm making 113 | things up. [3]_ 114 | 115 | *Italic text* will be used to denote a file name. 116 | 117 | **Bold text** will be used to denote a new or important term. 118 | 119 | .. warning:: 120 | 121 | Common pitfalls that could cause major problems will be shown in a warning box. 122 | 123 | .. note:: 124 | 125 | Supplemental information will appear in note boxes. 126 | 127 | 128 | Easter eggs 129 | ----------- 130 | 131 | Six backer names from the Kickstarter campaign have been encoded and 132 | sprinkled around the book. If you find all six and email the locations 133 | to me, I'll send you an extraordinarily mediocre prize. No hints. 134 | 135 | Summary 136 | ------- 137 | 138 | - This book contains recommendations for using Flask. 139 | - I'm assuming that you've gone through the Flask tutorial. 140 | - I'm using Python 2.7. 141 | - I'm using Flask 0.10. 142 | - I'll do my best to keep the content of the book up-to-date. 143 | - Each chapter in this book stands on its own. 144 | - There are a few ways that I'll use formatting to convey additional 145 | information about the content. 146 | - Summaries will appear as concise lists of takeaways from the 147 | chapters. 148 | 149 | .. [1] 150 | Source: `The Python wiki `_ 151 | .. [2] 152 | Source: `My conversation with Armin Ronacher `_ 153 | .. [3] 154 | See, it *must* be true! 155 | -------------------------------------------------------------------------------- /en/_sources/static.txt: -------------------------------------------------------------------------------- 1 | Static files 2 | ============ 3 | 4 | .. image:: _static/images/static.png 5 | :alt: Static files 6 | 7 | As their name suggests, static files are the files that don't change. In 8 | your average app, this includes CSS files, JavaScript files and images. 9 | They can also include audio files and other things of that nature. 10 | 11 | Organizing your static files 12 | ---------------------------- 13 | 14 | We'll create a directory for our static files called *static* inside our 15 | application package. 16 | 17 | :: 18 | 19 | myapp/ 20 | __init__.py 21 | static/ 22 | templates/ 23 | views/ 24 | models.py 25 | run.py 26 | 27 | How you organize the files in *static/* is a matter of personal 28 | preference. Personally, I get a little irked by having third-party 29 | libraries (e.g. jQuery, Bootstrap, etc.) mixed in with my own JavaScript 30 | and CSS files. To avoid this, I recommend separating third-party 31 | libraries out into a *lib/* folder within the appropriate directory. 32 | Some projects use *vendor/* instead of *lib/*. 33 | 34 | :: 35 | 36 | static/ 37 | css/ 38 | lib/ 39 | bootstrap.css 40 | style.css 41 | home.css 42 | admin.css 43 | js/ 44 | lib/ 45 | jquery.js 46 | home.js 47 | admin.js 48 | img/ 49 | logo.svg 50 | favicon.ico 51 | 52 | Serving a favicon 53 | ~~~~~~~~~~~~~~~~~ 54 | 55 | The files in our static directory will be served from 56 | *example.com/static/*. By default, web browsers and other software 57 | expects our favicon to be at *example.com/favicon.ico*. To fix this 58 | discrepency, we can add the following in the ```` section of our 59 | site template. 60 | 61 | :: 62 | 63 | 65 | 66 | Manage static assets with Flask-Assets 67 | -------------------------------------- 68 | 69 | Flask-Assets is an extension for managing your static files. There are 70 | two really useful tools that Flask-Assets provides. First, it lets you 71 | define **bundles** of assets in your Python code that can be inserted 72 | together in your template. Second, it lets you **pre-process** those 73 | files. This means that you can combine and minify your CSS and 74 | JavaScript files so that the user only has to load two minified files 75 | (CSS and JavaScript) without forcing you to develop a complex asset 76 | pipeline. You can even compile your files from Sass, LESS, CoffeeScript 77 | and a bunch of other sources. 78 | 79 | :: 80 | 81 | static/ 82 | css/ 83 | lib/ 84 | reset.css 85 | common.css 86 | home.css 87 | admin.css 88 | js/ 89 | lib/ 90 | jquery-1.10.2.js 91 | Chart.js 92 | home.js 93 | admin.js 94 | img/ 95 | logo.svg 96 | favicon.ico 97 | 98 | Defining bundles 99 | ~~~~~~~~~~~~~~~~ 100 | 101 | Our app has two sections: the public site and the admin panel, referred 102 | to as "home" and "admin" respectively in our app. We'll define four 103 | bundles to cover this: a JavaScript and CSS bundle for each section. 104 | We'll put these in an assets module inside our ``util`` package. 105 | 106 | :: 107 | 108 | # myapp/util/assets.py 109 | 110 | from flask.ext.assets import Bundle, Environment 111 | from .. import app 112 | 113 | bundles = { 114 | 115 | 'home_js': Bundle( 116 | 'js/lib/jquery-1.10.2.js', 117 | 'js/home.js', 118 | output='gen/home.js), 119 | 120 | 'home_css': Bundle( 121 | 'css/lib/reset.css', 122 | 'css/common.css', 123 | 'css/home.css', 124 | output='gen/home.css), 125 | 126 | 'admin_js': Bundle( 127 | 'js/lib/jquery-1.10.2.js', 128 | 'js/lib/Chart.js', 129 | 'js/admin.js', 130 | output='gen/admin.js), 131 | 132 | 'admin_css': Bundle( 133 | 'css/lib/reset.css', 134 | 'css/common.css', 135 | 'css/admin.css', 136 | output='gen/admin.css) 137 | } 138 | 139 | assets = Environment(app) 140 | 141 | assets.register(bundles) 142 | 143 | Flask-Assets combines your files in the order in which they are listed 144 | here. If *admin.js* requires *jquery-1.10.2.js*, make sure jquery is 145 | listed first. 146 | 147 | We're defining the bundles in a dictionary to make it easy to register 148 | them. webassets, the package behind Flask-Assets lets us register 149 | bundles in a number of ways, including passing a dictionary like the one 150 | we made in this snippet. [1]_ 151 | 152 | Since we're registering our bundles in ``util.assets``, all we have to 153 | do is import that module in *\_\_init\_\_.py* after our app has been 154 | initialized. 155 | 156 | :: 157 | 158 | # myapp/__init__.py 159 | 160 | # [...] Initialize the app 161 | 162 | from .util import assets 163 | 164 | Using our bundles 165 | ~~~~~~~~~~~~~~~~~ 166 | 167 | To use our admin bundles, we'll insert them into the parent template for 168 | the admin section: *admin/layout.html*. 169 | 170 | :: 171 | 172 | templates/ 173 | home/ 174 | layout.html 175 | index.html 176 | about.html 177 | admin/ 178 | layout.html 179 | dash.html 180 | stats.html 181 | 182 | :: 183 | 184 | {# myapp/templates/admin/layout.html #} 185 | 186 | 187 | 188 | 189 | {% assets "admin_js" %} 190 | 191 | {% endassets %} 192 | {% assets "admin_css" %} 193 | 194 | {% endassets %} 195 | 196 | 197 | {% block body %} 198 | {% endblock %} 199 | 200 | 201 | 202 | We can do the same thing for the home bundles in 203 | *templates/home/layout-.html*. 204 | 205 | Using filters 206 | ~~~~~~~~~~~~~ 207 | 208 | We can use filters to pre-process our static files. This is especially 209 | handy for minifying our JavaScript and CSS bundles. 210 | 211 | :: 212 | 213 | # myapp/util/assets.py 214 | 215 | # [...] 216 | 217 | bundles = { 218 | 219 | 'home_js': Bundle( 220 | 'lib/jquery-1.10.2.js', 221 | 'js/home.js', 222 | output='gen/home.js', 223 | filters='jsmin'), 224 | 225 | 'home_css': Bundle( 226 | 'lib/reset.css', 227 | 'css/common.css', 228 | 'css/home.css', 229 | output='gen/home.css', 230 | filters='cssmin'), 231 | 232 | 'admin_js': Bundle( 233 | 'lib/jquery-1.10.2.js', 234 | 'lib/Chart.js', 235 | 'js/admin.js', 236 | output='gen/admin.js', 237 | filters='jsmin'), 238 | 239 | 'admin_css': Bundle( 240 | 'lib/reset.css', 241 | 'css/common.css', 242 | 'css/admin.css', 243 | output='gen/admin.css', 244 | filters='cssmin') 245 | } 246 | 247 | # [...] 248 | 249 | .. note:: 250 | 251 | To use the ``jsmin`` and ``cssmin`` filters, you'll need to install the 252 | ``jsmin`` and ``cssmin`` packages (e.g. with 253 | ``pip install jsmin cssmin``). Make sure to add them to 254 | *requirements.txt* too. 255 | 256 | Flask-Assets will merge and compress our files the first time the 257 | template is rendered, and it'll automatically update the compressed file 258 | when one of the source files changes. 259 | 260 | .. note:: 261 | 262 | If you set `ASSETS_DEBUG = True` in your config, Flask-Assets will output each source file individually instead of merging them. 263 | 264 | .. note:: 265 | 266 | Take a look at some of `the other filters `_ that we can use with Flask-Assets. 267 | 268 | Summary 269 | ------- 270 | 271 | - Static files go in the *static/* directory. 272 | - Separate third-party libraries from your own static files. 273 | - Specify the location of your favicon in your templates. 274 | - Use Flask-Assets to insert static files in your templates. 275 | - Flask-Assets can compile, combine and compress your static files. 276 | 277 | .. [1] 278 | We can see how bundle registration works `in the source `_. -------------------------------------------------------------------------------- /en/_sources/storing.txt: -------------------------------------------------------------------------------- 1 | Storing data 2 | ============ 3 | 4 | .. image:: _static/images/storing.png 5 | :alt: Storing data 6 | 7 | Most Flask applications are going to deal with storing data at some 8 | point. There are many different ways to store data. Finding the best one 9 | depends entirely on the data you are going to store. If you are storing 10 | relational data (e.g. a user has posts, posts have a user, etc.) a 11 | relational database is probably going to be the way to go (big suprise). 12 | Other types of data might be more suited to NoSQL data stores, such as 13 | MongoDB. 14 | 15 | I'm not going to tell you how to choose a database engine for your 16 | application. There are people who will tell you that NoSQL is the only 17 | way to go and those who will say the same about relational databases. 18 | All I will say on that subject is that if you are unsure, a relational 19 | database (MySQL, PostgreSQL, etc.) will almost certainly work for 20 | whatever you're doing. 21 | 22 | Plus, when you use a relational database you get to use SQLAlchemy and 23 | SQLAlchemy is fun. 24 | 25 | SQLAlchemy 26 | ---------- 27 | 28 | SQLAlchemy is an ORM (Object Relational Mapper). It's basically an 29 | abstraction layer that sits on top of the raw SQL queries being executed 30 | on our database. It provides a consistent API to a long list of database 31 | engines. The most popular include MySQL, PostgreSQL and SQLite. This 32 | makes it easy to move data between our models and our database and it 33 | makes it really easy to do other things like switch database engines and 34 | migrate our schemas. 35 | 36 | There is a great Flask extension that makes using SQLAlchemy in Flask 37 | even easier. It's called Flask-SQLAlchemy. Flask-SQLAlchemy configures a 38 | lot of sane defaults for SQLAlchemy. It also handles some session 39 | management so we don't have to deal with janitorial stuff in our 40 | application code. 41 | 42 | Let's dive into some code. We're going to define some models then 43 | configure some SQLAlchemy. The models are going to go in 44 | *myapp/models.py*, but first we are going to define our database in 45 | *myapp/\ **init**.py* 46 | 47 | :: 48 | 49 | # ourapp/__init__.py 50 | 51 | from flask import Flask 52 | from flask.ext.sqlalchemy import SQLAlchemy 53 | 54 | app = Flask(__name__, instance_relative_config=True) 55 | 56 | app.config.from_object('config') 57 | app.config.from_pyfile('config.py') 58 | 59 | db = SQLAlchemy(app) 60 | 61 | First we initialize and configure our Flask app and then we use it to 62 | initialize our SQLAlchemy database handler. We're going to use an 63 | instance folder for our database configuration so we should use the 64 | ``instance_relative_config`` option when initializing the app and then 65 | call ``app.config.from_pyfile`` to load it. Then we can define our 66 | models. 67 | 68 | :: 69 | 70 | # ourapp/models.py 71 | 72 | from . import db 73 | 74 | class Engine(db.Model): 75 | 76 | # Columns 77 | 78 | id = db.Column(db.Integer, primary_key=True, autoincrement=True) 79 | 80 | title = db.Column(db.String(128)) 81 | 82 | thrust = db.Column(db.Integer, default=0) 83 | 84 | ``Column``, ``Integer``, ``String``, ``Model`` and other SQLAlchemy 85 | classes are all available via the ``db`` object constructed from 86 | Flask-SQLAlchemy. We have defined a model to store the 87 | current state of our spacecraft's engines. Each engine has an id, a 88 | title and a thrust level. 89 | 90 | We still need to add some database information to our configuration. 91 | We're using an instance folder to keep confidential configuration 92 | variables out of version control, so we are going to put it in 93 | *instance/config.py*. 94 | 95 | :: 96 | 97 | # instance/config.py 98 | 99 | SQLALCHEMY_DATABASE_URI = "postgresql://user:password@localhost/spaceshipDB" 100 | 101 | .. note:: 102 | 103 | Your database URI will be different depending on the engine you use and where it's hosted. See the `SQLAlchemy documentation for this syntax `_. 104 | 105 | Initializing the database 106 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 107 | 108 | Now that the database is configured and we have defined a model, we can 109 | initialize the database. This step basically involves creating the 110 | database schema from the model definitions. 111 | 112 | Normally that process might be a pain in the ... neck. Lucky for us, 113 | SQLAlchemy has a really cool command that will do all of this for us. 114 | 115 | Let's open up a Python terminal in our repository root. 116 | 117 | :: 118 | 119 | $ pwd 120 | /Users/me/Code/myapp 121 | $ workon myapp 122 | (myapp)$ python 123 | Python 2.7.5 (default, Aug 25 2013, 00:04:04) 124 | [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin 125 | Type "help", "copyright", "credits" or "license" for more information. 126 | >>> from myapp import db 127 | >>> db.create_all() 128 | >>> 129 | 130 | Now, thanks to SQLAlchemy, our tables have been created in the database 131 | specified in our configuration. 132 | 133 | Alembic migrations 134 | ~~~~~~~~~~~~~~~~~~ 135 | 136 | The schema of a database is not set in stone. For example, we may want 137 | to add a ``last_fired`` column to the engine table. If we don't have any 138 | data, we can just update the model and run ``db.create_all()`` again. 139 | However, if we have six months of engine data logged in that table, we 140 | probably don't want to start over from scratch. That's where database 141 | migrations come in. 142 | 143 | Alembic is a database migration tool created specifically for use with 144 | SQL-Alchemy. It lets us keep a versioned history of our database schema 145 | so that we can later upgrade to a new schema and even downgrade back to 146 | an older one. 147 | 148 | Alembic has an extensive tutorial to get you started, so I'll just give 149 | you a quick overview and point out a couple of things to watch out for. 150 | 151 | We'll create our alembic "migration environment" via the 152 | ``alembic init`` command. Once we run this in our repository root 153 | we'll have a new directory with the very creative name *alembic*. Our 154 | repository will end up looking something like the example in this listing, 155 | adapted from the Alembic tutorial. 156 | 157 | :: 158 | 159 | ourapp/ 160 | alembic.ini 161 | alembic/ 162 | env.py 163 | README 164 | script.py.mako 165 | versions/ 166 | 3512b954651e_add_account.py 167 | 2b1ae634e5cd_add_order_id.py 168 | 3adcc9a56557_rename_username_field.py 169 | myapp/ 170 | __init__.py 171 | views.py 172 | models.py 173 | templates/ 174 | run.py 175 | config.py 176 | requirements.txt 177 | 178 | 179 | The *alembic/* directory has the scripts that migrate our data between 180 | versions. There is also an *alembic.ini* file that contains 181 | configuration information. 182 | 183 | .. note:: 184 | 185 | Add *alembic.ini* to *.gitignore*! You are going to have your database 186 | credentials in this file, so you **do not** want it to end up in version 187 | control. 188 | 189 | You do want to keep *alembic/* in version control though. It does not 190 | contain sensitive information (that can't already be derived from your 191 | source code) and keeping it in version control will mean having multiple 192 | copies should something happen to the files on your computer. 193 | 194 | When it comes time to make a schema change, there are a couple of steps. 195 | First we run ``alembic revision`` to generate a migration script. Then 196 | we'll open up the newly generated Python file in 197 | *myapp/alembic/versions/* and fill in the ``upgrade`` and ``downgrade`` 198 | functions using the tools provided by Alembic's ``op`` object. 199 | 200 | Once we have our migration script ready, we can run 201 | ``alembic upgrade head`` to migrade our data to the latest version. 202 | 203 | .. note:: 204 | 205 | For the details on configuring Alembic, creating your migration scripts and running your migrations, see `the Alembic tutorial `_. 206 | 207 | .. warning:: 208 | 209 | Don't forget to put a plan in place to back up your data. The details of that plan are outside the scope of this book, but you should always have your datbase backed up in a secure and robust way. 210 | 211 | .. note:: 212 | 213 | The NoSQL scene is less established with Flask, but as long as the database engine of your choice has a Python library, you should be able to use it. There are even several extensions in `the Flask extension registry `_ to help integrate NoSQL engines with Flask. 214 | 215 | Summary 216 | ------- 217 | 218 | - Use SQLAlchemy to work with relational databases. 219 | - Use Flask-SQLAlchemy to work with SQLAlchemy. 220 | - Alembic helps you migrate your data between schema changes. 221 | - You can use NoSQL databases with Flask, but the methods and tools 222 | vary between engines. 223 | - Back up your data! 224 | 225 | -------------------------------------------------------------------------------- /en/_sources/templates.txt: -------------------------------------------------------------------------------- 1 | Templates 2 | ========= 3 | 4 | .. image:: _static/images/templates.png 5 | :alt: Templates 6 | 7 | While Flask doesn't force us to use any particular templating language, 8 | it assumes that we're going to use Jinja. Most of the developers in the 9 | Flask community use Jinja, and I recommend that you do the same. There 10 | are a few extensions that have been written to let us use other 11 | templating languages, like `Flask-Genshi `_ and `Flask-Mako `_. 12 | Stick with the default unless you have a good reason to use something else. Not knowing the 13 | Jinja syntax yet is not a good reason! You'll save yourself a lot of time and headache. 14 | 15 | .. note:: 16 | 17 | Almost all resources imply Jinja2 when they refer to "Jinja." There was a Jinja1, but we won't be dealing with it here. When you see Jinja, we're talking about this: `http://jinja.pocoo.org/ `_ 18 | 19 | A quick primer on Jinja 20 | ----------------------- 21 | 22 | The Jinja documentation does a great job of explaining the syntax and 23 | features of the language. I won't reiterate it all here, but I do want 24 | to make sure that you see this important note: 25 | 26 | There are two kinds of delimiters. ``{% ... %}`` and 27 | ``{{ ... }}``. The first one is used to execute statements such as 28 | for-loops or assign values, the latter prints the result of the 29 | expression to the template. 30 | 31 | --- `Jinja Template Designer Documentation `_ 32 | 33 | How to organize templates 34 | ------------------------- 35 | 36 | So where do templates fit into our app? If you've been following along 37 | at home, you may have noticed that Flask is really flexible about where 38 | we put things. Templates are no exception. You may also notice that 39 | there's usually a recommended place to put things. Two points for you. 40 | For templates, that place is in the package directory. 41 | 42 | :: 43 | 44 | myapp/ 45 | __init__.py 46 | models.py 47 | views/ 48 | templates/ 49 | static/ 50 | run.py 51 | requirements.txt 52 | 53 | :: 54 | 55 | templates/ 56 | layout.html 57 | index.html 58 | about.html 59 | profile/ 60 | layout.html 61 | index.html 62 | photos.html 63 | admin/ 64 | layout.html 65 | index.html 66 | analytics.html 67 | 68 | The structure of the *templates* directory parallels the structure of 69 | our routes. The template for the route *myapp.com/admin/analytics* is 70 | *templates/admin/an-alytics.html*. There are also some extra templates 71 | in there that won't be rendered directly. The *layout.html* files are 72 | meant to be inherited by the other templates. 73 | 74 | Inheritance 75 | ----------- 76 | 77 | Much like Batman's backstory, a well organized templates directory 78 | relies heavily on inheritance. The **parent template** usually defines a 79 | generalized structure that all of the **child templates** will work 80 | within. In our example, *layout.html* is a parent template and the other 81 | *.html* files are child templates. 82 | 83 | You'll generally have one top-level *layout.html* that defines the 84 | general layout for your application and one for each section of your 85 | site. If you take a look at the directory above, you'll see that there 86 | is a top-level *myapp/templates/lay-out.html* as well as 87 | *myapp/templates/profile/layout.html* and 88 | *myapp/templat-es/admin/layout.html*. The last two files inherit and 89 | modify the first. 90 | 91 | Inheritance is implemented with the ``{% extends %}`` and 92 | ``{% block %}`` tags. In the parent template, we can define blocks which 93 | will be populated by child templates. 94 | 95 | :: 96 | 97 | {# _myapp/templates/layout.html_ #} 98 | 99 | 100 | 101 | 102 | {% block title %}{% endblock %} 103 | 104 | 105 | {% block body %} 106 |

This heading is defined in the parent.

107 | {% endblock %} 108 | 109 | 110 | 111 | In the child template, we can extend the parent template and define the 112 | contents of those blocks. 113 | 114 | :: 115 | 116 | {# _myapp/templates/index.html_ #} 117 | 118 | {% extends "layout.html" %} 119 | {% block title %}Hello world!{% endblock %} 120 | {% block body %} 121 | {{ super() }} 122 |

This heading is defined in the child.

123 | {% endblock %} 124 | 125 | The ``super()`` function lets us include whatever was inside the block 126 | in the parent template. 127 | 128 | .. note:: 129 | 130 | For more information on inheritance, refer to the `Jinja Template Inheritence documentation `_. 131 | 132 | Creating macros 133 | --------------- 134 | 135 | We can implement DRY (Don't Repeat Yourself) principles in our templates 136 | by abstracting snippets of code that appear over and over into 137 | **macros**. If we're working on some HTML for our app's navigation, we 138 | might want to give a different class to the "active" link (i.e. the link 139 | to the current page). Without macros we'd end up with a block of 140 | ``if ... else`` statements that check each link to find the active one. 141 | 142 | Macros provide a way to modularize that code; they work like functions. 143 | Let's look at how we'd mark the active link using a macro. 144 | 145 | :: 146 | 147 | {# myapp/templates/layout.html #} 148 | 149 | {% from "macros.html" import nav_link with context %} 150 | 151 | 152 | 153 | {% block head %} 154 | My application 155 | {% endblock %} 156 | 157 | 158 | 163 | {% block body %} 164 | {% endblock %} 165 | 166 | 167 | 168 | What we are doing in this template is calling an undefined macro — 169 | ``nav_link`` — and passing it two parameters: the target endpoint 170 | (i.e. the function name for the target view) and the text we want to 171 | show. 172 | 173 | .. note:: 174 | 175 | You may notice that we specified ``with context`` in the import 176 | statement. The Jinja **context** consists of the arguments passed to the 177 | ``render_template()`` function as well as the Jinja environment context 178 | from our Python code. These variables are made available in the template 179 | that is being rendered. 180 | 181 | Some variables are explicitly passed by us, e.g. 182 | ``render_template("index.html", color="red")``, but there are several 183 | variables and functions that Flask automatically includes in the 184 | context, e.g. ``request``, ``g`` and ``session``. When we say 185 | ``{% from ... import ... with context %}`` we are telling Jinja to make 186 | all of these variables available to the macro as well. 187 | 188 | .. note:: 189 | 190 | - All of the global variables that are passed to the Jinja context by 191 | Flask: http://flask.pocoo.org/docs/templating/#standard-context} 192 | - We can define variables and functions that we want to be merged into 193 | the Jinja context with context processors: 194 | http://flask.pocoo.org/docs/templating/#context-processors 195 | 196 | Now it's time to define the ``nav_link`` macro that we used in our template. 197 | 198 | :: 199 | 200 | {# myapp/templates/macros.html #} 201 | 202 | {% macro nav_link(endpoint, text) %} 203 | {% if request.endpoint.endswith(endpoint) %} 204 |
  • {{text}}
  • 205 | {% else %} 206 |
  • {{text}}
  • 207 | {% endif %} 208 | {% endmacro %} 209 | 210 | Now we've defined the macro in *myapp/templates/macros.html*. In this macro 211 | we're using Flask's ``request`` object — which is available in 212 | the Jinja context by default — to check whether or not the current 213 | request was routed to the endpoint passed to ``nav_link``. If it was, 214 | than we're currently on that page, and we can mark it as active. 215 | 216 | .. note:: 217 | 218 | The from x import y statement takes a relative path for x. If our 219 | template was in *myapp/templates/user/blog.html* we would use 220 | ``from "../macros.html" import nav_link with context``. 221 | 222 | Custom filters 223 | -------------- 224 | 225 | Jinja filters are functions that can be applied to the result of an 226 | expression in the ``{{ ... }}`` delimeters. It is applied before that 227 | result is printed to the template. 228 | 229 | :: 230 | 231 |

    {{ article.title|title }}

    232 | 233 | 234 | In this code, the ``title`` filter will take ``article.title`` and return 235 | a title-cased version, which will then be printed to the template. This 236 | looks and works a lot like the UNIX practice of "piping" the output of 237 | one program to another. 238 | 239 | .. note:: 240 | 241 | There are loads of built-in filters like ``title``. See `the full list `_ in the Jinja docs. 242 | 243 | We can define our own filters for use in our Jinja templates. As an 244 | example, we'll implement a simple ``caps`` filter to capitalize all of 245 | the letters in a string. 246 | 247 | .. note:: 248 | 249 | Jinja already has an ``upper`` filter that does this, and a ``capitalize`` filter that capitalizes the first character and lowercases the rest. These also handle unicode conversion, but we'll keep our example simple to focus on the concept at hand. 250 | 251 | We're going to define our filter in a module located at 252 | *myapp/util/filters.py*. This gives us a ``util`` package in which to 253 | put other miscellaneous modules. 254 | 255 | :: 256 | 257 | # myapp/util/filters.py 258 | 259 | from .. import app 260 | 261 | @app.template_filter() 262 | def caps(text): 263 | """Convert a string to all caps.""" 264 | return text.uppercase() 265 | 266 | In this code we are registering our function as a Jinja filter by using 267 | the ``@app.template_filter()`` decorator. The default filter name is 268 | just the name of the function, but you can pass an argument to the 269 | decorator to change that. 270 | 271 | :: 272 | 273 | @app.template_filter('make_caps') 274 | def caps(text): 275 | """Convert a string to all caps.""" 276 | return text.uppercase() 277 | 278 | Now we can call ``make_caps`` in the template rather than ``caps``: 279 | ``{{ "hello world!"|make_caps }}``. 280 | 281 | To make our filter available in the templates, we just need to import it 282 | in our top-level *\_\_init.py\_\_*. 283 | 284 | :: 285 | 286 | # myapp/__init__.py 287 | 288 | # Make sure app has been initialized first to prevent circular imports. 289 | from .util import filters 290 | 291 | Summary 292 | ------- 293 | 294 | - Use Jinja for templating. 295 | - Jinja has two kinds of delimeters: ``{% ... %}`` and ``{{ ... }}``. 296 | The first one is used to execute statements such as for-loops or 297 | assign values, the latter prints the result of the contained 298 | expression to the template. 299 | - Templates should go in *myapp/templates/* — i.e. a directory inside 300 | of the application package. 301 | - I recommend that the structure of the *templates/* directory mirror 302 | the URL structure of the app. 303 | - You should have a top-level *layout.html* in *myapp/templates* as 304 | well as one for each section of the site. The former extend the 305 | latter. 306 | - Macros are like functions made-up of template code. 307 | - Filters are functions made-up of Python code and used in templates. 308 | 309 | -------------------------------------------------------------------------------- /en/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/ajax-loader.gif -------------------------------------------------------------------------------- /en/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | div.sphinxsidebar #searchbox input[type="text"] { 83 | width: 170px; 84 | } 85 | 86 | div.sphinxsidebar #searchbox input[type="submit"] { 87 | width: 30px; 88 | } 89 | 90 | img { 91 | border: 0; 92 | max-width: 100%; 93 | } 94 | 95 | /* -- search page ----------------------------------------------------------- */ 96 | 97 | ul.search { 98 | margin: 10px 0 0 20px; 99 | padding: 0; 100 | } 101 | 102 | ul.search li { 103 | padding: 5px 0 5px 20px; 104 | background-image: url(file.png); 105 | background-repeat: no-repeat; 106 | background-position: 0 7px; 107 | } 108 | 109 | ul.search li a { 110 | font-weight: bold; 111 | } 112 | 113 | ul.search li div.context { 114 | color: #888; 115 | margin: 2px 0 0 30px; 116 | text-align: left; 117 | } 118 | 119 | ul.keywordmatches li.goodmatch a { 120 | font-weight: bold; 121 | } 122 | 123 | /* -- index page ------------------------------------------------------------ */ 124 | 125 | table.contentstable { 126 | width: 90%; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable dl, table.indextable dd { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | } 158 | 159 | table.indextable tr.pcap { 160 | height: 10px; 161 | } 162 | 163 | table.indextable tr.cap { 164 | margin-top: 10px; 165 | background-color: #f2f2f2; 166 | } 167 | 168 | img.toggler { 169 | margin-right: 3px; 170 | margin-top: 3px; 171 | cursor: pointer; 172 | } 173 | 174 | div.modindex-jumpbox { 175 | border-top: 1px solid #ddd; 176 | border-bottom: 1px solid #ddd; 177 | margin: 1em 0 1em 0; 178 | padding: 0.4em; 179 | } 180 | 181 | div.genindex-jumpbox { 182 | border-top: 1px solid #ddd; 183 | border-bottom: 1px solid #ddd; 184 | margin: 1em 0 1em 0; 185 | padding: 0.4em; 186 | } 187 | 188 | /* -- general body styles --------------------------------------------------- */ 189 | 190 | a.headerlink { 191 | visibility: hidden; 192 | } 193 | 194 | h1:hover > a.headerlink, 195 | h2:hover > a.headerlink, 196 | h3:hover > a.headerlink, 197 | h4:hover > a.headerlink, 198 | h5:hover > a.headerlink, 199 | h6:hover > a.headerlink, 200 | dt:hover > a.headerlink { 201 | visibility: visible; 202 | } 203 | 204 | div.body p.caption { 205 | text-align: inherit; 206 | } 207 | 208 | div.body td { 209 | text-align: left; 210 | } 211 | 212 | .field-list ul { 213 | padding-left: 1em; 214 | } 215 | 216 | .first { 217 | margin-top: 0 !important; 218 | } 219 | 220 | p.rubric { 221 | margin-top: 30px; 222 | font-weight: bold; 223 | } 224 | 225 | img.align-left, .figure.align-left, object.align-left { 226 | clear: left; 227 | float: left; 228 | margin-right: 1em; 229 | } 230 | 231 | img.align-right, .figure.align-right, object.align-right { 232 | clear: right; 233 | float: right; 234 | margin-left: 1em; 235 | } 236 | 237 | img.align-center, .figure.align-center, object.align-center { 238 | display: block; 239 | margin-left: auto; 240 | margin-right: auto; 241 | } 242 | 243 | .align-left { 244 | text-align: left; 245 | } 246 | 247 | .align-center { 248 | text-align: center; 249 | } 250 | 251 | .align-right { 252 | text-align: right; 253 | } 254 | 255 | /* -- sidebars -------------------------------------------------------------- */ 256 | 257 | div.sidebar { 258 | margin: 0 0 0.5em 1em; 259 | border: 1px solid #ddb; 260 | padding: 7px 7px 0 7px; 261 | background-color: #ffe; 262 | width: 40%; 263 | float: right; 264 | } 265 | 266 | p.sidebar-title { 267 | font-weight: bold; 268 | } 269 | 270 | /* -- topics ---------------------------------------------------------------- */ 271 | 272 | div.topic { 273 | border: 1px solid #ccc; 274 | padding: 7px 7px 0 7px; 275 | margin: 10px 0 10px 0; 276 | } 277 | 278 | p.topic-title { 279 | font-size: 1.1em; 280 | font-weight: bold; 281 | margin-top: 10px; 282 | } 283 | 284 | /* -- admonitions ----------------------------------------------------------- */ 285 | 286 | div.admonition { 287 | margin-top: 10px; 288 | margin-bottom: 10px; 289 | padding: 7px; 290 | } 291 | 292 | div.admonition dt { 293 | font-weight: bold; 294 | } 295 | 296 | div.admonition dl { 297 | margin-bottom: 0; 298 | } 299 | 300 | p.admonition-title { 301 | margin: 0px 10px 5px 0px; 302 | font-weight: bold; 303 | } 304 | 305 | div.body p.centered { 306 | text-align: center; 307 | margin-top: 25px; 308 | } 309 | 310 | /* -- tables ---------------------------------------------------------------- */ 311 | 312 | table.docutils { 313 | border: 0; 314 | border-collapse: collapse; 315 | } 316 | 317 | table.docutils td, table.docutils th { 318 | padding: 1px 8px 1px 5px; 319 | border-top: 0; 320 | border-left: 0; 321 | border-right: 0; 322 | border-bottom: 1px solid #aaa; 323 | } 324 | 325 | table.field-list td, table.field-list th { 326 | border: 0 !important; 327 | } 328 | 329 | table.footnote td, table.footnote th { 330 | border: 0 !important; 331 | } 332 | 333 | th { 334 | text-align: left; 335 | padding-right: 5px; 336 | } 337 | 338 | table.citation { 339 | border-left: solid 1px gray; 340 | margin-left: 1px; 341 | } 342 | 343 | table.citation td { 344 | border-bottom: none; 345 | } 346 | 347 | /* -- other body styles ----------------------------------------------------- */ 348 | 349 | ol.arabic { 350 | list-style: decimal; 351 | } 352 | 353 | ol.loweralpha { 354 | list-style: lower-alpha; 355 | } 356 | 357 | ol.upperalpha { 358 | list-style: upper-alpha; 359 | } 360 | 361 | ol.lowerroman { 362 | list-style: lower-roman; 363 | } 364 | 365 | ol.upperroman { 366 | list-style: upper-roman; 367 | } 368 | 369 | dl { 370 | margin-bottom: 15px; 371 | } 372 | 373 | dd p { 374 | margin-top: 0px; 375 | } 376 | 377 | dd ul, dd table { 378 | margin-bottom: 10px; 379 | } 380 | 381 | dd { 382 | margin-top: 3px; 383 | margin-bottom: 10px; 384 | margin-left: 30px; 385 | } 386 | 387 | dt:target, .highlighted { 388 | background-color: #fbe54e; 389 | } 390 | 391 | dl.glossary dt { 392 | font-weight: bold; 393 | font-size: 1.1em; 394 | } 395 | 396 | .field-list ul { 397 | margin: 0; 398 | padding-left: 1em; 399 | } 400 | 401 | .field-list p { 402 | margin: 0; 403 | } 404 | 405 | .optional { 406 | font-size: 1.3em; 407 | } 408 | 409 | .versionmodified { 410 | font-style: italic; 411 | } 412 | 413 | .system-message { 414 | background-color: #fda; 415 | padding: 5px; 416 | border: 3px solid red; 417 | } 418 | 419 | .footnote:target { 420 | background-color: #ffa; 421 | } 422 | 423 | .line-block { 424 | display: block; 425 | margin-top: 1em; 426 | margin-bottom: 1em; 427 | } 428 | 429 | .line-block .line-block { 430 | margin-top: 0; 431 | margin-bottom: 0; 432 | margin-left: 1.5em; 433 | } 434 | 435 | .guilabel, .menuselection { 436 | font-family: sans-serif; 437 | } 438 | 439 | .accelerator { 440 | text-decoration: underline; 441 | } 442 | 443 | .classifier { 444 | font-style: oblique; 445 | } 446 | 447 | abbr, acronym { 448 | border-bottom: dotted 1px; 449 | cursor: help; 450 | } 451 | 452 | /* -- code displays --------------------------------------------------------- */ 453 | 454 | pre { 455 | overflow: auto; 456 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 457 | } 458 | 459 | td.linenos pre { 460 | padding: 5px 0px; 461 | border: 0; 462 | background-color: transparent; 463 | color: #aaa; 464 | } 465 | 466 | table.highlighttable { 467 | margin-left: 0.5em; 468 | } 469 | 470 | table.highlighttable td { 471 | padding: 0 0.5em 0 0.5em; 472 | } 473 | 474 | tt.descname { 475 | background-color: transparent; 476 | font-weight: bold; 477 | font-size: 1.2em; 478 | } 479 | 480 | tt.descclassname { 481 | background-color: transparent; 482 | } 483 | 484 | tt.xref, a tt { 485 | background-color: transparent; 486 | font-weight: bold; 487 | } 488 | 489 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 490 | background-color: transparent; 491 | } 492 | 493 | .viewcode-link { 494 | float: right; 495 | } 496 | 497 | .viewcode-back { 498 | float: right; 499 | font-family: sans-serif; 500 | } 501 | 502 | div.viewcode-block:target { 503 | margin: -1px -10px; 504 | padding: 0 10px; 505 | } 506 | 507 | /* -- math display ---------------------------------------------------------- */ 508 | 509 | img.math { 510 | vertical-align: middle; 511 | } 512 | 513 | div.body div.math p { 514 | text-align: center; 515 | } 516 | 517 | span.eqno { 518 | float: right; 519 | } 520 | 521 | /* -- printout stylesheet --------------------------------------------------- */ 522 | 523 | @media print { 524 | div.document, 525 | div.documentwrapper, 526 | div.bodywrapper { 527 | margin: 0 !important; 528 | width: 100%; 529 | } 530 | 531 | div.sphinxsidebar, 532 | div.related, 533 | div.footer, 534 | #top-link { 535 | display: none; 536 | } 537 | } -------------------------------------------------------------------------------- /en/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/comment-bright.png -------------------------------------------------------------------------------- /en/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/comment-close.png -------------------------------------------------------------------------------- /en/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/comment.png -------------------------------------------------------------------------------- /en/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:"\f02d"}.icon-book:before{content:"\f02d"}.fa-caret-down:before{content:"\f0d7"}.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} 2 | -------------------------------------------------------------------------------- /en/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /** 95 | * Small JavaScript module for the documentation. 96 | */ 97 | var Documentation = { 98 | 99 | init : function() { 100 | this.fixFirefoxAnchorBug(); 101 | this.highlightSearchWords(); 102 | this.initIndexTable(); 103 | }, 104 | 105 | /** 106 | * i18n support 107 | */ 108 | TRANSLATIONS : {}, 109 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 110 | LOCALE : 'unknown', 111 | 112 | // gettext and ngettext don't access this so that the functions 113 | // can safely bound to a different name (_ = Documentation.gettext) 114 | gettext : function(string) { 115 | var translated = Documentation.TRANSLATIONS[string]; 116 | if (typeof translated == 'undefined') 117 | return string; 118 | return (typeof translated == 'string') ? translated : translated[0]; 119 | }, 120 | 121 | ngettext : function(singular, plural, n) { 122 | var translated = Documentation.TRANSLATIONS[singular]; 123 | if (typeof translated == 'undefined') 124 | return (n == 1) ? singular : plural; 125 | return translated[Documentation.PLURALEXPR(n)]; 126 | }, 127 | 128 | addTranslations : function(catalog) { 129 | for (var key in catalog.messages) 130 | this.TRANSLATIONS[key] = catalog.messages[key]; 131 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 132 | this.LOCALE = catalog.locale; 133 | }, 134 | 135 | /** 136 | * add context elements like header anchor links 137 | */ 138 | addContextElements : function() { 139 | $('div[id] > :header:first').each(function() { 140 | $('\u00B6'). 141 | attr('href', '#' + this.id). 142 | attr('title', _('Permalink to this headline')). 143 | appendTo(this); 144 | }); 145 | $('dt[id]').each(function() { 146 | $('\u00B6'). 147 | attr('href', '#' + this.id). 148 | attr('title', _('Permalink to this definition')). 149 | appendTo(this); 150 | }); 151 | }, 152 | 153 | /** 154 | * workaround a firefox stupidity 155 | */ 156 | fixFirefoxAnchorBug : function() { 157 | if (document.location.hash && $.browser.mozilla) 158 | window.setTimeout(function() { 159 | document.location.href += ''; 160 | }, 10); 161 | }, 162 | 163 | /** 164 | * highlight the search words provided in the url in the text 165 | */ 166 | highlightSearchWords : function() { 167 | var params = $.getQueryParameters(); 168 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 169 | if (terms.length) { 170 | var body = $('div.body'); 171 | if (!body.length) { 172 | body = $('body'); 173 | } 174 | window.setTimeout(function() { 175 | $.each(terms, function() { 176 | body.highlightText(this.toLowerCase(), 'highlighted'); 177 | }); 178 | }, 10); 179 | $('') 181 | .appendTo($('#searchbox')); 182 | } 183 | }, 184 | 185 | /** 186 | * init the domain index toggle buttons 187 | */ 188 | initIndexTable : function() { 189 | var togglers = $('img.toggler').click(function() { 190 | var src = $(this).attr('src'); 191 | var idnum = $(this).attr('id').substr(7); 192 | $('tr.cg-' + idnum).toggle(); 193 | if (src.substr(-9) == 'minus.png') 194 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 195 | else 196 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 197 | }).css('display', ''); 198 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 199 | togglers.click(); 200 | } 201 | }, 202 | 203 | /** 204 | * helper function to hide the search marks again 205 | */ 206 | hideSearchWords : function() { 207 | $('#searchbox .highlight-link').fadeOut(300); 208 | $('span.highlighted').removeClass('highlighted'); 209 | }, 210 | 211 | /** 212 | * make the url absolute 213 | */ 214 | makeURL : function(relativeURL) { 215 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 216 | }, 217 | 218 | /** 219 | * get the current relative url 220 | */ 221 | getCurrentURL : function() { 222 | var path = document.location.pathname; 223 | var parts = path.split(/\//); 224 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 225 | if (this == '..') 226 | parts.pop(); 227 | }); 228 | var url = parts.join('/'); 229 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 230 | } 231 | }; 232 | 233 | // quick alias for translations 234 | _ = Documentation.gettext; 235 | 236 | $(document).ready(function() { 237 | Documentation.init(); 238 | }); 239 | -------------------------------------------------------------------------------- /en/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/down-pressed.png -------------------------------------------------------------------------------- /en/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/down.png -------------------------------------------------------------------------------- /en/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/file.png -------------------------------------------------------------------------------- /en/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /en/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /en/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /en/_static/images/balanced-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/balanced-logo.png -------------------------------------------------------------------------------- /en/_static/images/blueprints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/blueprints.png -------------------------------------------------------------------------------- /en/_static/images/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/configuration.png -------------------------------------------------------------------------------- /en/_static/images/conventions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/conventions.png -------------------------------------------------------------------------------- /en/_static/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/cover.png -------------------------------------------------------------------------------- /en/_static/images/deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/deployment.png -------------------------------------------------------------------------------- /en/_static/images/environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/environment.png -------------------------------------------------------------------------------- /en/_static/images/forms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/forms.png -------------------------------------------------------------------------------- /en/_static/images/me-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/me-box.png -------------------------------------------------------------------------------- /en/_static/images/organizing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/organizing.png -------------------------------------------------------------------------------- /en/_static/images/static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/static.png -------------------------------------------------------------------------------- /en/_static/images/storing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/storing.png -------------------------------------------------------------------------------- /en/_static/images/templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/templates.png -------------------------------------------------------------------------------- /en/_static/images/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/users.png -------------------------------------------------------------------------------- /en/_static/images/views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/images/views.png -------------------------------------------------------------------------------- /en/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | $( document ).ready(function() { 2 | // Shift nav in mobile when clicking the menu. 3 | $(document).on('click', "[data-toggle='wy-nav-top']", function() { 4 | $("[data-toggle='wy-nav-shift']").toggleClass("shift"); 5 | $("[data-toggle='rst-versions']").toggleClass("shift"); 6 | }); 7 | // Close menu when you click a link. 8 | $(document).on('click', ".wy-menu-vertical .current ul li a", function() { 9 | $("[data-toggle='wy-nav-shift']").removeClass("shift"); 10 | $("[data-toggle='rst-versions']").toggleClass("shift"); 11 | }); 12 | $(document).on('click', "[data-toggle='rst-current-version']", function() { 13 | $("[data-toggle='rst-versions']").toggleClass("shift-up"); 14 | }); 15 | // Make tables responsive 16 | $("table.docutils:not(.field-list)").wrap("
    "); 17 | }); 18 | 19 | window.SphinxRtdTheme = (function (jquery) { 20 | var stickyNav = (function () { 21 | var navBar, 22 | win, 23 | stickyNavCssClass = 'stickynav', 24 | applyStickNav = function () { 25 | if (navBar.height() <= win.height()) { 26 | navBar.addClass(stickyNavCssClass); 27 | } else { 28 | navBar.removeClass(stickyNavCssClass); 29 | } 30 | }, 31 | enable = function () { 32 | applyStickNav(); 33 | win.on('resize', applyStickNav); 34 | }, 35 | init = function () { 36 | navBar = jquery('nav.wy-nav-side:first'); 37 | win = jquery(window); 38 | }; 39 | jquery(init); 40 | return { 41 | enable : enable 42 | }; 43 | }()); 44 | return { 45 | StickyNav : stickyNav 46 | }; 47 | }($)); 48 | -------------------------------------------------------------------------------- /en/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/minus.png -------------------------------------------------------------------------------- /en/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/plus.png -------------------------------------------------------------------------------- /en/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /en/_static/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= 11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /en/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/up-pressed.png -------------------------------------------------------------------------------- /en/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/_static/up.png -------------------------------------------------------------------------------- /en/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Index — Explore Flask 1.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
    42 | 43 | 44 | 155 | 156 |
    157 | 158 | 159 | 163 | 164 | 165 | 166 |
    167 |
    168 |
    169 |
      170 |
    • Docs »
    • 171 | 172 |
    • 173 |
    • 174 | 175 |
    • 176 |
    177 |
    178 |
    179 |
    180 | 181 | 182 |

    Index

    183 | 184 |
    185 | 186 |
    187 | 188 | 189 |
    190 |
    191 | 192 | 193 |
    194 | 195 |
    196 |

    197 | © Copyright 2014, Robert Picard. 198 |

    199 |
    200 | 201 | Sphinx theme provided by Read the Docs 202 |
    203 |
    204 |
    205 | 206 |
    207 | 208 |
    209 | 210 | 211 | 212 | 213 | 214 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 241 | 242 | 243 | 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /en/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/en/objects.inv -------------------------------------------------------------------------------- /en/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Search — Explore Flask 1.0 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
    41 | 42 | 43 | 154 | 155 |
    156 | 157 | 158 | 162 | 163 | 164 | 165 |
    166 |
    167 |
    168 |
      169 |
    • Docs »
    • 170 | 171 |
    • 172 |
    • 173 | 174 |
    • 175 |
    176 |
    177 |
    178 |
    179 | 180 | 188 | 189 | 190 |
    191 | 192 |
    193 | 194 |
    195 |
    196 | 197 | 198 |
    199 | 200 |
    201 |

    202 | © Copyright 2014, Robert Picard. 203 |

    204 |
    205 | 206 | Sphinx theme provided by Read the Docs 207 |
    208 |
    209 |
    210 | 211 |
    212 | 213 |
    214 | 215 | 216 | 217 | 218 | 219 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 247 | 248 | 251 | 252 | 253 | 254 | 255 | 265 | 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /zh/1-introduction.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 本书旨在展示使用 Flask 的最佳实践。 4 | 开发一个普通的 Flask 应用需要跟形形色色的领域打交道。比如,你经常需要操作数据库,验证用户。 5 | 在接下来的几页里我将尽我所能来介绍处理这些事情时的“正确之道”。 6 | 我的建议并不总能有用,但我希望它们在大多数情况下都是一个好选择。 7 | 8 | ## 假设 9 | 10 | 为了给你提供更贴切的建议,我将基于几个基本的假设撰写本书。当你阅读并在自己的项目中应用这些建议时,请勿忘这一点。 11 | 12 | ### 受众 13 | 14 | 本书的内容基于官方文档之上。我强烈建议你深入阅读[官方用户指南](http://flask.pocoo.org/docs/#user-s-guide)和[新手教程](http://flask.pocoo.org/docs/tutorial/)。这将给你一个更熟悉 Flask 的机会。你至少需要知道什么是 view,Jinja 模板的基础知识以及新手应有的其他基本概念。我会尽量避免重提用户指南中存在的信息,所以如果直接阅读本书,你就会有对阅读官方文档的急迫需求(这不错吧?)。 15 | 16 | 虽然这么说,本书涉及的主题并不高深。本书仅仅是强调减轻开发者负担的最佳实践和模式。尽量避免啰嗦官方文档中提到的内容的同时,我也会再次强调一些概念来加深印象。 17 | 在阅读这部分内容时,你不需要重读新手教程。 18 | 19 | ### 版本 20 | 21 | #### Python 2 还是 Python 3 22 | 23 | 当我写下此文,Python 社区正处于从 Python 2 迁移到 Python 3 的动荡之中。Python Software Foundation 的官方态度如下: 24 | 25 | > Python 2.x is the status quo, Python 3.x is the present and future of the language.[Python wiki: python2还是python3](http://wiki.python.org/moin/Python2orPython3) 26 | 27 | 到了版本 0.10,Flask 现在可以在 Python 3.3 上运行。就新的 Flask 应用是否需要使用 Python 3 的问题,我问过 Armin Ronacher,他回答说,这不是必须的: 28 | 29 | > 我自己现在并不用它,我也不会向别人推荐自己都不相信的东西,所以我不会推荐Python 3. 30 | > -- Armin Ronacher, Flask作者[我和Armin Ronacher的对话](https://www.youtube.com/watch?feature=player_detailpage&v=fs20qdvm0K4#t=190) 31 | 32 | 主要的理由在于许多常用的包没有 Python 3 的版本。 33 | 你总不会愿意接受用 Python 3 开发了几个月后发现自己不能使用包 X, Y, Z…… 34 | 也许总有一天 Flask 官方将推荐用 Python 3 开始新的项目,但是现在依然是 Python 2 的天下。 35 | 36 | **另注** 37 | 38 | [Python 3 Wall of Superpowers](https://python3wos.appspot.com/)记录了一些已经移植到 Python 3 的包。 39 | 40 | 既然本书需要提供实践上的建议,我将假定你正使用 Python 2。 41 | 更准确地说,我将基于 Python 2.7 撰写本书。随着 Flask 社区的变迁,将来的更新会改变这一点,但是在当下,我们依然活在 Python 2.7 的世界里。 42 | 43 | #### Flask 版本 0.10 44 | 45 | 正当本书撰写之时,0.10 是 Flask 的最新版本(准确说,是 0.10.1 版)。本书中大多数内容不会受到 Flask 的较小的变动的影响,但是你需要了解这一点。 46 | 47 | ## 持续集成 48 | 49 | 本书的内容将持续更新,而不是周期性发布。 50 | 这样做有一个好处,内容可以得到及时地更新,而不会待字闺中。 51 | 所以这个网站内容将会比印刷版甚至 PDF 更加前沿。 52 | 53 | ## 本书用到的约定 54 | 55 | ### 各章独立成文 56 | 57 | 本书的每一章独立成文。许多书和教程通篇浑然一体。通常这意味着,一个示范程序或一个应用的创建和更新将贯穿全书来展示概念和主题。 58 | 本书的范例将散布于每一章节来展示概念,但不意味着这些范例可以组合成一个大的项目。 59 | 60 | ### 格式 61 | 62 | 示例代码将以代码块形式来呈现。 63 | 64 | ```python 65 | print “Hello world!” 66 | ``` 67 | 68 | 目录列表有时会被用来展示一个应用或目录的大致结构。 69 | 70 | ``` 71 | static/ 72 | style.css 73 | logo.png 74 | vendor/ 75 | jquery.min.js 76 | ``` 77 | 78 | 脚注会用于引用中,这样就不会跟正文混乱起来了。 79 | 80 | *斜体* 将用来表示文件名。 81 | 82 | **粗体** 将用来表示新的或重要的内容。 83 | 84 | > **注意** 85 | > 这里会有容易掉进去(而且会造成大问题)的坑。 86 | 87 | --- 88 | 89 | > **参见** 90 | > 这里会有一些补充信息。 91 | 92 | ## 总结 93 | 94 | * 本书包含了使用 Flask 的最佳实践。 95 | * 我假定你通读了 Flask 教程 96 | * 本书基于 Python 2.7 97 | * 本书基于 Flask 0.10 98 | * 通过年度审查,我尽量让本书的内容保持更新。 99 | * 本书中每一章独立成文。 100 | * 我通过一些约定来表达跟内容相关的附加信息。 101 | * 每章的结尾都会出现对本章内容的总结。 102 | -------------------------------------------------------------------------------- /zh/10-storing-data.md: -------------------------------------------------------------------------------- 1 | ![存储](images/storing.png) 2 | 3 | # 存储 4 | 5 | 大多数 Flask 应用都将要跟数据打交道。有很多种不同的方法存储数据。至于哪种最优,取决于数据的类型。如果你储存的是关系性数据(比如一个用户有多个邮件,一个邮件对应一个用户),关系型数据库无疑是你的选择。其他类型的数据也许适合储存到 NoSQL 数据库(比如 MongoDB)中。 6 | 7 | 我不会告诉你如何为你的应用选择数据库。如果有人告诉你,NoSQL 是你的唯一选择;那么必然也会有人建议用关系型数据库处理同样的问题。对此我唯一需要说的是,如果你不清楚,关系型数据库(MySQL, PostgreSQL 等等)将满足你绝大部分的需求。 8 | 9 | 另外,当你使用关系型数据库,你就能用到 SQLAlchemy,而 SQLAlchemy 用起来真爽。 10 | 11 | ## SQLAlchemy 12 | 13 | SQLAlchemy 是一个 ORM([对象关系映射](http://zh.wikipedia.org/wiki/%E5%AF%B9%E8%B1%A1%E5%85%B3%E7%B3%BB%E6%98%A0%E5%B0%84))。基于对目标数据库的原生 SQL 的抽象,它提供了与一长串数据库引擎的一致的 API。这一列表中包括 MySQL,PostgreSQL,和 SQLite。这使得在你的模型和数据库间交换数据变得轻松愉快,同时也使得诸如换掉数据库引擎和迁移数据库模式等其他事情变得没那么繁琐。 14 | 15 | 存在一个很棒的 Flask 插件使得在 Flask 中使用 SQLAlchemy 更为轻松。它就是 Flask-SQLAlchemy。Flask-SQLAlchemy 为 SQLAlchemy 设置了许多合理的配置。它也内置了一些 session 管理,这样你就不用在应用代码里处理这种基础事务了。 16 | 17 | 让我们深入看看一些代码。我们将先定义一些模型,接着配置下 SQLAchemy。这些模型将位于 *myapp/models.py*,不过首先我们要在 *myapp/\_\_init\_\_.py* 定义我们的数据库。 18 | 19 | myapp/\_\_init\_\_.py_ 20 | ``` 21 | from flask import Flask 22 | from flask_sqlalchemy import SQLAlchemy 23 | 24 | app = Flask(__name__, instance_relative_config=True) 25 | 26 | app.config.from_object('config') 27 | app.config.from_pyfile('config.py') 28 | 29 | db = SQLAlchemy(app) 30 | ``` 31 | 32 | 我们首先初始化并配置你的 Flask 应用,然后用它来初始化你的 SQLAlchemy 数据库处理程序。我们将为数据库配置使用一个 instance 文件夹,所以我们应该在初始化应用时加上 `instance_relative_config` 选项,然后调用 `app.config.from_pyfile`。现在我们可以定义模型了。 33 | 34 | _myapp/models.py_ 35 | ``` 36 | from . import db 37 | 38 | class Engine(db.Model): 39 | 40 | # Columns 41 | 42 | id = db.Column(db.Integer, primary_key=True, autoincrement=True) 43 | 44 | title = db.Column(db.String(128)) 45 | 46 | thrust = db.Column(db.Integer, default=0) 47 | ``` 48 | 49 | `Column`,`Integer`,`String`,`Model` 和其他的 SQLAlchemy 类都可以通过由 Flask-SQLAlchemy 构造的 `db` 对象访问。我们会定义一个储存我们的太空飞船引擎的当前状态的模型。每个引擎有一个 ID,一个标题和一个推力等级。 50 | 51 | 我们需要往我们的配置添加一些数据库信息。我们打算使用一个 instance 文件夹来避免配置变量被记录进版本控制系统,所以我们要把它们放入 *instance/config.py*。 52 | 53 | _instance/config.py_ 54 | ``` 55 | SQLALCHEMY_DATABASE_URI = "postgresql://user:password@localhost/spaceshipDB" 56 | ``` 57 | 58 | > **注意** 59 | > 你的数据库URI将取决于你选择的数据库和它部署的位置。看一下这个相关的SQLAlchemy文档: 60 | > 61 | 62 | ## 初始化数据库 63 | 64 | 既然数据库已经配置好了,而模型也定义了,是时候初始化数据库了。这个步骤从由模型定义中创建数据库模式开始。 65 | 66 | 通常这会是非常痛苦的过程。不过幸运的是,SQLAlchemy 提供了一个十分酷的工具帮我们完成了所有的琐事。 67 | 68 | 让我们在版本库的根目录下打开一个 Python 终端。 69 | 70 | ``` 71 | $ pwd 72 | /Users/me/Code/myapp 73 | $ workon myapp 74 | (myapp)$ python 75 | Python 2.7.5 (default, Aug 25 2013, 00:04:04) 76 | [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin 77 | Type "help", "copyright", "credits" or "license" for more information. 78 | >>> from myapp import db 79 | >>> db.create_all() 80 | >>> 81 | ``` 82 | 83 | 现在,感谢 SQLAlchemy,你会发现在你配置的数据库中,所需的表格已经被创建出来了。 84 | 85 | ## Alembic 迁移工具 86 | 87 | 数据库的模式并非亘古不变的。举个例子,你可能需要在引擎的表里添加一个 `last_fired` 的项。如果这个表是一张白纸,你只需要更新模型并重新运行 `db.create_all()`。然而,如果你在引擎表里记录了六个月的数据,你肯定不会想要从头开始。这时候就需要数据库迁移工具了。 88 | 89 | Alembic 是专用于 SQLAlchemy 的数据库迁移工具。它允许你保持你的数据库模式的版本历史,这样你就可以升级到一个新的模式,或者降级到旧的模式。 90 | 91 | Alembic 有一个可拓展的新手教程,所以我只会大概地说一下并指出一些需要注意的事项。 92 | 93 | 通过一个初始化的 `alembic init` 命令,你将创建一个 alembic "迁移环境"。在你的版本库的根目录下执行这个命令,你将得到一个叫 `alembic` 的新文件夹。你的版本库将看上去就像 Alembic 教程中的这个例子一样: 94 | 95 | ``` 96 | myapp/ 97 | alembic.ini 98 | alembic/ 99 | env.py 100 | README 101 | script.py.mako 102 | versions/ 103 | 3512b954651e_add_account.py 104 | 2b1ae634e5cd_add_order_id.py 105 | 3adcc9a56557_rename_username_field.py 106 | myapp/ 107 | __init__.py 108 | views.py 109 | models.py 110 | templates/ 111 | run.py 112 | config.py 113 | requirements.txt 114 | 115 | ``` 116 | 117 | *alembic/* 文件夹中包括了在版本间迁移数据的脚本。同时会有一个包括配置信息的 *alembic.ini* 文件。 118 | 119 | > **注意** 120 | > 把*alembic.ini*添加到*.gitignore*中!在那里会有你的数据库凭证,所以你*不应该*把它留在版本控制中。 121 | 122 | > 不过你可以把*alembic/*放进版本控制。它不会包含敏感信息(而且不能从你的源代码中重新生成),并且在版本控制中保存多个副本可以避免你的电脑发生不测。 123 | 124 | 当数据库模式需要发生变化时,我们需要做一系列事情。首先,运行 `alembic revision` 来生成迁移脚本。在 *myapp/alembic/versions/* 打开新生成的 Python 文件并使用 Alembic 的 `op` 对象完成 `upgrade` 和 `downgrade` 函数。 125 | 126 | 一旦我们的迁移脚本已经准备就绪,我们只需运行 `alembic upgrade head` 来迁移我们的数据到最新版本。 127 | 128 | > **参见** 129 | > 想知道更多关于配置Alembic,创建你的迁移脚本和运行你的迁移,请看Alembic教程: 130 | > 131 | 132 | > **注意** 133 | > 不要忘记设定数据的备份计划。备份计划的话题已经超出本书的范围,但你应该总是要有一个安全和健壮的方式备份你的数据库。 134 | 135 | > **注意** 136 | > Flask在NoSQL上的支持较少,但只要有你选择的数据库引擎有对应的Python库,你就能够用上它。这里有一些Flask插件,可以给Flask提供NoSQL引擎的支持。 137 | > 138 | 139 | ## 总结 140 | 141 | - 使用 SQLAchemy 来搭配关系型数据库。 142 | - 使用 Flask-SQLAlchemy 来包装 SQLAlchemy。 143 | - Alembic 会在数据库模式改变时帮助你管理数据迁移。 144 | - 你可以用 NoSQL 搭配 Flask,但具体做法取决于具体引擎。 145 | - 记得备份你的数据! 146 | -------------------------------------------------------------------------------- /zh/11-handling_forms.md: -------------------------------------------------------------------------------- 1 | ![处理表单](images/forms.png) 2 | 3 | # 处理表单 4 | 5 | 表单是允许用户跟你的 web 应用交互的基本元素。Flask 自己不会帮你处理表单,但 Flask-WTF 插件允许用户在 Flask 应用中使用脍炙人口的 WTForms 包。这个包使得定义表单和处理表单功能变得轻松。 6 | 7 | ## Flask-WTF 8 | 9 | 你首要做的事(当然是在安装 Flask-WTF 之后),就是在 `myapp.forms` 包下定义一个表单类(form)。 10 | 11 | _myapp/forms.py_ 12 | ```python 13 | from flask_wtf import Form 14 | from wtforms import StringField, PasswordField 15 | from wtforms.validators import DataRequired, Email 16 | 17 | class EmailPasswordForm(Form): 18 | email = StringField('Email', validators=[DataRequired(), Email()]) 19 | password = PasswordField('Password', validators=[DataRequired()]) 20 | ``` 21 | 22 | > **注意** 23 | > 直到0.9版,Flask-WTF为WTForms的fields和validators提供自己的包装。你可能见过许多代码直接从`flask_wtforms`而不是`wtforms`中直接导入`TextField`,`PasswordField`等等。 24 | > 而从0.9版之后,我们得直接从`wtforms`中导入它们。 25 | 26 | 这个表单将用于用户注册表单。我们可以称之为 `SignInForm()`,但是通过保持抽象,我们可以在别的地方重用它,比如作为登录表单。如果我们针对特定功能定义表单,最终就会得到许多相似却无法重用的表单。基于表单中包含的域 - 那些使得表单与众不同的元素 - 进行命名,显然会清晰很多。当然,有时候你会有复杂的,只在一个地方用到的表单,你再给它起个独一无二的名字也不迟。 27 | 28 | 这个表单可以帮我们做一些事情。它可以保护我们的应用免遭 CSRF 伤害,验证用户输入,为我们定义的域渲染适当的标记。 29 | 30 | ### CSRF 保护和验证 31 | 32 | CSRF 全称是 cross site request forgery,跨站请求伪造。CSRF 通过第三方伪造表单数据,post 到应用服务器上。受害服务器以为这些数据来自于它自己的网站,于是大意地中招了。 33 | 34 | 举个例子,假设你的邮件服务商允许你通过提交一个表单来注销账户。这个表单发送一个 POST 请求到他们服务器的 `account_delete` 页面,并且用户已经登录,就可以注销账户。你可以在你自己的网站中创建一个会发送到同一个 `account_delete` 页面的表单。现在,假如有个倒霉蛋点击了你的表单的 'submit'(或者在他们加载你的页面的时候通过 Javascript 做到这一点),同时他们又登录了邮件账号,那么他们的账户就会被注销。除非你的邮件服务商知道不能假定提交过来的请求都是来自于自己的页面。 35 | 36 | 所以我们怎样判断一个 POST 请求是否来自我们自己的表单呢?WTForms 在渲染每个表单时生成一个独一无二的 token,使得这一切变得可能。那个 token 将在 POST 请求中随表单数据一起传递,并且会在表单被接受之前进行验证。关键在于 token 的值取决于储存在用户的会话(cookies)中的一个值,而且会在一定时间之后过时(默认 30 分钟)。这样只有登录了页面的人(或至少是在那个设备之后的人)才能提交一个有效的表单,而且仅仅是在登录页面 30 分钟之内才能这么做。 37 | 38 | > **参见** 39 | > * 这里是关于WTForms是怎么生成token的文档: http://wtforms.simplecodes.com/docs/1.0.1/ext.html#module-wtforms.ext.csrf.session 40 | 41 | > * 这里有关于CSRF更多的信息: https://www.owasp.org/index.php/CSRF 42 | 43 | 为了开始使用 Flask-WTF 做 CSRF 防护,我们得先给我们的登录页定义一个视图。 44 | 45 | myapp/views.py 46 | ```python 47 | from flask import render_template, redirect, url_for 48 | 49 | from . import app 50 | from .forms import EmailPasswordForm 51 | 52 | @app.route('/login', methods=["GET", "POST"]) 53 | def login(): 54 | form = EmailPasswordForm() 55 | if form.validate_on_submit(): 56 | 57 | # Check the password and log the user in 58 | # [...] 59 | 60 | return redirect(url_for('index')) 61 | return render_template('login.html', form=form) 62 | ``` 63 | 64 | 我们从 `forms` 包中导入 form 对象,并于视图内实例化。然后运行 `form.validate_on_submit()`。如果表单已经 submit 了(比如通过 HTTP 方法 PUT 或 POST),这个函数返回 `True` 并且用定义在 *forms.py* 中的验证函数来验证表单。 65 | 66 | > **参见** 67 | > `validate_on_submit()`的文档和源码在此: 68 | > * http://flask-wtf.readthedocs.org/en/latest/api.html#flask_wtf.Form.validate_on_submit 69 | > * https://github.com/ajford/flask-wtf/blob/v0.8.4/flask_wtf/form.py#L120 70 | 71 | 如果一个表单已经提交并且通过验证,我们可以开始处理登录逻辑的部分了。如果它还没有提交(比如,它只是一个 GET 请求),我们需要传递这个表单对象给模板来进行渲染。下面展示如何在模板中使用 CSRF 防护。 72 | 73 | myapp/templates/login.html 74 | ``` 75 | {% extends "layout.html" %} 76 | 77 | 78 | Login Page 79 | 80 | 81 |
    82 | 83 | 84 | {{ form.csrf_token }} 85 |
    86 | 87 | 88 | ``` 89 | 90 | `{{ form.csrf_token }}` 将渲染一个隐藏的包括防范 CSRF 的特殊 token 的域,而 WTForms 会在验证表单时查找这个域。我们不用操心添加的任何特殊的验证 token 正确性的逻辑。万岁! 91 | 92 | #### 使用 CSRFtoken 来保护 AJAX 调用 93 | 94 | Flask-WTF 的 CSRF token 不仅限于保护表单提交。如果你的应用需要接受其他可能被伪造的请求(特别是 AJAX 调用),你也可以给它们添加 CSRF 保护!想了解更多信息,请查看 Flask-WTF 的文档:https://flask-wtf.readthedocs.org/en/latest/csrf.html#ajax 95 | 96 | ### 自定义验证函数 97 | 98 | 除了 WTForms 提供的内置表单验证函数(比如 `Required()`,`Email()` 等等),你可以创建自己的验证函数。通过创建一个可用于检查数据库并确保用户提供的值未曾存在的 `Unique()` 验证函数,我将展示这一点。这个函数可以确保一个用户名或邮件地址未被使用。如果没有 WTForms,我们不得不在视图中完成这些检查,但现在我们可以抽象出来作为 form 类的一部分。 99 | 100 | _myapp/forms.py_ 101 | ```python 102 | from flask_wtf import Form 103 | from wtforms import StringField, PasswordField, 104 | from wtforms.validators import DataRequired, Email 105 | 106 | class EmailPasswordForm(Form): 107 | email = StringField('Email', validators=[DataRequired(), Email()]) 108 | password = PasswordField('Password', validators=[DataRequired()]) 109 | ``` 110 | 111 | 现在我们想要添加一个验证函数来确认提供的邮件地址未曾出现在数据库中。我们将把验证函数放在一个新的 `util` 模块里,即 `util.validators`。 112 | 113 | _myapp/util/validators.py_ 114 | ``` 115 | from wtforms.validators import ValidationError 116 | 117 | class Unique(object): 118 | def __init__(self, model, field, message=u'该内容已经存在。'): 119 | self.model = model 120 | self.field = field 121 | 122 | def __call__(self, form, field): 123 | check = self.model.query.filter(self.field == field.data).first() 124 | if check: 125 | raise ValidationError(self.message) 126 | ``` 127 | 128 | 这个验证函数假定你是用 SQLAlchemy 来定义你的模型。WTForms 要求验证函数返回可调用的(callable)类型(比如一个可调用的类)。 129 | 130 | 在 *\_\_init\_\_.py* 中,我们可以指定哪些参数应该传递给验证函数。在这个例子中我们需要检查相关的模型(比如 `User` 模型)和域。当验证函数被调用时,如果表单提交的值跟定义的模型的某个实例重复了,它会抛出一个 `ValidationError`。我们也提供一个带默认值的信息参数,作为 `ValidationError` 的一部分。 131 | 132 | 现在我们给 `EmailPasswordForm` 添加 `Unique` 验证器。 133 | 134 | _myapp/forms.py_ 135 | ``` 136 | from flask_wtf import Form 137 | from wtforms import StringField, PasswordField, 138 | from wtforms.validators import DataRequired, Email 139 | 140 | from .util.validators import Unique 141 | from .models import User 142 | 143 | class EmailPasswordForm(Form): 144 | email = StringField('Email', validators=[DataRequired(), Email(), Unique(User, User.email, message='该邮箱已被用于注册']) 145 | password = PasswordField('Password', validators=[DataRequired()]) 146 | ``` 147 | 148 | > **注意** 149 | > 你的验证函数不一定需要是可调用的类。它也可以是一个返回可调用对象的工厂类或者可调用对象。看这里的一些例子: 150 | > http://wtforms.simplecodes.com/docs/0.6.2/validators.html#custom-validators 151 | 152 | ### 渲染表单 153 | 154 | WTForms 也可以帮助我们给我们只需要表单渲染 HTML。WTForms 实现的 `Field` 类能根据域的形式渲染对应的 HTML,所以我们只需要在模板中调用它们。就像是渲染 `csrf_token` 域一样。下面是当我们使用 WTForms 来渲染我们的其他域时,login 模板大概的样子。 155 | 156 | myapp/templates/login.html 157 | ``` 158 | {% extends "layout.html" %} 159 | 160 | 161 | Login Page 162 | 163 | 164 |
    165 | {{ form.email }} 166 | {{ form.password }} 167 | {{ form.csrf_token }} 168 |
    169 | 170 | 171 | ``` 172 | 173 | 通过传递域的性质(properties)作为调用域的参数,我们可以自定义域的渲染形式。下面我们添加一个 `placeholder=` 性质给 email 域: 174 | 175 | ``` 176 |
    177 | {{ form.email.label }}: {{ form.email(placeholder='yourname@email.com') }}
    178 | {{ form.password.label }}: {{ form.password }}
    179 | {{ form.csrf_token }} 180 |
    181 | ``` 182 | 183 | > **注意** 184 | > 如果我们想要传递HTML属性“class”, 我们得使用`class_=''`,因为“class”是Python的保留关键字。 185 | 186 | > **参见** 187 | > 这个文档列出了所有可用的域性质: 188 | > http://wtforms.simplecodes.com/docs/1.0.4/fields.html#wtforms.fields.Field.name 189 | 190 | > **注意** 191 | > 你大概注意到了我们不需要使用Jinja的`|safe`过滤器。这是因为WTForms自己会处理掉HTML转义的问题。在这里了解更多信息: 192 | > http://pythonhosted.org/Flask-WTF/#using-the-safe-filter 193 | 194 | ## 总结 195 | 196 | * 表单可能会是安全上的阿喀琉斯之踵。 197 | * WTForms(以及 Flask-WTF)使得定义,保护和渲染你的表单更加轻松。 198 | * 使用 Flask-WTF 提供的 CSRF 防范来保护你的表单。 199 | * 你也可以使用 Flask-WTF 来防止 AJAX 调用遭到 CSRF 攻击。 200 | * 定义自定义的表单验证函数,避免在视图函数中写入验证逻辑。 201 | * 使用 WTForms 的域渲染功能来渲染你的表单的 HTML,这样每次修改表单的定义时,你不需要更新模板。 202 | -------------------------------------------------------------------------------- /zh/13-deployment.md: -------------------------------------------------------------------------------- 1 | ![部署](images/deployment.png) 2 | 3 | # 部署 4 | 5 | 最终,你终于可以向全世界展示你的应用了。是时候部署它了。这个过程总能让人感到受挫,因为有太多任务需要完成。同时在部署的过程中你需要做出太多艰难的决定。我们会谈论一些关键的地方以及我们一些可能的选择。 6 | 7 | ## 托管主机 8 | 9 | 首先,你需要一个服务器。世上服务器提供商成千,但我只取三家。我不会谈论如何开始使用它们的服务的细节,因为这超出本书的范围。相反,我只会谈论它们作为 Flask 应用托管商上的优点。 10 | 11 | ### Amazon Web Services EC2(因为国情问题,让我们直接看下一个吧) 12 | 13 | Amazon Web Services 指的是一套相关的服务,提供商是……~~卓越~~ 亚马逊!今日,许多著名的初创公司选择使用它,所以你或许已经听过它的大名。AWS 服务中我们最关心的是 EC2,全称是 Elastic Compute Cloud。EC2 的最大的卖点是你能够获得虚拟主机,或者说实例(这是 AWS 官方称呼),在仅仅几秒之内。如果你需要快速拓展你的应用,就只需启动多一点 EC2 实例给你的应用,并且用一个负载平衡器(load balancer)管理它们。(这时还可以试试 AWS Elastic Load Balancer) 14 | 15 | 对于 Flask 而言,AWS 就是一个常规的虚拟主机。付上一些费用,你可以用你喜欢的 Linux 发行版启动它,并安上你的 Flask 应用。之后你的服务器就起来了。不过它意味着你需要一些系统管理知识。 16 | 17 | ### Heroku 18 | 19 | Heroku 是一个应用托管网站,基于诸如 EC2 的 AWS 的服务。他们允许你获得 EC2 的便利,而无需系统管理经验。 20 | 21 | 对于 Heroku,你通过 `git push` 来在它们的服务器上部署代码。这是非常便利的,如果你不想浪费时间 ssh 到服务器上,安装并配置软件,继续整个常规的部署流程。这种便利是需要花钱购买的,尽管 AWS 和 Heroku 都提供了一定量的免费服务。 22 | 23 | > **参见** 24 | > Heroku有一个如何在它们的服务器上部署Flask应用的教程: 25 | > 26 | 27 | > **注意** 28 | > 管理你自己的数据库将会花上许多时间,而把它做好也需要一些经验。通过配置你自己的站点来学习数据库管理是好的,但有时候你会想要外包给专业团队来省下时间和精力。Heroku和AWS都提供有数据库管理服务。我个人还没试过,但听说它们不错。如果你想要保障数据安全以及备份,却又不想要自己动手,值得考虑一下它们。 29 | 30 | > - Heroku Postgres: https://www.heroku.com/postgres 31 | > - Amazon RDS: https://aws.amazon.com/rds/ 32 | 33 | ### Digital Ocean 34 | 35 | Digital Ocean 是最近出现的 EC2 的竞争对手。一如 EC2,Digital Ocean 允许你快速地启动虚拟主机(在这里叫 droplet)。所有的 droplet 都运行在 SSD 上,而在 EC2,如果你用的是普通服务,你是享受不到这种待遇的。对我而言,最大的卖点是它提供的控制接口比 AWS 控制面板简单和容易多了。Digital Ocean 是我个人的最爱,我建议你考虑下它。 36 | 37 | 在 Digital Ocean,Flask 应用部署方式就跟在 EC2 一样。你会得到一个全新的 Linux 发行版,然后需要安装你的全套软件。 38 | 39 | ## 部署工具 40 | 41 | 这一节将包括一些为了向别人提供服务,你需要安装在服务器上的软件。最基本的是一个前置服务器,用来反向代理请求给一个运行你的 Flask 应用的应用容器。你通常也需要一个数据库,所以我们也会略微谈论下这方面的内容。 42 | 43 | ### 应用容器 44 | 45 | 在开发应用时,本地运行的那个服务器并不能处理真实的请求。当你真的需要向公众发布你的应用,你需要在应用容器,例如 Gunicorn,上运行它。Gunicorn 接待请求,并处理诸如线程的复杂事务。 46 | 47 | 要想使用 Gunicorn,需要通过 pip 安装 `gunicorn` 到你的虚拟环境中。运行你的应用只需简单的命令。为了简明起见,让我们假设这就是我们的 Flask 应用: 48 | 49 | _rocket.py_ 50 | ```python 51 | from flask import Flask 52 | 53 | app = Flask(__name__) 54 | 55 | @app.route('/') 56 | def index(): 57 | return "Hello World!" 58 | ``` 59 | 60 | 哦,这真简明扼要。现在,使用 Gunicorn 来运行它吧,我们只需执行这个命令: 61 | 62 | ``` 63 | (ourapp)$ gunicorn rocket:app 64 | 2014-03-19 16:28:54 [62924] [INFO] Starting gunicorn 18.0 65 | 2014-03-19 16:28:54 [62924] [INFO] Listening at: http://127.0.0.1:8000 (62924) 66 | 2014-03-19 16:28:54 [62924] [INFO] Using worker: sync 67 | 2014-03-19 16:28:54 [62927] [INFO] Booting worker with pid: 62927 68 | ``` 69 | 70 | 你应该能在 http://127.0.0.1:8000 看到“Hello World!”。 71 | 72 | 为了在后台运行这个服务器(也即使它变成守护进程),我们可以传递 `-D` 选项给 Gunicorn。这下它会持续运行,即使你关闭了当前的终端会话。 73 | 74 | 如果我们这么做了,当我们想要关闭服务器时就会困惑于到底应该关闭哪个进程。我们可以让 Gunicorn 把进程 ID 储存到文件中,这样如果想要停止或者重启服务器时,我们可以不用在一大串运行中的进程中搜索它。我们使用 `-p ` 选项来这么做。现在,我们的 Gunicorn 部署命令是这样: 75 | 76 | ``` 77 | (ourapp)$ gunicorn rocket:app -p rocket.pid -D 78 | (ourapp)$ cat rocket.pid 79 | 63101 80 | ``` 81 | 82 | 要想重新启动或者关闭服务器,我们可以运行对应的命令: 83 | 84 | ``` 85 | (ourapp)$ kill -HUP `cat rocket.pid` # 发送一个SIGHUP信号,终止进程 86 | (ourapp)$ kill `cat rocket.pid` 87 | ``` 88 | 89 | 默认下 Gunicorn 会运行在 8000 端口。如果这已经被另外的应用占用了,你可以通过添加 `-b` 选项来指定端口。 90 | 91 | ``` 92 | (ourapp)$ gunicorn rocket:app -p rocket.pid -b 127.0.0.1:7999 -D 93 | ``` 94 | 95 | #### 将 Gunicorn 摆上前台 96 | 97 | > **注意** 98 | > Gunicorn应该隐藏于反向代理之后。如果你直接让它监听来自外网的请求,它很容易成为拒绝服务攻击的目标。它不应该接受这样的考验。只有在debug的情况下你才能把Gunicorn摆上前台,而且完工之后,切记把它重新隐藏到幕后。 } 99 | 100 | 如果你像前面说的那样在服务器上运行 Gunicorn,将不能从本地系统中访问到它。这是因为默认情况下 Gunicorn 绑定在 127.0.0.1。这意味着它仅仅监听来自服务器自身的连接。所以通常使用一个反向代理来作为外网和 Gunicorn 服务器的中介。不过,假如为了 debug,你需要直接从外网发送请求给 Gunicorn,可以告诉 Gunicorn 绑定 0.0.0.0。这样它就会监听所有请求。 101 | 102 | ``` 103 | (ourapp)$ gunicorn rocket:app -p rocket.pid -b 0.0.0.0:8000 -D 104 | ``` 105 | 106 | > **注意** 107 | > - 从文档中可以读到更多关于运行和部署Gunicorn的信息 : http://docs.gunicorn.org/en/latest/ 108 | > - Fabric是一个可以允许你不通过SSH连接到每个服务器上就可以执行部署和管理命令的工具 : http://docs.fabfile.org/en/latest 109 | 110 | ### Nginx 反向代理 111 | 112 | 反向代理处理公共的 HTTP 请求,发送给 Gunicorn 并将响应带回给发送请求的客户端。Nginx 是一个优秀的客户端,更何况 Gunicorn 强烈建议我们使用它。 113 | 114 | 要想配置 Nginx 作为运行在 127.0.0.1:8000 的 Gunicorn 的反向代理,我们可以在 */etc/nginx/sites-available* 下给应用创建一个文件。不如称之为 *exploreflask.com* 吧。 115 | 116 | _/etc/nginx/sites-available/exploreflask.com_ 117 | ``` 118 | # Redirect www.exploreflask.com to exploreflask.com 119 | server { 120 | server_name www.exploreflask.com; 121 | rewrite ^ http://exploreflask.com/ permanent; 122 | } 123 | 124 | # Handle requests to exploreflask.com on port 80 125 | server { 126 | listen 80; 127 | server_name exploreflask.com; 128 | 129 | # Handle all locations 130 | location / { 131 | # Pass the request to Gunicorn 132 | proxy_pass http://127.0.0.1:8000; 133 | 134 | # Set some HTTP headers so that our app knows where the request really came from 135 | proxy_set_header Host $host; 136 | proxy_set_header X-Real-IP $remote_addr; 137 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 138 | } 139 | } 140 | ``` 141 | 142 | 现在在 */etc/nginx/sites-enabled* 下创建该文件的符号链接,接着重启 Nginx。 143 | 144 | ``` 145 | $ sudo ln -s \ 146 | /etc/nginx/sites-available/exploreflask.com \ 147 | /etc/nginx/sites-enabled/exploreflask.com 148 | ``` 149 | 150 | 你现在应该可以发送请求给 Nginx 然后收到来自应用的响应。 151 | 152 | > **参见** 153 | > Gunicorn文档中关于配置Nginx的部分会给你更多启动Nginx的信息: 154 | > 155 | 156 | #### ProxyFix 157 | 158 | 有时,你会遇到 Flask 不能恰当处理转发的请求的情况。这也许是因为在 Nginx 中设置的某些 HTTP 报文头部造成的。我们可以使用 Werkzeug 的 ProxyFix 来 fix 转发请求。 159 | 160 | _app.py_ 161 | ```python 162 | from flask import Flask 163 | 164 | # Import the fixer 165 | from werkzeug.contrib.fixers import ProxyFix 166 | 167 | app = Flask(__name__) 168 | 169 | # Use the fixer 170 | app.wsgi_app = ProxyFix(app.wsgi_app) 171 | 172 | @app.route('/') 173 | def index(): 174 | return "Hello World!" 175 | ``` 176 | 177 | > **参见** 178 | > 在Werkzeug文档中可以读到更多关于ProxyFix的信息: 179 | > 180 | 181 | ## 总结 182 | 183 | * 你可以把 Flask 应用托管到 AWS EC2,Heroku 和 Digital Ocean。(译者注:建议托管到国内的云平台上) 184 | * Flask 应用的基本部署依赖包括一个应用容器(比如 Gunicorn)和一个反向代理(比如 Nginx)。 185 | * Gunicorn 应该退居 Nginx 幕后并监听 127.0.0.1(内部请求)而非 0.0.0.0(外部请求) 186 | * 使用 Werkzeug 的 ProxyFix 来处理 Flask 应用遇到的特定的转发报文头部。 187 | -------------------------------------------------------------------------------- /zh/2-coding_conventions.md: -------------------------------------------------------------------------------- 1 | ![代码约定](images/conventions.png) 2 | 3 | # 代码约定 4 | 5 | 在 Python 社区中有许多关于代码风格的约定。如果你写过一段时间 Python 了,那么也许对此已经有些了解。 6 | 我会简单介绍一下,同时给你一些 URL 链接,从中你可以找到关于这个话题的详细信息。 7 | 8 | ## 让我们提出一个 PEP! 9 | 10 | **PEP** 全称是“Python Enhancement Proposal”(Python 增强提案)。你可以在[python.org](http://www.python.org/dev/peps/)上找到它们以及对应的索引目录。 11 | PEP 在索引目录中按照数字编号排列,包括了元 PEP(meta-PEP,讨论关于 PEP 的细节)。与之对应的是技术 PEP(technical PEP),思考的是诸如 Python 内部实现的改良这样的话题。 12 | 13 | 有一些 PEP,比如 PEP 8 和 PEP 257,影响了 Python 代码风格的标准。 14 | PEP 8 包括了 Python 代码风格的规约。 15 | 而 PEP 257 包括了文档字符串(docstrings,在 Python 中给代码加文档的标准方式)的规约。 16 | 17 | ### PEP 8: Python 代码风格规约 18 | 19 | PEP 8 是对 Python 代码风格的官方规约。 20 | 我建议你阅读它并将之付诸在 Flask 项目(以及其他 Python 项目)的开发实践中。 21 | 当项目规模膨胀到多个包含成百上千行代码的文件时,这样做会使你的代码更加工整、了然。毕竟 PEP 8 的建议都是围绕着实现更加可读的代码这个目标。 22 | 另外,如果你的项目准备开源,潜在的奉献者(contributors)会很高兴看到你的代码是遵循 PEP 8 的。 23 | 24 | 一个至关重要的建议是每级缩进使用 4 个空格。不要使用 tab。 25 | 如果你打破了这个规约,它将会成为你(以及你的队友)在项目间切换的一个负担。 26 | 这种不一致一向是任意语言心中的痛,但是对于 Python,一门着重留白的语言,这是一个不可承受之重。 27 | 因为 tab 与 space 之间的混搭会导致不可预期且难以排查的错误。 28 | 29 | ### PEP 257: 文档字符串规约 30 | 31 | PEP 257 覆盖了 Python 的另一项标准:**docstrings**。 32 | 你可以阅读 PEP 中的定义和相关建议,不过这里会给一个例子来展示一个文档字符串应该是怎样的: 33 | 34 | ```python 35 | def launch_rocket(): 36 | """主要的火箭发射调度器 37 | 38 | 启动发射火箭所需的每一个步骤。 39 | """ 40 | # [...] 41 | ``` 42 | 43 | 这种风格的文档字符串可以通过一些诸如[Sphinx](http://sphinx-doc.org/)的软件来生成不同格式的文档。 44 | 同时它们也有助于让你的代码更加工整。 45 | 46 | > 参见 47 | > * PEP 8 48 | > * PEP 257 49 | > * Sphinx ,一个文档生成器,同出于Flask作者之手 50 | 51 | ## 相对形式的 import 52 | 53 | 开发 Flask 应用时,使用相对形式的 import 会让你的生活更加轻松。 54 | 原因很简单。之前,当需要 import 一个内部模块时,你也许要显式指明应用的包名(the app's package name)。假设你想要从 *myapp/models.py* 中导入 `User` 模型: 55 | 56 | ```python 57 | # 使用绝对路径来导入User 58 | from myapp.models import User 59 | ``` 60 | 61 | 用了相对形式的 import 后,你可以使用点标记法:第一个 `.` 来表示当前目录,之后的每一个 `.` 表示下一个父目录。 62 | 63 | ```python 64 | # 使用相对路径来导入User 65 | from .models import User 66 | ``` 67 | 68 | 这种做法的好处在于使得 package 变得更加模块化了。 69 | 现在你可以重命名你的 package 并在别的项目中重用模块,而无需忍受更新被硬编码的包名之苦。 70 | 71 | > 参见 72 | > * 你可以在PEP 328的[这一节](http://www.python.org/dev/peps/pep-0328/#guido-s-decision)里读到更多关于相对形式的import的语法 73 | > * 在写作本书的过程中,我碰巧在这个Tweet上面看到了一个使用相对形式的import的好处: 74 | > 75 | > Just had to rename our whole package. Took 1 second. Package relative imports FTW! 76 | 77 | ## 总结 78 | 79 | * 尽量遵循 PEP 8 中的代码风格规约。 80 | * 尽量遵循 PEP 257 中的文档字符串规约。 81 | * 使用相对形式的 import 来 import 你的应用中的内部模块。 82 | -------------------------------------------------------------------------------- /zh/3-environment.md: -------------------------------------------------------------------------------- 1 | ![环境](images/environment.png) 2 | 3 | # 环境 4 | 5 | 为了正确地跑起来,你的应用需要依赖许多不同的软件。 6 | 就算是再怎么否认这一点的人,也无法否认至少需要依赖 Flask 本身。 7 | 你的应用的 **运行环境**,在当你想要让它跑起来时,是至关重要的。 8 | 幸运的是,我们有许多工具可以减低管理环境的复杂度。 9 | 10 | ## 使用 virtualenv 来管理环境 11 | 12 | [virtualenv](http://www.virtualenv.org/en/latest/)是一个能把你的应用隔离在一个 **虚拟环境** 中的工具。 13 | 一个虚拟环境是一个包含了你的应用依赖的软件的文件夹。一个虚拟环境同时也封存了你在开发时的环境变量。 14 | 与其把依赖包,比如 Flask,下载到你的系统包管理文件夹,或用户包管理文件夹,我们可以把它下载到对应当前应用的一个隔离的文件夹之下。 15 | 这使得你可以指定一个特定的 Python 二进制版本,取决于当前开发的项目。 16 | 17 | virtualenv 也可以让你给不同的项目指定同样的依赖包的不同版本。 18 | 当你在一个老旧的包含众多不同项目的平台上开发时,这种灵活性十分重要。 19 | 20 | 用了 virtualenv,你将只会把少数几个 Python 模块安装到系统的全局空间中。其中一个会是 virtualenv 本身: 21 | 22 | ```sh 23 | # 使用pip安装virtualenv 24 | $ pip install virtualenv 25 | ``` 26 | 27 | 安装完 virtualenv,就可以开始创建虚拟环境。切换到你的项目文件夹,运行 `virtualenv` 命令。这个命令接受一个参数,作为虚拟环境的名字(同样也是它的位置,在当前文件夹 `ls` 下你就知道了)。 28 | 29 | ```sh 30 | $ virtualenv venv 31 | New python executable in venv/bin/python 32 | Installing setuptools, pip...done. 33 | ``` 34 | 35 | 这将创建一个包含所有依赖的文件夹。 36 | 37 | 一旦新的 virtual environment 已经准备就绪,你需要给对应的 virtual environment 下的 `bin/activate` 脚本执行 `source`,来激活它。 38 | 39 | ``` 40 | $ source venv/bin/activate 41 | ``` 42 | 43 | 你可以通过运行 `which python` 看到:`python` 现在指向的是 virtual environment 中的二进制版本。 44 | 45 | ```sh 46 | $ which python 47 | /usr/local/bin/python 48 | $ source venv/bin/activate 49 | (venv)$ which python 50 | /Users/robert/Code/myapp/venv/bin/python 51 | ``` 52 | 53 | 当一个 virtual environment 被激活了,依赖包会被 pip 安装到 virtual environment 中而不是全局系统环境。 54 | 55 | 你也许注意到了,你的 shell 提示符发生了改变。 56 | virtualenv 在它前面添加了当前被激活的 virtual environment,所以你能意识到你并不存在于全局系统环境中。 57 | 58 | 运行 `deactivate` 命令,你就能离开你的 virtual environment。 59 | 60 | ``` 61 | (venv)$ deactivate 62 | $ 63 | ``` 64 | 65 | ## 使用 virtualenvwrapper 管理你的 virtual environment 66 | 67 | 我想要让你了解到[virtualenvwrapper](http://virtualenvwrapper.readthedocs.org/en/latest/)对于前面的工作做了什么改进,这样你就知道为什么你应该使用它。 68 | 69 | 虚拟环境文件夹现在已经位于你的项目文件夹之下。 70 | 但是你仅仅是在激活了虚拟环境后才会跟它交互。它甚至不应该被放入版本控制中。所以它呆在项目文件夹中也是挺碍眼的。 71 | 解决这个问题的一个方法就是使用 virtualenvwrapper。这个包把你所有的 virtual environment 整理到单独的文件夹下,通常是 `~/.virtualenvs/`。 72 | 73 | 要安装 virtualenvwrapper,遵循这个[文档](http://virtualenvwrapper.readthedocs.org/en/latest/)中的步骤。 74 | 75 | > **注意** 76 | > 确保你在安装virtualenvwrapper时不在任何一个virtual environment中。你需要把它安装在全局空间,而不是一个已存在的virtual environment中 77 | 78 | 现在,你不再需要运行 `virtualenv` 来创建一个环境,只需运行 `mkvirtualenv`: 79 | 80 | ```sh 81 | $ mkvirtualenv rocket 82 | New python executable in rocket/bin/python 83 | Installing setuptools, pip...done. 84 | ``` 85 | 86 | 在你的 virtual environment 目录之下,比如在 `~/.virtualenv` 之下,`mkvirtualenv` 创建了一个文件夹,并替你激活了它。 87 | 就像普通的 `virtualenv`,`python` 和 `pip` 现在指向的是 virtual environment 而不是全局系统。 88 | 为了激活想要的环境,运行这个命令:`workon [environment name]`,而 `deactivate` 依然会关闭环境。 89 | 90 | ## 记录依赖变动 91 | 92 | 随着项目增长,你会发现它的依赖列表也一并随着增长。在你能运行一个 Flask 应用之前,即使已经需要数以十记的依赖包也毫不奇怪。 93 | 管理依赖的最简单的方法就是使用一个简单的文本文件。 94 | pip 可以生成一个文本文件,列出所有已经安装的包。它也可以解析这个文件,并在新的系统(或者新的环境)下安装每一个包。 95 | 96 | ### pip freeze 97 | 98 | **requirements.txt** 是一个常常被许多 Flask 应用用于列出它所依赖的包的文本文件。它是通过 `pip freeze > requirements.txt` 生成的。 99 | 使用 `pip install -r requirements.txt`,你就能安装所有的包。 100 | 101 | > **注意** 102 | > 在freeze或install依赖包时,确保你正位于正确的virtual environment之中。 103 | 104 | ### 手动记录依赖变动 105 | 106 | 随着项目增长,你可能会发现 `pip freeze` 中列出的每一个包并不再是运行应用所必须的了。 107 | 也许有些包只是在开发时用得上。`pip freeze` 没有判断力;它只是列出了当前安装的所有的包。所以你只能手动记录依赖的变动了。 108 | 你可以把运行应用所需的包和开发应用所需的包分别放入对应的 *require_run.txt* 和 *require_dev.txt*。 109 | 110 | ## 版本控制 111 | 112 | 选择一个版本控制系统并使用它。 113 | 我推荐 Git。如我所知,Git 是当下最大众的版本控制系统。 114 | 在删除代码的时候无需担忧潜在的巨大灾难是无价的。 115 | 你现在可以对过去那种把不要的代码注释掉的行为说拜拜了,因为你可以直接删掉它们,即使将来突然需要,也可以通过 `git revert` 来恢复。 116 | 另外,你将会有整个项目的备份,存在 GitHub, Bitbucket 或你自己的 Git server。 117 | 118 | ### 什么不应该在版本控制里 119 | 120 | 我通常不把一个文件放在版本控制里,如果它满足以下两个原因中的一个。 121 | 1. 它是不必要的 122 | 2. 它是不公开的。 123 | 124 | 编译的产物,比如 `.pyc`,和 virtual environment(如果你因为某些原因没有使用 virtualenvwrapper)正是前者的例子。 125 | 它们不需要加入到版本控制中,因为它们可以通过 `.py` 或 `requirements.txt` 生成出来。 126 | 127 | 接口密钥(调用接口时必须传入的参数),应用密钥,以及数据库证书则是后者的例子。 128 | 它们不应该在版本控制中,因为一旦泄密,将造成严重的安全隐患。 129 | 130 | > **注意** 131 | > 在做安全相关的决定时,我会假设稳定版本库将会在一定程度上被公开。这意味着要清除所有的隐私,并且永不假设一个安全漏洞不会被发现, 132 | > 因为“谁能想到他们会干出什么事情?” 133 | 134 | 在使用 Git 时,你可以在版本库中创建一个特别的文件名为 *.gitignore*。 135 | 在里面,能使用正则表达式来列出对应的文件。任何匹配的文件将被 Git 所忽略。 136 | 我建议你至少在其中加入 `*.pyc` 和 `/instance`。instance 文件夹中存放着跟你的应用相关的不便公开的配置。 137 | 138 | ``` 139 | .gitignore: 140 | *.pyc 141 | instance/ 142 | ``` 143 | 144 | > **参见** 145 | > * 在这里你可以了解什么是*.gitignore* : http://git-scm.com/docs/gitignore 146 | > * Flask文档中对instance目录的一段介绍 : http://flask.pocoo.org/docs/config/#instance-folders 147 | 148 | ## 调试 149 | 150 | ### 调试模式 151 | 152 | Flask 有一个便利的特性叫做“debug mode”。在你的应用配置中设置 `debug = True` 就能启动它。 153 | 当它被启动后,服务器会在代码变动之后重新加载,并且一旦发生错误,错误会打印成一个带交互式命令行的调用栈。 154 | 155 | > **注意** 156 | > 不要在生产环境中开启debug mode。交互式命令行运行任意的代码输入,如果是在运行中的网站上,这将导致安全上的灾难性后果。 157 | 158 | > **另见** 159 | > - 阅读一下quickstart页面的debug mode部分 : http://docs.jinkan.org/docs/flask/quickstart.html#debug-mode 160 | > - 这里有一些关于错误处理,日志记录和使用其他调试工具的信息 : http://docs.jinkan.org/docs/flask/errorhandling.html 161 | 162 | ### Flask-DebugToolbar 163 | 164 | [Flask-DebugToolbar](http://flask-debugtoolbar.readthedocs.org/en/latest/) 是用来调试你的应用的另一个得力工具。在 debug mode 中,它在你的应用中添加了一个侧边条。 165 | 这个侧边条会给你提供有关 SQL 查询,日志,版本,模板,配置和其他有趣的信息。 166 | 167 | ## 总结 168 | 169 | * 使用 virtualenv 来打包你的应用的依赖包。 170 | * 使用 virtualenvwrapper 来打包你的 virtual environment。 171 | * 使用一个或多个文本文件来记录依赖变化。 172 | * 使用一个版本控制系统。我推荐 Git。 173 | * 使用.gitignore 来排除不必要的或不能公开的东西混进版本控制。 174 | * debug mode 会在开发时给你有关 bug 的信息。 175 | * Flaks-DebugToolbar 拓展将给你有关 bug 更多的信息。 176 | -------------------------------------------------------------------------------- /zh/4-organizing_your_project.md: -------------------------------------------------------------------------------- 1 | ![组织你的项目](images/organizing.png) 2 | 3 | # 组织你的项目 4 | 5 | Flask 会把项目组织的职责托付给你。 6 | 这是我喜欢使用 Flask 开始项目的其中一个理由,但是这意味着你不得不思考怎么组织你的代码。 7 | 你可以把这个应用放到一个文件中,或者把它分割多个包。然而这两种结构并不适合大多数项目。 8 | 这里有一些固定的组织模式,你可以遵循它们以便于开发和部署。 9 | 10 | ## 约定 11 | 12 | 在这一段中我想要先约定一些概念。 13 | 14 | **版本库(Repository)**:你的应用的根目录。这个概念来自于版本控制系统,但在这里有所拓展。 15 | 当我在这一章提到“版本库”时,指的是你的项目的根目录。在开发你的应用时,你不太可能会离开这个目录。 16 | 17 | **包(Package)**:包含了你的应用代码的一个包。在这一章,我将深入探讨以包的形式建立你的应用,但是现在只需知道包是版本库的一个子目录。 18 | 19 | **模块(Module)**:一个模块是一个简单的,可以被其它 Python 文件引入的 Python 文件。一个包由多个模块组成。 20 | 21 | > **参见** 22 | > * 在这里可以读到更多的关于Python模块的内容: http://docs.python.org/2/tutorial/modules.html 23 | > * 这个链接中也有一节关于包的内容: http://docs.python.org/2/tutorial/modules.html#packages 24 | 25 | ## 组织模式 26 | 27 | ### 单一模块 28 | 29 | 在许多 Flask 例子里,你会看到它们把所有的代码放到一个单一文件中,通常是 *app.py*。对于一些微(~~写完就丢~~)项目来说这恰到好处,毕竟你只需要处理几个路由(route)并且只有百来行代码。(示例用的应用就是这样) 30 | 31 | 单一模块的应用的版本库看起来像这样: 32 | 33 | ``` 34 | app.py 35 | config.py 36 | requirements.txt 37 | static/ 38 | templates/ 39 | ``` 40 | 41 | 在这个例子中,应用逻辑部分会存放在 *app.py* 42 | 43 | ### 包 44 | 45 | 当你开始在一个变得更加复杂的项目上工作时,单一模块就会造成严重的问题。 46 | 你需要为模型(model)和表单(form)定义多个类,而它们会跟你的路由和配置代码又吵又闹。所有的一切让你焦头烂额。 47 | 为了解决这个问题,我们得把应用中不同的组件分开到单独的、高内聚的一组模块 - 也即是包 - 之中。 48 | 49 | 基于包的应用的版本库看起来就像是这样: 50 | 51 | ``` 52 | config.py 53 | requirements.txt 54 | run.py 55 | instance/ 56 | /config.py 57 | yourapp/ 58 | /__init__.py 59 | /views.py 60 | /models.py 61 | /forms.py 62 | /static/ 63 | /templates/ 64 | ``` 65 | 66 | 这个结构允许你理智地整理你的应用的不同组件。 67 | 有关模型的类定义全待在 *models.py*,而路由定义在 *views.py*,有关表单的类定义全待在 *forms.py*(我们等会会用整整一章的篇幅谈谈表单)。 68 | 69 | 下面的表格列举了大多数 Flask 应用都有的基本组件。 70 | 对于你的应用,可能还需要别的一些文件,但这些适用于大多数 Flask 应用。 71 | 72 | | 组件 | 作用 | 73 | | ------------------ |-------------| 74 | | run.py | 这个文件中用于启动一个开发服务器。它从你的包获得应用的副本并运行它。这不会在生产环境中用到,不过依然在许多Flask开发的过程中看到。 | 75 | | requirements.txt | 这个文件列出了你的应用依赖的所有Python包。你可能需要把它分成生产依赖和开发依赖。[请看第三章] | 76 | | config.py | 这个文件包含了你的应用需要的大多数配置变量 | 77 | | instance/config.py | 这个文件包含不应该出现在版本控制的配置变量。其中有类似调用密钥和数据库URI连接密码。同样也包括了你的应用中特有的不能放到阳光下的东西。比如,你可能在*config.py*中设定`DEBUG = False`,但在你自己的开发机上的*instance/config.py*设置`DEBUG = True`。因为这个文件可以在*config.py*之后被载入,它将覆盖掉`DEBUG = False`,并设置`DEBUG = True`。| 78 | | yourapp/ | 这个包里包括了你的应用。| 79 | | yourapp/\_\_init\_\_.py| 这个文件初始化了你的应用并把所有其它的组件组合在一起。| 80 | | yourapp/views.py | 这里定义了路由。它也许需要作为一个包(*yourapp/views/*),由一些包含了紧密相联的路由的模块组成。| 81 | | yourapp/models.py | 在这里定义了应用的模型。你可能需要像对待*views.py*一样把它分割成许多模块。| 82 | | yourapp/static/ | 这个文件包括了公共CSS, Javascript, images和其他你想通过你的应用展示出去的静态文件。默认情况下人们可以从*yourapp.com/static/*获取这些文件。| 83 | | yourapp/templates/ | 这里放置着你的应用的Jinja2模板。| 84 | 85 | ### Blueprints 86 | 87 | 有朝一日你可能会发觉应用里有许多相关的路由了。如果是我,我会首先把 *views.py* 分割成一个包并把相关的路由组织成模块。 88 | 要是你已经这么做了,是时候把你的应用分解成[蓝图](http://docs.jinkan.org/docs/flask/blueprints.html)(blueprints)了 89 | 90 | 蓝图是按照一定程度上的自组织的方式,作为你的应用的一部分的组件。 91 | 它们表现得就像你的应用下的子应用一样。你可能使用不同的蓝图来对应管理面板(admin panel),前端(front-end)和用户面板(user dashboard)。 92 | 这使得你按照组件组织视图,静态文件和模板,并在组件间共享模型,表单和你的应用的其他部分。 93 | 94 | 你可以在第 7 章阅读到关于蓝图的更多内容。 95 | 96 | ## 总结 97 | 98 | * 对于微应用,建议使用单一模块结构。 99 | * 对于包含了视图,模型,表单以及更多的项目,使用包结构。 100 | * 蓝图是把项目按照一些不同的组件组织起来的好办法。 101 | -------------------------------------------------------------------------------- /zh/5-configuration.md: -------------------------------------------------------------------------------- 1 | ![配置](images/configuration.png) 2 | 3 | # 配置 4 | 5 | 当你开始学习 Flask 时,配置看上去是小菜一碟。你仅仅需要在 *config.py* 定义几个变量,然后万事大吉。 6 | 然而当你不得不管理一个生产上的应用的配置时,这一切将变得棘手万分。 7 | 你不得不设法保护 API 密钥,或者纠结于为了不同的环境(比如开发环境和生产环境)使用不同的配置。 8 | 在本章我们将探讨 Flask 的一些高级特性,它们能让配置管理更为轻松。 9 | 10 | ## 从小处起步 11 | 12 | 一个简单的应用不需要任何复杂的配置。你仅仅需要在你的根目录下放置一个 *config.py* 文件,并在 *app.py* 或 *yourapp/\_\_init\_\_.py* 中加载它。 13 | 14 | *config.py* 的每一行中应该是某一个变量的赋值语句。一旦 *config.py* 在稍后被加载,这个配置变量可以通过 `app.config` 字典来获取,比如 `app.config["DEBUG"]`。 15 | 以下是一个小项目的 *config.py* 文件的范例: 16 | 17 | ```python 18 | DEBUG = True # 启动Flask的Debug模式 19 | BCRYPT_LEVEL = 13 # 配置Flask-Bcrypt拓展 20 | MAIL_FROM_EMAIL = "robert@example.com" # 设置邮件来源 21 | ``` 22 | 23 | 有一些配置变量是内建的,比如 `DEBUG`。还有些配置变量是关于 Flask 拓展的,比如 `BCPYRT_LEVEL` 就是用于 Flask-Bcrypt 拓展(一个用于 hash 映射密码的拓展)。 24 | 你甚至可以定义在这个应用中用到的自己的配置变量。 25 | 在这个例子中,我使用 `app.config["MAIL_FROM_EMAIL"]` 来表示邮件往来时(比如重置密码)默认的发送方。 26 | 这使得在将来要修改的时候不会带来太多麻烦。 27 | 28 | 为了加载这些配置变量,我通常使用 `app.config.from_object()`。如果是单一模块应用中,是在 *app.py*;或者在 *yourapp/\_\_init\_\_.py*,如果是基于包的应用。 29 | 无论在哪种情况下,代码看上去像这样: 30 | 31 | ``` 32 | from flask import Flask 33 | 34 | app = Flask(__name__) 35 | app.config.from_object('config') 36 | # 现在通过app.config["VAR_NAME"],我们可以访问到对应的变量 37 | ``` 38 | 39 | ### 一些重要的配置变量 40 | 41 | | 变量 | 描述 | 默认值| 42 | | -------------|:-------------:|------- | 43 | | DEBUG | 在调试错误的时候给你一些有用的工具。比如当一个请求导致异常的发生时,会出现的一个web界面的调用堆栈和Python命令行。| 在开发环境下应该设置成True,在生产环境下应设置为False。| 44 | | SECRET_KEY | Flask使用这个密钥来对cookies和别的东西进行签名。你应该在*instance*文件夹中设定这个值,并不要把它放入版本控制中。你可以在下一节读到关于*instance*文件夹的更多信息。| 这应该是一个复杂的任意值。| 45 | | BCRYPT_LEVEL | 如果使用Flask-Bcrypt来hash映射用户密码(如果没有,现在就用它),你需要为hash密码的算法指定“rounds”的值。设置的rounds值越高,计算一次hash花费的时间就越长(同样的效果作用于破解方,这个才是重要的)。rounds的值应该随着你的设备的计算能力的提升而增加| 如果使用Flask-Bcrypt来hash映射用户密码(如果没有,现在就用它),你需要为hash密码的算法指定“rounds”的值。设置的rounds值越高,计算一次hash花费的时间就越长(同样的效果作用于破解方,这个才是重要的)。rounds的值应该随着你的设备的计算能力的提升而增加| 46 | 47 | ** 确保生产环境下已经设置了 `DEBUG = False`。如果忘记关掉,用户会很乐意对你的服务器执行任意的 Python 代码。** 48 | 49 | ## instance 文件夹 50 | 51 | 有时你需要定义一些不能为人所知的配置变量。为此,你会想要把它们从 *config.py* 中的其他变量分离出来,并保持在版本控制之外。 52 | 你可能要隐藏类似数据库密码和 API 密钥的秘密,或定义特定于当前机器的参数。 53 | 为了让这更加轻松,Flask 提供了一个叫 *instance 文件夹* 的特性。 54 | instance 文件夹是根目录的一个子文件夹,包括了一个特定于当前应用实例的配置文件。我们不要把它提交到版本控制中。 55 | 56 | 这是一个使用了 instance 文件夹的简单 Flask 应用的结构: 57 | 58 | ``` 59 | config.py 60 | requirements.txt 61 | run.py 62 | instance/ 63 | config.py 64 | yourapp/ 65 | __init__.py 66 | models.py 67 | views.py 68 | templates/ 69 | static/ 70 | ``` 71 | 72 | ### 使用 instance 文件夹 73 | 74 | 要想加载定义在 instance 文件夹中的配置变量,你可以使用 `app.config.from_pyfile()`。 75 | 如果在调用 `Flask()` 创建应用时设置了 `instance_relative_config=True`,`app.config.from_pyfile()` 将查看在 *instance* 文件夹的特殊文件。 76 | 77 | ```python 78 | app = Flask(__name__, instance_relative_config=True) 79 | app.config.from_object('config') 80 | app.config.from_pyfile('config.py') 81 | ``` 82 | 83 | 现在,你可以在 *instance/config.py* 中定义变量,一如在 *config.py*。 84 | 你也应该将 instance 文件夹加入到版本控制系统的忽略名单中。比如假设你用的是 git,你需要在 *gitignore* 中新开一行,写下 `instance/`。 85 | 86 | ### 密钥 87 | 88 | instance 文件夹的隐秘属性使得它成为藏匿密钥的好地方。 89 | 你可以在放入应用的密钥或第三方的 API 密钥。假如你的应用是开源的,或者将会是开源的,这会很重要。我们希望其他人去使用他们自己申请的密钥。 90 | 91 | ```python 92 | # instance/config.py 93 | 94 | SECRET_KEY = 'Sm9obiBTY2hyb20ga2lja3MgYXNz' 95 | STRIPE_API_KEY = 'SmFjb2IgS2FwbGFuLU1vc3MgaXMgYSBoZXJv' 96 | SQLALCHEMY_DATABASE_URI= \ 97 | "postgresql://user:TWljaGHFgiBCYXJ0b3N6a2lld2ljeiEh@localhost/databasename" 98 | ``` 99 | 100 | ### 最小化依赖于环境的配置 101 | 102 | 如果你的生产环境和开发环境之间的差别非常小,你可以使用你的 instance 文件夹抹平配置上的差别。 103 | 在 *instance/config.py* 中定义的变量可以覆盖在 *config.py* 中设定的值。 104 | 你只需要在 `app.config.from_object()` 之后才调用 `app.config.from_pyfile()`。 105 | 这样做的其中一个优点是你可以在不同的机器中修改你的应用的配置。你的开发版本库可能看上去像这样: 106 | 107 | config.py 108 | ``` 109 | DEBUG = False 110 | SQLALCHEMY_ECHO = False 111 | ``` 112 | 113 | instance/config.py 114 | ``` 115 | DEBUG = True 116 | SQLALCHEMY_ECHO = True 117 | ``` 118 | 119 | 然后在生产环境中,你将这些代码从 *instance/config.py* 中移除,它就会改用回 *config.py* 中设定的变量。 120 | 121 | > **参见** 122 | > * 在这里可以读到关于Flask-SQLAlchemy的配置密钥: http://pythonhosted.org/Flask-SQLAlchemy/config.html#configuration-keys 123 | 124 | ## 依照环境变量来配置 125 | 126 | instance 文件夹不应该在版本控制中。这意味这你将不能追踪你的 instance 配置。 127 | 在只有一两个变量的情况下这不是什么问题,但如果你有关于多个环境(生产,稳定,开发,等等)的一大堆配置,你不会愿意冒失去它们的风险。 128 | 129 | Flask 给我们提供了根据环境变量选择一个配置文件的能力。 130 | 这意味着我们可以在我们的版本库中有多个配置文件,并总是能根据具体环境,加载到对的那个。 131 | 132 | 当我们到了有多个配置文件共存的境况,是时候把文件都移动到 `config` 包之下。 133 | 下面是在这样的一个版本库中大致的样子: 134 | 135 | ``` 136 | requirements.txt 137 | run.py 138 | config/ 139 | __init__.py # 空的,只是用来告诉Python它是一个包。 140 | default.py 141 | production.py 142 | development.py 143 | staging.py 144 | instance/ 145 | config.py 146 | yourapp/ 147 | __init__.py 148 | models.py 149 | views.py 150 | static/ 151 | templates/ 152 | ``` 153 | 154 | 在我们有一些不同的配置文件的情况下,可以这样设置: 155 | 156 | | 文件名 | 内容 | 157 | | ------------------ |------------- | 158 | | config/default.py | 默认值,适用于所有的环境或交由具体环境进行覆盖。举个例子,在*config/default.py*中设置`DEBUG = False`,在*config/development.py*中设置`DEBUG = True`。| 159 | | config/development.py| 在开发环境中用到的值。这里你可以设定在localhost中用到的数据库URI链接。| 160 | | config/production.py | 在生产环境中用到的值。这里你可以设定数据库服务器的URI链接,而不是开发环境下的本地数据库URI链接。| 161 | | config/staging.py | 在你的开发过程中,你可能需要在一个模拟生产环境的服务器上测试你的应用。你也许会使用不一样的数据库,想要为稳定版本的应用替换掉一些配置。| 162 | 163 | 要在不同的环境中指定所需的变量,你可以调用 `app.config.from_envvar()`: 164 | 165 | ```python 166 | # yourapp/__init__.py 167 | 168 | app = Flask(__name__, instance_relative_config=True) 169 | app.config.from_object('config.default') 170 | app.config.from_pyfile('config.py') # 从instance文件夹中加载配置 171 | app.config.from_envvar('APP_CONFIG_FILE') 172 | ``` 173 | 174 | `app.config.from_envvar(‘APP_CONFIG_FILE’)` 将加载由环境变量 `APP_CONFIG_FILE` 指定的文件。这个环境变量的值应该是一个配置文件的绝对路径。 175 | 176 | 这个环境变量的设定方式取决于你运行你的应用的平台。如果你是在一台标准的 Linux 服务器上运行,你可以使用一个 shell 脚本来设置环境变量并运行 `run.py`。 177 | 178 | start.sh 179 | ``` 180 | APP_CONFIG_FILE=/var/www/yourapp/config/production.py 181 | python run.py 182 | ``` 183 | 184 | *start.sh* 特定于某个环境,所以它也不能放入版本控制当中。如果你把应用托管到 Heroku,你可以用 Heroku 提供的工具设置环境变量参数。对于其他 PAAS 平台也是同样的处理。 185 | 186 | ## 总结 187 | 188 | * 一个简单的应用也许仅需一个配置文件:*config.py* 189 | * instance 文件夹可以帮助我们隐藏不愿为人所知的配置变量。 190 | * instance 文件夹可以用来改变特定环境下的程序配置。 191 | * 应对复杂的,基于环境的配置,我们可以结合环境变量和 `app.config.from_envvar()` 来使用。 192 | -------------------------------------------------------------------------------- /zh/6-advanced_patterns_for_views_and_routing.md: -------------------------------------------------------------------------------- 1 | ![关于视图和路由的进阶技巧](images/views.png) 2 | 3 | # 关于视图和路由的进阶技巧 4 | 5 | ## 视图装饰器 6 | 7 | Python 装饰器让我们可以用其他函数包装特定函数。 8 | 当一个函数被一个装饰器“装饰”时,那个装饰器会被调用,接着会做额外的工作,修改变量,调用原来的那个函数。我们可以把我们想要重用的代码作为装饰器来包装一系列视图。 9 | 10 | 装饰器的语法看上去像这样: 11 | 12 | ```python 13 | @decorator_function 14 | def decorated(): 15 | pass 16 | ``` 17 | 18 | 如果你看过 Flask 入门指南,那么对这个语法应该不感到陌生。`@app.route` 正是用于在 Flask 应用中给视图函数设定路由 URL 的装饰器。 19 | 20 | 让我们看一下在你的 Flask 应用中用得上的一些别的装饰器。 21 | 22 | ### 认证 23 | 24 | Flask-Login 使得用户认证系统的实现不再困难。 25 | 除了处理用户认证的细节之外,Flask-Login 允许我们使用 `@login_required` 这个装饰器来验证用户对某些资源的访问权限。 26 | 27 | 下面是从一个用到 Flask-Login 和 `@login_required` 装饰器的一个示范应用中获取的例子: 28 | 29 | ``` 30 | from flask import render_template 31 | from flask_login import login_required, current_user 32 | 33 | 34 | @app.route('/') 35 | def index(): 36 | return render_template("index.html") 37 | 38 | @app.route('/dashboard') 39 | @login_required 40 | def account(): 41 | return render_template("account.html") 42 | ``` 43 | 44 | > **注意** 45 | > `@app.route`必须是最外面的视图装饰器。 46 | 47 | 只有已经验证的用户能够接触到 */dashboard* 路由。你可以配置 Flask-Login 来重定向未验证用户到登录页面,返回 HTTP 401 状态码或别的你乐意的事。 48 | 49 | > **参见** 50 | > 通过[官方文档](http://flask-login.readthedocs.org/en/latest/)可以读到更多关于Flask-Login的内容 51 | 52 | ### 缓存 53 | 54 | 意淫一下,假如你的应用突然有一天在微博/朋友圈或网上别的地方火了。 55 | 于是秒秒钟会有成千上万的请求涌向你的应用。你的主页在每个请求中都要从数据库跑上一大趟,结果海量的请求导致网站慢得像教务系统一样。 56 | 你能做什么来加速这一过程,以免用户以为你的应用挂掉了? 57 | 58 | 答案不止一个,不过就本章主旨而言,标准答案是实现缓存。 59 | 特别的,我们将要用到[Flask-Cache](http://pythonhosted.org/Flask-Cache/)拓展。这个拓展给我们提供一个可以用来缓存某个响应一段时间的装饰器。 60 | 61 | 你可以将 Flask-Cache 配置成跟你想用的后台缓存一起使用。一个普遍的选择是[Redis](http://redis.io/),一个容易配置和使用的软件。 62 | 假设 Flask-Cache 已经配置好了,下面是我们的被装饰的视图的例子: 63 | 64 | ``` 65 | from flask_cache import Cache 66 | from flask import Flask 67 | 68 | app = Flask() 69 | 70 | # 通过这个方式获取相关配置 71 | cache = Cache(app) 72 | 73 | @app.route('/') 74 | @cache.cached(timeout=60) 75 | def index(): 76 | [...] # 进行一些数据库调用来获取所需信息 77 | return render_template( 78 | 'index.html', 79 | latest_posts=latest_posts, 80 | recent_users=recent_users, 81 | recent_photos=recent_photos 82 | ) 83 | 84 | ``` 85 | 86 | 现在这个函数将会在每 60 秒最多运行一次。响应的结果会被保存在缓存中,并可以让期间的每一个请求获取。 87 | 88 | > **注意** 89 | > Flask-Cache同时允许我们**记住**函数 - 或缓存通过给定的参数调用的某个函数。你甚至可以缓存过于复杂的Jinja2模板片段! 90 | 91 | ### 自定义装饰器 92 | 93 | 在这个例子中,让我们假设我们有一个应用,每个月要求用户定期付费。如果一个用户的账户已经过期,我们要重定向他们到账单页面,并告知其悲伤的现实。 94 | 95 | myapp/util.py 96 | ``` 97 | from functools import wraps 98 | from datetime import datetime 99 | 100 | from flask import flash, redirect, url_for 101 | 102 | from flask_login import current_user 103 | 104 | def check_expired(func): 105 | @wraps(func) 106 | def decorated_function(*args, **kwargs): 107 | if datetime.utcnow() > current_user.account_expires: 108 | flash("Your account has expired. Please update your billing information.") 109 | return redirect(url_for('account_billing')) 110 | return func(*args, **kwargs) 111 | 112 | return decorated_function 113 | ``` 114 | 115 | 1. 当用 `@check_expired` 装饰一个函数时,`check_expired()` 被调用,被装饰的函数作为一个参数被传递进来。 116 | 2. `@wraps` 是一个装饰器,告知 Python 函数 `decorated_function()` 包装了视图函数 `func()`。严格来说这不是必须的,但是这么做会使得装饰函数更加自然一些,更有利于文档和调试。 117 | 3. `decorated_function` 将截取原本传递给视图函数 `func()` 的 args 和 kwargs。在这里我们检查用户的账户是否过期。如果是,我们将闪烁一则信息,并重定向到账单页面。 118 | 4. 既然已经处理好自己的事情,我们把原来的参数交由视图函数 `func()` 去继续执行。 119 | 120 | 位于最顶部的装饰器将最先运行,然后调用下一个函数:一个视图函数或下一个装饰器。装饰器语法只是一个语法糖而已。 121 | 122 | ```python 123 | # 这样 124 | @foo 125 | @bar 126 | def one(): 127 | pass 128 | 129 | r1 = one() 130 | ``` 131 | 132 | 133 | ```python 134 | # 等同于这样: 135 | def two(): 136 | pass 137 | two = foo(bar(two)) 138 | r2 = two() 139 | 140 | r1 == r2 # True 141 | ``` 142 | 143 | 下面这个例子用到了我们自定义的装饰器和来自 Flask-login 拓展的 `@login_required` 装饰器。我们可以将多个装饰器堆成栈来一起使用。 144 | 145 | myapp/views.py 146 | ``` 147 | from flask import render_template 148 | 149 | from flask_login import login_required 150 | 151 | from . import app 152 | from .util import check_expired 153 | 154 | @app.route('/use_app') 155 | @login_required 156 | @check_expired 157 | def use_app(): 158 | """欢迎光临""" 159 | 160 | return render_template('use_app.html') 161 | 162 | @app.route('/account/billing') 163 | @login_required 164 | def account_billing(): 165 | """拿账单来""" 166 | # [...] 167 | return render_template('account/billing.html') 168 | ``` 169 | 170 | 当一个用户试图访问 */use\_app* 时,`check_expired()` 将在执行视图函数之前确保相关的账户资料不会泄漏。 171 | 172 | > 参见 173 | > 在Python文档中可以读到更多关于`wraps()`的内容: 174 | 175 | ## URL 转换器 176 | 177 | ### 内建转换器 178 | 179 | 当你在 Flask 中定义一个路由时,你可以将指定的一部分转换成 Python 变量并传递给视图函数。 180 | 181 | ``` 182 | @app.route('/user/') 183 | def profile(username): 184 | pass 185 | ``` 186 | 187 | 在 URL 中作为的那一部分内容将作为 `username` 参数传递给视图函数。你也可以指定一个转换器过滤出特定的类型。 188 | 189 | ``` 190 | @app.route('/user/id/') 191 | def profile(user_id): 192 | pass 193 | ``` 194 | 195 | 在这个代码块中, 这个 URL 会返回一个 404 状态码 -- 此物无处觅。 196 | 这是因为 URL 中预期是整数的部分却遇到了一串字符串。 197 | 198 | 我们可以有另外一个接受一个字符串的视图函数。*/usr/id/tomato/* 将调用它,而前一个函数只会被 */user/id/124* 所调用。 199 | 200 | 下面是来自 Flask 文档的关于默认转换器的表格: 201 | 202 | | 类型 | 作用 | 203 | | -------|------------------------------- | 204 | | string | 接受任何没有斜杠`/`的文本(默认)| 205 | | int | 接受整数 | 206 | | float | 类似于`int`,但是接受的是浮点数 | 207 | | path | 类似于`string`,但是接受斜杠`/` | 208 | 209 | ### 自定义转换器 210 | 211 | 我们也可以按照自己的需求打造自定义的转换器。 212 | Reddit - 一个知名的链接分享网站 - 用户在此可以创建和管理基于主题和链接分享的社区。 213 | 比如 `/r/python` 和 `/r/flask`,分别由 URL `reddit.com/r/python` 和 `reddit.com/r/flask` 表示。 214 | Reddit 有一个有趣的特性是,通过在 URL 中用一个 `+` 隔开各个社区名,你可以同时看到来自多个社区的帖子。比如 `reddit.com/r/python+flask`。 215 | 216 | 我们可以使用一个自定义转换器来实现这种特性。 217 | 我们可以接受由加号隔离开来的任意数目参数,通过我们的 ListConverter 转换成一个列表,并传递给视图函数。 218 | 219 | util.py 220 | ```python 221 | from werkzeug.routing import BaseConverter 222 | 223 | class ListConverter(BaseConverter): 224 | 225 | def to_python(self, value): 226 | return value.split('+') 227 | 228 | def to_url(self, values): 229 | return '+'.join(BaseConverter.to_url(value) 230 | for value in values) 231 | ``` 232 | 233 | 我们需要定义两个方法:`to_python()` 和 `to_url()`。 234 | 一如其名,`to_python()` 用于转换路径成一个 Python 对象,并传递给视图函数。而 `to_url()` 被 `url_for()` 调用,来转换参数成为符合 URL 的形式。 235 | 236 | 为了使用我们的 ListConverter,我们首先得将它的存在告知 Flask。 237 | 238 | /myapp/\_\_init\_\_.py 239 | ``` 240 | from flask import Flask 241 | 242 | app = Flask(__name__) 243 | 244 | from .util import ListConverter 245 | 246 | app.url_map.converters['list'] = ListConverter 247 | ``` 248 | 249 | > **注意** 250 | > 假如你的util模块有一行`from . import app`,那么有可能陷入循环import的问题。这就是为什么我等到app初始化之后才import ListConverter。 251 | 252 | 现在我们可以一如使用内建转换器一样使用我们的转换器。我们在字典中指定它的键为 "list",所以我们可以在 `@app.route()` 中这样使用: 253 | 254 | views.py 255 | ```python 256 | from . import app 257 | 258 | @app.route('/r/') 259 | def subreddit_home(subreddits): 260 | """显示给定subreddits里的所有帖子""" 261 | posts = [] 262 | for subreddit in subreddits: 263 | posts.extend(subreddit.posts) 264 | 265 | return render_template('/r/index.html', posts=posts) 266 | ``` 267 | 268 | 这应该会像 Reddit 的子社区系统一样工作。这样的方法可以用来实现你能想到的 URL 转换器。 269 | 270 | ## 总结 271 | 272 | * Custom URL converters can be a great way to implement creative features involving URL’s. 273 | * 来自 Flask-Login 的 `@login_required` 装饰器可以帮助你限制验证用户对视图的访问。 274 | * Flask-Cache 插件为你提供一组装饰器来实现多种方式的缓存。 275 | * 我们可以开发自定义视图装饰器来帮助我们组织自己的代码,并坚守 DRY(Don't Repeat Yourself 不重复你自己)原则。 276 | * 自定义的 URL 转换器将会让你很嗨地玩转 URL。 277 | -------------------------------------------------------------------------------- /zh/8-templates.md: -------------------------------------------------------------------------------- 1 | ![模板](images/templates.png) 2 | 3 | # 模板 4 | 5 | 尽管 Flask 并不强迫你使用某个特定的模板语言,它还是默认你会使用 Jinja。在 Flask 社区的大多数开发者使用 Jinja,并且我建议你也跟着做。有一些插件允许你用其他模板语言进行替代(比如[Flask-Genshi](http://pythonhosted.org/Flask-Genshi/)和[Flask-Mako](http://pythonhosted.org/Flask-Mako/)),但除非你有充分理由(不懂 Jinja 可不是一个充分的理由!),否则请保持那个默认的选项;这样你会避免浪费很多时间来焦头烂额。 6 | 7 | > **注意** 8 | > 几乎所有提及Jinja的资源讲的都是Jinja2。Jinja1确实曾存在过,但在这里我们不会讲到它。当你看到Jinja时,我们讨论的是这个Jinja: http://jinja.pocoo.org/ 9 | 10 | ## Jinja 快速入门 11 | 12 | **Jinja** 文档在解释这门语言的语法和特性这方面做得很棒。在这里我不会啰嗦一遍,但还是会再一次向你强调下面一点: 13 | 14 | {% raw %} 15 | > Jinja有两种定界符。`{% ... %}`和`{{ ... }}`。前者用于执行像for循环或赋值等语句,后者向模板输出一个表达式的结果。 16 | {% endraw %} 17 | 18 | > **参见**: http://jinja.pocoo.org/docs/templates/#synopsis 19 | 20 | ## 怎样组织模板 21 | 22 | 所以要将模板放进我们的应用的哪里呢?如果你是从头开始阅读的本文,你可能注意到了 Flask 在对待你如何组织项目结构的事情上十分随意。模板也不例外。你大概也已经注意到,总会有一个放置文件的推荐位置。记住两点。对于模板,这个最佳位置是放在包文件夹下。 23 | 24 | ``` 25 | myapp/ 26 | __init__.py 27 | models.py 28 | views/ 29 | templates/ 30 | static/ 31 | run.py 32 | requirements.txt 33 | ``` 34 | 35 | 让我们打开模板文件夹看看。 36 | 37 | ``` 38 | templates/ 39 | layout.html 40 | index.html 41 | about.html 42 | profile/ 43 | layout.html 44 | index.html 45 | photos.html 46 | admin/ 47 | layout.html 48 | index.html 49 | analytics.html 50 | ``` 51 | 52 | **模板** 的结构平行于对应的路由的结构。对应于路由 *myapp.com/admin/analytics* 的模板是 *templates/admin/analytics.html*。这里也有一些额外的模板不会被直接渲染。*layout.html* 文件就是用于被其他模板继承的。 53 | 54 | ## 继承 55 | 56 | 就像蝙蝠侠一样,一个组织良好的模板文件夹也离不开继承带来的好处。**基础模板** 通常定义了一个适用于所有的 *子模板* 的主体结构。在我们的例子里,*layout.html* 是一个基础模板,而其他的 *html* 文件都是子模板。 57 | 58 | 通常,你会有一个顶级的 *layout.html* 定义你的应用的主体布局,外加站点的每一个节点也有自己的一个 *layout.html*。如果再看一眼上面的文件夹结构,你会看到一个顶级的 *myapp/templates/layout.html*,以及 *myapp/templates/profile/layout.html* 和 *myapp/templates/admin/layout.html*。后两个文件继承并修改第一个文件。 59 | 60 | {% raw %} 61 | 继承是通过 `{% extends %}` 和 `{% block %}` 标签实现的。在双亲模板中,你可以定义要给子模板处理的 block。 62 | {% endraw %} 63 | 64 | _myapp/templates/layout.html_ 65 | ``` 66 | 67 | 68 | 69 | {% block title %}{% endblock %} 70 | 71 | 72 | {% block body %} 73 |

    这个标题在双亲模板中定义

    74 | {% endblock %} 75 | 76 | 77 | ``` 78 | 79 | 在子模板中,你可以拓展双亲模板并定义 block 里面的内容。 80 | 81 | _myapp/templates/index.html_ 82 | ``` 83 | {% extends "layout.html" %} 84 | {% block title %}Hello world!{% endblock %} 85 | {% block body %} 86 | {{ super() }} 87 |

    这个标题在子模板中定义

    88 | {% endblock %} 89 | ``` 90 | 91 | `super()` 函数让我们在子模板里加载双亲模板中这个 block 的内容。 92 | 93 | > **参见** 94 | > 若想了解更多关于继承的内容,请移步到Jinja模板继承方面的文档。 95 | > 96 | 97 | ## 创建宏 98 | 99 | 凭借将反复出现的代码片段抽象成 **宏**,我们可以实现 DRY 原则(Don't Repeat Yourself)。在撰写用于应用的导航功能的 HTML 时,我们可能会需要给“活跃”链接(比如,到当前页面的链接)一个不同的类。如果没有宏,我们将不得不使用一大堆 if/else 语句来从每个链接中过滤出“活跃”链接。 100 | 101 | 宏提供了模块化模板代码的一种方式;它们就像是函数一样。让我们看一下如何使用宏来标记活跃链接。 102 | 103 | myapp/templates/layout.html 104 | ``` 105 | {% from "macros.html" import nav_link with context %} 106 | 107 | 108 | 109 | {% block head %} 110 | 我的应用 111 | {% endblock %} 112 | 113 | 114 | 119 | {% block body %} 120 | {% endblock %} 121 | 122 | 123 | ``` 124 | 125 | 现在我们调用了一个尚未定义的宏 - `nav_link` - 并传递两个参数给它:一个目标(比如目标视图的函数名)和我们想要展示的文本。 126 | 127 | > **注意** 128 | > 你可能注意到了我们在import语句中加入了**with context**。Jinja的**上下文(context)**包括了通过`render_template()`函数传递的参数以及在我们的Python代码的Jinja环境上下文。这些变量能够被用于模板的渲染。 129 | > 130 | {% raw %} 131 | > 一些变量是我们显式传递过去的,比如`render_template("index.html", color="red")`,但还有些变量和函数是Flask自动加入到上下文的,比如`request`,`g`和`session`。使用了`{% from ... import ... with context %}`,我们告诉Jinja让所有的变量也在宏里可用。 132 | {% endraw %} 133 | 134 | > **参见** 135 | > * 所有的全局变量都是由Flask传递给Jinja上下文的: http://flask.pocoo.org/docs/templating/#standard-context 136 | > * 通过上下文处理器(context processors),我们可以增加传递给Jinja上下文的变量和函数: http://flask.pocoo.org/docs/templating/#context-processors 137 | 138 | 是时候定义模板中用的 `nav_link` 宏了。 139 | 140 | myapp/templates/macros.html 141 | ``` 142 | {% macro nav_link(endpoint, text) %} 143 | {% if request.endpoint.endswith(endpoint) %} 144 |
  • {{text}}
  • 145 | {% else %} 146 |
  • {{text}}
  • 147 | {% endif %} 148 | {% endmacro %} 149 | ``` 150 | 151 | 现在我们已经在 *myapp/templates/macros.html* 中定义了一个宏。我们所做的,就是使用 Flask 的 `request` 对象 - 默认在 Jinja 上下文中可用 - 来检查当前路由是否是传递给 `nav_link` 的那个路由参数。如果是,我们就在目标链接指向的页面上,于是可以标记它为活跃的。 152 | 153 | > **注意** 154 | > `from x import y`语句中要求x是相对于y的相对路径。如果我们的模板位于*myapp/templates/user/blog.html*,我们需要使用`from "../macros.html" import nav_link with context`。 155 | 156 | ## 自定义过滤器 157 | 158 | Jinja 过滤器是在渲染成模板之前,作用于 `{{ ... }}` 中的表达式的值的函数。 159 | 160 | ``` 161 |

    {{ article.title|title }}

    162 | ``` 163 | 164 | 在这个代码中,`title` 过滤器接受 `article.title` 并返回一个标题格式的文本,用于输出到模板中。它的语法,以及功能,皆一如 Unix 中修改程序输出的“管道”一样。 165 | 166 | > **参见** 167 | > 除了`title`,还有许许多多别的内建的过滤器。在这里可以看到完整的列表: http://jinja.pocoo.org/docs/templates/#builtin-filters 168 | 169 | 我们可以自定义用于 Jinja 模板的过滤器。作为例子,我们将实现一个简单的 `caps` 过滤器来使字符串中所有的字母大写。 170 | 171 | > **注意** 172 | > Jinja已经有一个`upper`过滤器能实现这一点,还有一个`capitalize`过滤器能大写第一个字符并小写剩余字符。这些过滤器还能处理Unicode转换,不过我们的这个例子将只专注于阐述相关概念。 173 | 174 | 我们将在 *myapp/util/filters.py* 中定义我们的过滤器。这个 `util` 包可以用来放置各种杂项。 175 | 176 | myapp/util/filters.py 177 | ``` 178 | from .. import app 179 | 180 | @app.template_filter() 181 | def caps(text): 182 | """Convert a string to all caps.""" 183 | return text.uppercase() 184 | ``` 185 | 186 | 在上面的代码中,通过 `@app.template_filter()` 装饰器,我们能将某个函数注册成 Jinja 过滤器。默认的过滤器名字就是函数的名字,但是通过传递一个参数给装饰器,你可以改变它: 187 | 188 | ``` 189 | @app.template_filter('make_caps') 190 | def caps(text): 191 | """Convert a string to all caps.""" 192 | return text.uppercase() 193 | ``` 194 | 195 | 现在我们可以在模板中调用`make_caps`而不是`caps`:`{{ "hello world!"|make_caps }}`。 196 | 197 | 为了让我们的过滤器在模板中可用,我们仅需要在顶级 *\_\_init\_\_.py* 中 import 它。 198 | 199 | myapp/\_\_init\_\_.py 200 | ``` 201 | # 确保app已经被初始化以免导致循环import 202 | from .util import filters 203 | ``` 204 | 205 | ## 总结 206 | 207 | * 使用 Jinja 作为模板语言。 208 | * Jinja 有两种定界符:`{% ... %}` 和 `{{ ... }}`。前者用于执行类似循环或赋值的语句,后者向模板输出表达式求值的结果。 209 | * 模板应该放在* myapp/templates/* - 一个在应用文件夹里面的目录。 210 | * 我建议 *template/* 文件夹的结构应该与应用 URL 结构一一对应。 211 | * 你应该在 *myapp/templates* 以及站点的每一部分放置一个 *layout.html* 作为布局模板。后者是前者的拓展。 212 | * 可以用模板语言写类似于函数的宏。 213 | * 可以用 Python 代码写应用在模板中的过滤器函数。 214 | -------------------------------------------------------------------------------- /zh/9-static_files.md: -------------------------------------------------------------------------------- 1 | ![静态文件](images/static.png) 2 | 3 | # 静态文件 4 | 5 | 一如其名,静态文件是那些不会改变的文件。一般情况下,在你的应用中,这包括 CSS 文件,Javascript 文件和图片。它也可以包括视频文件和其他可能的东西。 6 | 7 | ## 组织你的静态文件 8 | 9 | 我们将在应用的包中创建一个叫 *static* 的文件夹放置我们的静态文件。 10 | 11 | ``` 12 | myapp/ 13 | __init__.py 14 | static/ 15 | templates/ 16 | views/ 17 | models.py 18 | run.py 19 | ``` 20 | 21 | *static/* 里面的文件组织方式取决于个人的爱好。就我个人来说,如果第三方库(比如 jQuery, Bootstrap 等等)跟自己的 Javascript 和 CSS 文件混起来,我会因此而不爽。所以,我要将第三方库全放到一个 *lib/* 文件夹中。有时会用 *vendor/* 来代替 *lib/*。 22 | 23 | ``` 24 | static/ 25 | css/ 26 | lib/ 27 | bootstrap.css 28 | style.css 29 | home.css 30 | admin.css 31 | js/ 32 | lib/ 33 | jquery.js 34 | home.js 35 | admin.js 36 | img/ 37 | logo.svg 38 | favicon.ico 39 | ``` 40 | 41 | ### 提供一个 favicon 42 | 43 | 用户将通过 yourapp.com/static/访问你的静态文件夹中的文件。默认下浏览器和其他软件认为你的 favicon 位于 yourapp.com/favicon.ico。要想解决这种不一致。你可以在站点模板的 `` 部分添加下面内容。 44 | 45 | ``` 46 | 47 | ``` 48 | 49 | ## 用 Flask-Assets 管理静态文件 50 | 51 | Flask-Assets 是一个管理静态文件的插件。它提供了两种非常有用的特性。首先,它允许你在 Python 代码中定义 *多组*(bundles)可以同时插入你的模板的静态文件。其次,它允许你预处理这些文件。这意味着你可以合并并压缩你的 CSS 和 Javascript 文件,这样用户就会仅仅得到两个压缩后的文件(CSS 和 Javascript)而免于花费太多带宽。你甚至可以从 Sass,Less,CoffeeScript 或别的源码里编译出最终产物。 52 | 53 | 下面是这一章中也做例子的静态文件夹的基本结构。 54 | 55 | _myapp/static/_ 56 | ``` 57 | static/ 58 | css/ 59 | lib/ 60 | reset.css 61 | common.css 62 | home.css 63 | admin.css 64 | js/ 65 | lib/ 66 | jquery-1.10.2.js 67 | Chart.js 68 | home.js 69 | admin.js 70 | img/ 71 | logo.svg 72 | favicon.ico 73 | ``` 74 | 75 | ### 定义分组 76 | 77 | 我们的应用有两部分:公共网站和管理面板(分别称作 "home" 和 "admin")。我们将定义四个分组来覆盖它:每个部分有一个 Javascript 和一个 CSS 分组。我们将它们放入 `util` 包里的 assets 模块。 78 | 79 | _myapp/util/assets.py_ 80 | ``` 81 | from flask_assets import Bundle, Environment 82 | from .. import app 83 | 84 | bundles = { 85 | 86 | 'home_js': Bundle( 87 | 'js/lib/jquery-1.10.2.js', 88 | 'js/home.js', 89 | output='gen/home.js), 90 | 91 | 'home_css': Bundle( 92 | 'css/lib/reset.css', 93 | 'css/common.css', 94 | 'css/home.css', 95 | output='gen/home.css), 96 | 97 | 'admin_js': Bundle( 98 | 'js/lib/jquery-1.10.2.js', 99 | 'js/lib/Chart.js', 100 | 'js/admin.js', 101 | output='gen/admin.js), 102 | 103 | 'admin_css': Bundle( 104 | 'css/lib/reset.css', 105 | 'css/common.css', 106 | 'css/admin.css', 107 | output='gen/admin.css) 108 | } 109 | 110 | assets = Environment(app) 111 | 112 | assets.register(bundles) 113 | ``` 114 | 115 | Flask-Assets 按照被列出来的顺序合并你的文件。如果 *admin.js* 依赖 *jquery-1.10.2.js*,确保 jquery 被列在前面。 116 | 117 | 我们通过字典来定义分组,这样方便注册它们。[webassets](https://github.com/miracle2k/webassets/blob),实际上是 Flask-Assets 的核心,提供了一系列方式来注册分组,包括上面我们演示的以字典作参数的方法。(译注:webassets 之于 Flask-Assets,正如 SQLAlchemy 之于 Flask-SQLAlchemy。) 118 | 119 | > **参见** 120 | > webassets在这里注册了分组: 121 | > 122 | 123 | 既然我们已经在 `util.assets` 中注册了我们的分组,剩下的就是在\_\_init\_\_.py 中,在 app 对象初始化之后,来导入这个模块。 124 | 125 | myapp/\_\_init\_\_.py 126 | ``` 127 | # [...] Initialize the app 128 | 129 | from .util import assets 130 | ``` 131 | 132 | ### 使用你的分组 133 | 134 | 下面是我们的例子中的模板文件夹: 135 | 136 | _myapp/templates/_ 137 | ``` 138 | templates/ 139 | home/ 140 | layout.html 141 | index.html 142 | about.html 143 | admin/ 144 | layout.html 145 | dash.html 146 | stats.html 147 | ``` 148 | 149 | 要使用我们的 admin 分组,我们将插入它们到 admin 部分的基础模板 - *admin/layout.html* - 中。 150 | 151 | myapp/templates/admin/layout.html 152 | ``` 153 | 154 | 155 | 156 | {% assets "admin_js" %} 157 | 158 | {% endassets %} 159 | {% assets "admin_css" %} 160 | 161 | {% endassets %} 162 | 163 | 164 | {% block body %} 165 | {% endblock %} 166 | 167 | 168 | ``` 169 | 170 | 对于 home 分组,我们也同样在 *templates/home/layout.html* 做一样的处理。 171 | 172 | ### 使用过滤器 173 | 174 | 我们可以使用 webassets 过滤器来预处理我们的静态文件。这将方便我们压缩 Javascript 和 CSS 文件。现在修改下我们的代码来实现这一点。 175 | 176 | myapp/util/assets.py 177 | ``` 178 | # [...] 179 | 180 | bundles = { 181 | 182 | 'home_js': Bundle( 183 | 'lib/jquery-1.10.2.js', 184 | 'js/home.js', 185 | output='gen/home.js', 186 | filters='jsmin'), 187 | 188 | 'home_css': Bundle( 189 | 'lib/reset.css', 190 | 'css/common.css', 191 | 'css/home.css', 192 | output='gen/home.css', 193 | filters='cssmin'), 194 | 195 | 'admin_js': Bundle( 196 | 'lib/jquery-1.10.2.js', 197 | 'lib/Chart.js', 198 | 'js/admin.js', 199 | output='gen/admin.js', 200 | filters='jsmin'), 201 | 202 | 'admin_css': Bundle( 203 | 'lib/reset.css', 204 | 'css/common.css', 205 | 'css/admin.css', 206 | output='gen/admin.css', 207 | filters='cssmin') 208 | } 209 | 210 | # [...] 211 | ``` 212 | 213 | > **注意** 214 | > 要想使用`jsmin`和`cssmin`过滤器,你需要安装jsmin和cssmin包(使用`pip install jsmin cssmin`)。确保把它们也加入到*requirements.txt*。 215 | 216 | 一旦模板已经渲染好,Flask-Assets 将在合并的同时压缩我们的文件,而且当其中一个源文件改变时,它会自动更新压缩文件。 217 | 218 | > **注意** 219 | > 如果你在配置中设置`ASSETS_DEBUG = True`, Flask-Assets将独立输出每一个源文件而不会合并它们。 220 | 221 | > **参见** 222 | > 你可以使用Flask-Assets过滤器来自动编译Sass,Less,CoffeeScript,和其他预处理器。来看下你还可以使用哪些过滤器: http://elsdoerfer.name/docs/webassets/builtin_filters.html#js-css-compilers 223 | 224 | ## 总结 225 | 226 | * 静态文件归于 *static/*文件夹。 227 | * 将第三方库跟你自己的静态文件隔离开来。 228 | * 在你的模板里指定你的 favicon 的路径。 229 | * 使用 Flask-Assets 将静态文件插入到你的模板中。 230 | * Flask-Assets 可以编译,合并以及压缩你的静态文件。 231 | -------------------------------------------------------------------------------- /zh/README.md: -------------------------------------------------------------------------------- 1 | # Flask 之旅 2 | 3 | 《explore flask》中文翻译 4 | 5 | 欢迎来到该译本的生产地点!任何 bug report 和别的 pull request 都会受到热烈欢迎!不要犹豫! 6 | 7 | 如果你想购买 PDF 格式的英文原本,前往。 8 | 9 | ## 想参与? 10 | 11 | 如果你对本书有些建议,你可以在 issue 面板新开一个 issue 或提交一个 pull request。如果你准备进行较大的改动,最好先在 issue 中提及,这样我们好能事先讨论这么做有没有必要。 12 | 13 | 译者:如果是关于翻译方面的问题,欢迎提 issue 或 pull request。如果是关乎内容的问题,请移步 14 | 15 | ## 协议 16 | 17 | 中文译本的协议同英文原本,使用[Create Commons Attribution-NonCommercial 3.0 Unported license](http://creativecommons.org/licenses/by-nc/3.0/)。 18 | 你可以自由地分发并修改本书,前提是保证不把它重新销售并保留原作者(我指的是英文原本作者)的作品归属权。 19 | 20 | ## 感谢 21 | 22 | 由于本人水平有限,翻译过程中难免有错漏之处。感谢帮忙校正的各位网友:https://github.com/spacewander/explore-flask-zh/graphs/contributors 23 | 24 | ## 下载 25 | 26 | 见 27 | -------------------------------------------------------------------------------- /zh/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | This is the contents of the book. 4 | 5 | * [前言](1-introduction.md) 6 | * [代码约定](2-coding_conventions.md) 7 | * [环境](3-environment.md) 8 | * [组织你的项目](4-organizing_your_project.md) 9 | * [配置](5-configuration.md) 10 | * [关于视图和路由的进阶技巧](6-advanced_patterns_for_views_and_routing.md) 11 | * [蓝图](7-blueprints.md) 12 | * [模板](8-templates.md) 13 | * [静态文件](9-static_files.md) 14 | * [存储](10-storing-data.md) 15 | * [处理表单](11-handling_forms.md) 16 | * [用户管理的规范](12-handling_users.md) 17 | * [部署](13-deployment.md) 18 | 19 | -------------------------------------------------------------------------------- /zh/book.json: -------------------------------------------------------------------------------- 1 | { 2 | "output": null, 3 | "gitbook": ">=2.2.0", 4 | "generator": "site", 5 | 6 | "title": "Flask之旅", 7 | "description": "关于Flask的最佳实践", 8 | 9 | "extension": null, 10 | 11 | "plugins": [], 12 | 13 | "pluginsConfig": { 14 | "fontSettings": { 15 | "theme": "sepia", 16 | "family": "sans", 17 | "size": 2 18 | } 19 | }, 20 | "pdf": { 21 | "fontSize": 22 22 | }, 23 | 24 | "links": { 25 | "sharing": { 26 | "google": null, 27 | "facebook": null, 28 | "twitter": null, 29 | "weibo": null, 30 | "all": null 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /zh/cover.md: -------------------------------------------------------------------------------- 1 | ![The cover](images/cover.png) 2 | -------------------------------------------------------------------------------- /zh/images/.gitkeep: -------------------------------------------------------------------------------- 1 | # this directory was conjured with black magic :) 2 | -------------------------------------------------------------------------------- /zh/images/balanced-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/balanced-logo.png -------------------------------------------------------------------------------- /zh/images/blueprints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/blueprints.png -------------------------------------------------------------------------------- /zh/images/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/configuration.png -------------------------------------------------------------------------------- /zh/images/conventions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/conventions.png -------------------------------------------------------------------------------- /zh/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/cover.png -------------------------------------------------------------------------------- /zh/images/deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/deployment.png -------------------------------------------------------------------------------- /zh/images/environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/environment.png -------------------------------------------------------------------------------- /zh/images/forms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/forms.png -------------------------------------------------------------------------------- /zh/images/me-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/me-box.png -------------------------------------------------------------------------------- /zh/images/organizing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/organizing.png -------------------------------------------------------------------------------- /zh/images/static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/static.png -------------------------------------------------------------------------------- /zh/images/storing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/storing.png -------------------------------------------------------------------------------- /zh/images/templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/templates.png -------------------------------------------------------------------------------- /zh/images/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/users.png -------------------------------------------------------------------------------- /zh/images/views.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spacewander/explore-flask-zh/67f87557ec99e9f580357dead6924906a21bfcf2/zh/images/views.png --------------------------------------------------------------------------------