Python is an incredibly versatile language. It’s considered to be a staple of modern development. It’s used for the simplest of scripts to complex machine learning and neural network training algorithms.
Python是一种难以置信的通用语言。 它被认为是现代发展的主要内容。 它用于最简单的脚本到复杂的机器学习和神经网络训练算法。
But perhaps the less-known usage of Python is its use as a web server. Overshadowed by more popular frameworks like as Node/Express and Ruby on Rails, Python is often overlooked as a web server choice for most developers.
但是,也许鲜为人知的Python用法是将其用作Web服务器。 Python被Node / Express和Ruby on Rails等更流行的框架所掩盖,对于大多数开发人员而言,Python通常被视为Web服务器的选择。
Having a backend written in Python is really useful for several reasons, among which are:
使用Python编写后端确实很有用,原因有以下几个:
The purpose of this article is to demonstrate how Python can be used to create a full stack web application. In this tutorial, I will be using Flask, a Python “microframework” to developing a web application.
本文的目的是演示如何使用Python创建完整的Web应用程序。 在本教程中,我将使用Flask(Python“微框架”)开发Web应用程序。
I would be remiss not to mention that there are other more popular Python frameworks out there such as Django, but Flask is useful for the budding developer since it is bare bones and requires developers to create/utilize the components they need within the App based on their requirement (rather than calling some command line tool that generates 20 files automatically… lookin’ at you Ruby on Rails). Of course, I won’t be going through how to start a Web App completely from scratch, rather I’ll give you an intro to Flask and then move onto how you can use a project called flask-base to get upto speed quickly in the future.
不用说我还有其他更流行的Python框架,例如Django,但是Flask对于刚起步的开发人员很有用,因为它是基础知识,并且要求开发人员根据以下内容在App中创建/利用他们需要的组件他们的要求(而不是调用某些会自动生成20个文件的命令行工具,而是在Ruby on Rails上查找)。 当然,我不会讲解如何从头开始完全启动Web应用程序,而是向您介绍Flask,然后继续介绍如何使用名为flask-base的项目来快速入门。未来。
Flask is a microframework (read as: It doesn’t come with much) for web development in Python. Before we do a deep(ish) dive, let’s cover some basic concepts of backend development.
Flask是用Python进行Web开发的微框架(读作:不多)。 在深入探讨之前,让我们介绍一些后端开发的基本概念。
Let’s imagine you’re visiting apple.com
and want to go to the Mac section at apple.com/mac/
. How do Apple’s servers know to serve you the specific page that shows the details about Mac devices. It is most likely because they have a web app running on a server that knows when someone looks up apple.com
and goes to the /mac/
section of the website, handle that request and send some pages back. The logic behind figuring out what to do when someone goes to /mac/
is done by a route.
假设您要访问apple.com
并想转到apple.com/mac/
的Mac部分。 Apple的服务器如何知道如何为您提供特定页面,该页面显示有关Mac设备的详细信息。 这很可能是因为他们有一个运行在服务器上的Web应用程序,该应用程序知道何时有人查找apple.com
并转到网站的/mac/
部分,处理该请求并发回一些页面。 弄清楚当有人去/mac/
时该怎么做的逻辑是由一条路线完成的。
So when I visit apple.com
(implied apple.com/
), the /
route handles what is shown. If I go to apple.com/purchase
, there is a /purchase
route. If I go to apple.com/purchase/1
where 1
is some item identifier, there most likely is a generic route handler /purchase/<int:item-
id> that handles that request. Routes can handle both GET and POST requests as well.
因此,当我访问apple.com
(暗示apple.com/
)时, /
路由会处理显示的内容。 如果我转到apple.com/purchase
,则有一条/purchase
路线。 如果我去apple.com/purchase/1
,其中1
是某些项目标识符,则很可能是通用路由处理程序/purchase/<int:item-
id>处理该请求。 路由也可以处理GET和POST请求。
So how do we make a basic Flask app that has routes? Well, let’s take a look at the docs. Create a Python file called hello.py
that contains the following.
那么我们如何制作具有路线的基本Flask应用程序呢? 好吧,让我们看一下文档。 创建一个名为hello.py
的Python文件,其中包含以下内容。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World"
Let’s break down what’s happening here.
让我们分解一下这里发生的事情。
We create an instance of a Flask App. The argument passed into the Flask instantiator (__name__
) evaluates to a string that "names" the Flask App. When run from the command line, __name__ == "__main__"
. You can set the first argument to whatever you want.
我们创建一个Flask应用的实例。 传递给Flask实例化器( __name__
)的参数的计算结果为一个为Flask应用程序“命名”的字符串。 从命令行运行时, __name__ == "__main__"
。 您可以将第一个参数设置为所需的任何值。
We set up a route /
on our App that executes the hello()
function immediately below it when that route is visited. Note that the function must return a string or a rendered template.
我们在应用程序上设置了一个路由/
,当访问该路由时,将在其下方立即执行hello( )
函数。 请注意,该函数必须返回字符串或渲染的模板。
On the command line, let’s set up something called a virtual environment (it will help us isolate our development environment and package installations from the rest of our system).
在命令行上,让我们设置一个称为虚拟环境的东西(它将帮助我们将开发环境和软件包安装与系统的其余部分隔离开)。
If you haven’t done so already, install pip via easy_install pip
(you may need to run sudo
in front of this if you are on a Mac.
如果尚未安装,请通过easy_install pip
安装pip(如果您使用的是Mac,则可能需要在此之前运行sudo
。
Run pip install virtualenv
to install virtualenv using pip
运行pip install virtualenv
以使用pip安装virtualenv
In the directory of your App, create your virtual environment by running virtualenv venv
(this creates a virtual environment in a folder called venv
of the current directory).
在您应用程序的目录中,通过运行virtualenv venv
创建虚拟环境(这会在当前目录的venv
文件夹中创建一个虚拟环境)。
Run source venv/bin/activate
to activate the virtual environment. This is specifically required to install packages into it. You can deactivate the virtual environment by running deactivate
from the command line. Pretty simple.
运行source venv/bin/activate
以激活虚拟环境。 将软件包安装到其中特别需要此操作。 您可以通过从命令行运行deactivate
来停用虚拟环境。 很简单
Now that our virtual environment is installed and activated, let’s install Flask. It’s really simple, just run pip install Flask
. We can then run the example from earlier by writing the following in our command line.
现在已经安装并激活了我们的虚拟环境,让我们安装Flask。 这非常简单,只需运行pip install Flask
。 然后,我们可以通过在命令行中编写以下代码来运行该示例。
FLASK_APP=hello.py flask run
You should see something like * Running on http://localhost:5000/
in your terminal. And if you visit that link in your browser, you'll see a page with just Hello World
.
您应该在终端中看到* Running on http://localhost:5000/
。 而且,如果您在浏览器中访问该链接,则会看到仅包含Hello World
的页面。
Note: The code for this project can be found at this repository on GitHub.
注意:该项目的代码可以在GitHub的此存储库中找到 。
Now let’s figure out some project to create in order to demonstrate the full capabilities of Flask. One recent project I came up with is a club rating app called “PennClubReview”.
现在,让我们找出一些要创建的项目,以演示Flask的全部功能。 我最近提出的一个项目是一个名为“ PennClubReview”的俱乐部评级应用程序。
I’m currently attending the University of Pennsylvania. One of the most common problems that freshmen on campus face is choosing which clubs to join on campus. This process is further complicated by the fact that some clubs are very competitive to get into, have multiple interview rounds, and require a large time commitment. Often, none of these aspects of clubs are discussed during club information sessions.
我目前正在宾夕法尼亚大学上学。 大学新生面对的最普遍的问题之一是选择哪些俱乐部加入校园。 由于有些俱乐部进入竞争非常激烈,需要进行多次面试,而且需要大量时间投入,因此这一过程变得更加复杂。 通常,在俱乐部信息会议期间不会讨论俱乐部的这些方面。
So, in order to combat this issue, we can create an app where
因此,为了解决这个问题,我们可以创建一个应用
In order to develop this app, we’ll need to have some more components in addition to Flask, such as a backing database, a login management system, some way to organize routes, and handle emailing. We could code this from scratch. But, there is already an amazing boilerplate that can give you a great starting place.
为了开发该应用程序,除了Flask之外,我们还需要其他一些组件,例如后备数据库,登录管理系统,一些组织路线和处理电子邮件的方法。 我们可以从头开始编写代码。 但是,已经有一个惊人的样板可以为您提供一个很好的起点。
Flask-base is a project that my friends and I developed as part of a student run nonprofit called Hack4Impact. We work with nonprofits over a semester to develop technical projects that help them accomplish their mission.
Flask-base是我和我的朋友们在一个学生组织的非营利组织Hack4Impact中开发的一个项目。 我们在一个学期内与非营利组织合作,开发可帮助他们完成任务的技术项目。
While working on so many projects, we found out that we would often be repeating the same code across all of our applications. So we decided to create a single code base containing the most common parts that any App we made would need. This code base would include:
在进行许多项目时,我们发现我们经常在所有应用程序中重复相同的代码。 因此,我们决定创建一个代码库,其中包含我们制作的任何App所需的最常见部分。 该代码库将包括:
It recently became fairly popular, garnering 1200+ GitHub stars over the course of a few months. This codebase is perfect for what we are trying to set up. You can find the GitHub repo containing the code for Flask base here.
它最近变得相当流行,在几个月的时间内获得了1200多个GitHub星。 该代码库非常适合我们尝试设置的内容。 您可以在此处找到包含Flask base代码的GitHub存储库。
First let’s clone flask-base. Follow the instructions on the README.md page. In a nutshell run the following.
首先,让我们克隆flask-base。 请按照README.md页上的说明进行操作。 简而言之,运行以下命令。
git clone https://github.com/hack4impact/flask-base.git
cd flask-base
virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
python manage.py recreate_db
python manage.py setup_dev
OK. I’ll elaborate on what we’ve done here.
好。 我将在这里详细说明我们所做的。
Read the package dependencies in the requirements.txt
file and install all of them via pip
.
阅读requirements.txt
文件中的软件包依赖项,然后通过pip
安装所有依赖项。
Additionally, let’s create a running database migration. This will keep track of changes in our database models without needing to recreate our database (i.e. remove all the information and then rebuild the database from scratch). Migrations allow us to preserve information. We can do this via the command below.
另外,让我们创建一个正在运行的数据库迁移。 这将跟踪数据库模型中的更改,而无需重新创建数据库(即删除所有信息,然后从头开始重建数据库)。 迁移使我们能够保留信息。 我们可以通过下面的命令来做到这一点。
To run the App, run honcho start -f Local
(you'll need to install Honcho if you haven't already). If you have any issues, chances are they have been addressed in the README of flask-base already. Now you can visit localhost:5000
and pull up a running flask-base application.
要运行该应用程序,请运行honcho start -f Local
(如果尚未安装,则需要安装Honcho)。 如果您有任何问题,很可能已经在flask-base的自述文件中解决了。 现在您可以访问localhost:5000
并启动一个正在运行的flask-base应用程序。
To log into the App as an administrator, go to the login link and type in for the username flask-base-admin@example.com
with a password password
. You can then invite new users into the application from the administrator screen. Note that before you do so, you'll need to create a config.env
file that contains the following two variables:
要以管理员身份登录该应用,请转到登录链接,然后输入用户名flask-base-admin@example.com
和密码password
。 然后,您可以从管理员屏幕邀请新用户加入应用程序。 请注意,在执行此操作之前,您需要创建一个包含以下两个变量的config.env
文件:
MAIL_PASSWORD=someSendGridPasswordMAIL_USERNAME=someSendGridUsername
Upon creation of a user account, the account remains unconfirmed until the new invited user clicks a link sent to their email. Additionally, a user can register for the App and will go through a similar authentication flow with regards to confirmation.
创建用户帐户后,该帐户将一直未确认,直到新邀请的用户单击发送到其电子邮件的链接为止。 此外,用户可以注册该应用程序,并将通过类似的身份验证流程进行确认。
Look through the flask-base documentation to get a better sense of some of the capabilities of flask-base out of the box. For now, we’re going to move on to how we can use it to make our App.
查看烧瓶瓶文档,以更好地了解烧瓶瓶的某些功能。 现在,我们将继续介绍如何使用它来制作我们的App。
All our database logic is wrapped by the SQLAlchemy ORM so we don’t have to make very verbose database statements to run queries or add / delete records. All the database models (think of them as classes) are contained within the app/models
folder. Let's think of some models that are needed for the application itself.
我们所有的数据库逻辑都由SQLAlchemy ORM封装,因此我们不必制作非常冗长的数据库语句即可运行查询或添加/删除记录。 所有数据库模型 (将它们视为类)都包含在app/models
文件夹中。 让我们考虑一下应用程序本身所需的一些模型。
So we need to have a Club
model that contains the name
of the club (Datatype: String), a club description
(Datatype: Text) and a variable is_confirmed
(Datatype: Boolean) to keep track of whether a club that is suggested has been approved by an administrator to be shown. Additionally, we want some way to refer to the categories of a club, and another way to refer to the question answers that belong to a club.
因此,我们需要一个Club
模型,其中包含Club
的name
(数据类型:字符串),俱乐部description
(数据类型:文本)和变量is_confirmed
(数据类型:布尔值),以跟踪所建议的俱乐部是否已被由管理员批准显示。 此外,我们需要某种方式来引用俱乐部的类别,以及另一种方式来引用属于俱乐部的问题答案。
Let’s think about how Clubs and Club Categories should relate to each other. We can think of it as follows. A club has many categories (e.g. a club can be a Social Impact
and Tech
club) and a club category can belong to many clubs (e.g. there can be many Tech
clubs on campus). The only attribute this ClubCategory
has a category_name
(Datatype: String).
让我们考虑一下“俱乐部”和“俱乐部类别”应该如何相互关联。 我们可以这样认为。 俱乐部具有许多类别(例如,一个俱乐部可以是“ Social Impact
和Tech
俱乐部),而俱乐部类别可以属于许多俱乐部(例如,校园中可以有很多Tech
俱乐部)。 该ClubCategory
的唯一属性具有category_name
(数据类型:字符串)。
We can create this relationship (a many to many relationship), via an association table.
我们可以通过关联表创建此关系(多对多关系)。
Now how do we encode that logic into flask-base? First, create a file called club.py
in app/models
. First let's create the Club
and ClubCategory
models.
现在,我们如何将这种逻辑编码为flask-base? 首先,在app/models
创建一个名为club.py
的文件。 首先,我们创建Club
和ClubCategory
模型。
So now we have two models, but they aren’t connected to each other. Each of them have individual attributes, but neither can be explicitly connected to each other. We make the connection via an association as I mentioned earlier. After the db
import, add the following lines.
因此,现在我们有两个模型,但是它们没有相互连接。 它们每个都具有各自的属性,但是两者都不能明确地相互连接。 如前所述,我们通过关联建立连接。 db
导入后,添加以下行。
What this does is create a new association table (an intermediary between the Club and ClubCategory model). There are two columns in this table club_id
and club_category_id
which refer to the respective id’s of their respective models (note that the id
attribute is a Primary Key within each model, i.e. the thing that is unique for each record). But within the association table, we refer to these Primary Keys as Foreign Keys (because they are refering to other tables). Additionally, we need to add a line to the Club
model at the bottom.
这样做是创建一个新的关联表(Club和ClubCategory模型之间的中介)。 该表中有两列club_id
和club_category_id
,它们引用各自模型的各自ID(请注意, id
属性是每个模型内的主键,即每个记录的唯一属性)。 但是在关联表中,我们将这些主键称为外键(因为它们正在引用其他表)。 此外,我们需要在底部的Club
模型中添加一条线。
categories = db.relationship( 'ClubCategory', secondary=club_category_assoc, backref='clubs')
And this actually creates the bidirectional relationship between the Club
and ClubCategory
models and sets up a relationship between Club
and ClubCategory
using the club_category_assoc
association table. The backref
tells the ClubCategory
model how to refer to the Club
models. So, with a given club club
, you can run club.categories
to get an array of category object backs. With a given ClubCategory
called category
, you can get all the clubs in that category by doing category.clubs
.
这实际上创建之间的双向关系, Club
和ClubCategory
达之间的关系模型和集Club
和ClubCategory
使用club_category_assoc
关联表。 backref
告诉ClubCategory
模型如何引用Club
模型。 因此,对于给定的club club
,您可以运行club.categories
以获得类别对象club.categories
阵列。 对于给定的ClubCategory
称为category
,你可以通过执行获得该类别所有的俱乐部category.clubs
。
You can see this in action by doing the following:
您可以通过执行以下操作来查看此操作:
In app/models/__init__.py
add the line
在app/models/__init__.py
添加以下行
And then run python manage.py shell
. Run the following commands to interact with your database models (note that >
;>> indicates an input you put in).
然后运行python manage.py shell
。 运行以下命令与数据库模型进行交互(请注意, >
; >>表示您输入的输入)。
Great! We now have a working Club and ClubCategory model. Now let’s move onto the Question
and Answer
models. For a question, we need to keep track of the content
of the question which will be a String containing the text of the question itself. We will also include a max_rating
attribute that will contain the maximum rating an individual can give for the question. For example, if the question content is "Rate the community of the club 10 is the best", we could set max_rating
to be 10. Additionally, we'll keep track of a boolean free_response
to determine whether we will allow people to include an optional extra response that is long form. Lastly, we will need to have a relation to the Answer
model. This will be a one to many relation because a question can have multiple answers but an answer can only have one question.
大! 现在,我们有了一个有效的Club和ClubCategory模型。 现在,让我们走上了Question
和Answer
的机型。 对于问题,我们需要跟踪问题的content
,该content
将是包含问题本身文本的字符串。 我们还将包括一个max_rating
属性,该属性将包含个人可以对问题给出的最高评分。 例如,如果问题内容为“ max_rating
俱乐部10的社区为最佳”,则可以将max_rating
设置为10。此外,我们将跟踪布尔值free_response
以确定是否允许人们加入长格式的可选额外响应。 最后,我们需要与Answer
模型有关系。 这将是一对多的关系,因为一个问题可以有多个答案,但是一个答案只能有一个问题。
The Answer
model will have the following attributes:
Answer
模型将具有以下属性:
an answer
attribute corresponding the the free text response of an answer (if the question allows a free text response)
对应于answer
的自由文本响应的answer
属性(如果问题允许自由文本响应)
a rating
ranging from 1 to whatever is the max rating for the question
rating
范围从1到问题的最高评分
a user_id
relating to the user who wrote the question (once again a user can have many answers, but an answer can only have one user)
与写问题的用户有关的user_id
(再次,一个用户可以有很多答案,但是一个答案只能有一个用户)
a question_id
referring to the question
that the answer belongs to
一个question_id
指的是question
的答案属于
a club_id
referring to the club
the answer belongs to
引用答案所属club
的club_id
Let’s create a file question.py
让我们创建一个文件question.py
Most of the stuff in here is fairly straightforward except for the last line. The last line connects the Question
and Answer
models. It says to set up a relationship with the Answer
model which can refer to the Question
model via the keyword question
. Given an answer a
, you can get the question via a.question
and given a question q
, you can get the answer associated with it via q.answers
. Let's now set up the Answer
model. Create a new file called answer.py
in the models folder and paste in the following.
除了最后一行,这里的大多数内容都非常简单。 最后一行连接Question
和Answer
模型。 它说要与Answer
模型建立关系,该模型可以通过关键字question
引用Question
模型。 给定答案a
,您可以通过a.question
获得问题,给定问题q
,您可以通过q.answers
获得与其相关的答案。 现在,我们来建立Answer
模型。 在models文件夹中创建一个名为answer.py
的新文件,然后粘贴以下内容。
So this file is much longer, but recall that there are many things an answer is related to. Let’s start at the beginning, note that question_id
refers to the Question
model via the foreign key questions.id
(the id
column of the questions
table (which contains records of instances of the Question
model).
因此,该文件要长得多,但是请记住,答案与很多事情有关。 让我们从头开始,需要注意的是question_id
指Question
通过外交重点型号questions.id
(该id
的列questions
表(其中包含的实例记录Question
模型)。
Note that we also have a user_id
column that refers to a user. Let's go into user.py
within the app/models
folder and add the following line after the role_id
declaration.
请注意,我们还有一个user_id
列,它引用了一个用户。 让我们进入app/models
文件夹中的user.py
,并在role_id
声明之后添加以下行。
This statement uses very similar syntax to that of the Question
model.
该语句使用与Question
模型非常相似的语法。
Also note that there is a club_id
attribute that refers to the club the answer is associated with. Edit the club.py
file to include the following line as the last attribute of the Club
model.
还要注意,有一个club_id
属性指向与答案相关联的俱乐部。 编辑club.py
文件,以club.py
下行作为Club
模型的最后一个属性。
Finally, add these two lines to __init__.py
in app/models
最后,将这两行添加到app/models
__init__.py
And now we should be able to play around with our databases as follows.
现在,我们应该可以按如下方式使用我们的数据库了。
Lastly, let’s address the newAnswer
method. This method is used to insert new answers into the database while making sure that if a user has already answered that question, we delete it and insert the new response.
最后,让我们解决newAnswer
方法。 此方法用于将新答案插入数据库,同时确保如果用户已经回答了该问题,我们将其删除并插入新答案。
Once again, we can run python manage.py shell
再一次,我们可以运行python manage.py shell
There, we are now done with the models :)
在那里,我们现在完成了模型:)
Now the database stuff is out of way, let’s create the way for users to interact with the application itself. First let’s set up some blueprints.
现在数据库内容已不合时宜,让我们为用户创建与应用程序本身进行交互的方式。 首先,让我们设置一些蓝图。
Blueprints are a great way to organize you flask application. It allows you to mount all routes that are associated with each other in a single file. For example, for all actions associated with an account, such as account management, user password reset, forgot password, etc. would be included in the account
blueprint.
蓝图是组织烧瓶应用程序的好方法。 它允许您在单个文件中装载彼此关联的所有路由。 例如,对于与帐户关联的所有操作,例如帐户管理,用户密码重置,忘记密码等,都将包括在account
蓝图中。
Each blueprint has a folder associated with it under app
. For example, there is an account/
folder and a folder under templates
containing the actual html templates that will be rendered to the user.
每个蓝图在app
下都有一个与之关联的文件夹。 例如,在templates
下有一个account/
文件夹和一个文件夹,其中包含将呈现给用户的实际html模板。
Let’s add some blueprints. Before the return app
line of app/__init__.py
add the following.
让我们添加一些蓝图。 在app/__init__.py
的return app
行之前,添加以下内容。
These calls create blueprints mounted at the url prefixes /club
, /question
, and /category
respectively. Let's create the folders club
, question
, and category
for each of the blueprints. Within each of the folders create the files __init__.py
, forms.py
, and views.py
.
这些调用分别创建在URL前缀/club
, /question
和/category
挂载的蓝图。 让我们为每个蓝图创建文件夹club
, question
和category
。 在每个文件夹中创建文件__init__.py
. forms.py
, forms.py
和views.py
。
I’ll walk through how to set up the views/templates for club
blueprint. The other views are fairly easy to understand from the code.
我将逐步介绍如何为club
蓝图设置视图/模板。 其他视图从代码中很容易理解。
So within the club view, we want to have a few different things to show
因此,在俱乐部视图中,我们想展示一些不同的东西
Let’s first create a couple of forms within forms.py
that we will then pass to our views, specifically the view that handles create a new club and the one that edits club information.
让我们首先在forms.py
中创建几个表单,然后将它们传递到视图,特别是处理创建新俱乐部和编辑俱乐部信息的视图。
In forms.py
for club
add the following lines:
在forms.py
为club
增加下面几行:
Flask-base uses wtforms
to create forms. wtforms allows us to create forms in an object oriented manner where each form is a class.
Flask-base使用wtforms
创建表单。 wtforms允许我们以面向对象的方式创建表单,其中每个表单都是一个类。
So we create two forms, one called NewClubForm
that extends the base wtforms
Form
class, and has 3 fields - name
(Datatype: Text), desc
(Datatype: Text) containing the description of the club, and categories
(a multiple select dropdown). With the categories
field, we query the ClubCategory
model with a Lambda function (which is basically an anonymous function) for the category names and populate the category select field options with the results from that query.
因此,我们创建了两种形式,一种名为NewClubForm
,用于扩展基础wtforms
Form
类,并具有3个字段- name
(数据类型:文本), desc
(包含俱乐部说明)的desc
(数据类型:文本)和categories
(多选下拉列表) 。 使用categories
字段,我们使用Lambda函数(基本上是一个匿名函数)查询ClubCategory
模型的类别名称,并使用该查询的结果填充类别选择字段选项。
Lastly, we have a submit
field, so the submit button can be rendered.
最后,我们有一个submit
字段,因此可以渲染Submit按钮。
Next, we have an EditClubForm
which extends the NewClubForm
field set by adding a new field called is_confirmed
. Recall that is_confirmed
in our Club
model determines whether the given club instance can be shown or not shown to the public. We will be adding the function for a club to be suggested by users, and by default, suggested clubs are hidden until approved by an admin. We also overwrite the submit
field to display the text "Edit Club".
接下来,我们有一个EditClubForm
,它通过添加一个称为is_confirmed
的新字段来扩展NewClubForm
字段集。 回想一下,我们Club
模型中的is_confirmed
决定了是否可以向公众显示给定的俱乐部实例。 我们将为用户建议的俱乐部添加功能,默认情况下,建议的俱乐部在管理员批准之前是隐藏的。 我们还将覆盖submit
字段以显示文本“编辑俱乐部”。
In views.py
under club/
, we create a few routes.
在club/
下的views.py
,我们创建了一些路线。
/new-club
(GET, POST) LOGIN PROTECTED: The renders and accepts data from form for creating a new club.
/new-club
(获取,发布)登录保护:渲染并接受来自表单的数据以创建新俱乐部。
/clubs
(GET) ADMIN PROTECTED: Renders all the clubs
/clubs
(GET)受保护:渲染所有俱乐部
/<int:club_id>/(
:info) (GET) LOGIN PROTECTED: Will render out info for a given club instance with id = c
lub_id and can access the route at /club/1 or /club/1/info.
/<int:club_id>/(
:info)(获取)登录保护:将呈现with id = c
lub_id的给定俱乐部实例的信息,并可以访问/ club / 1或/ club / 1 / info的路由。
/<int:club_id>/change-club-d
etails (GET, POST) ADMIN PROTECTED: Render and accept data from form for editing club information.
/<int:club_id>/change-club-d
细节(GET,POST)受保护的管理员:渲染并接受来自表单的数据以编辑俱乐部信息。
/<int:club_id>/
delete (GET) ADMIN PROTECTED: Render page to delete club
/<int:club_id>/
删除(GET)受保护:呈现页面以删除俱乐部
/<int:club_id>/_
delete (GET) ADMIN PROTECTED: Delete club with club id.
/<int:club_id>/_
删除(GET)受保护:删除具有俱乐部ID的俱乐部。
For the first route /new-club
, we want to also allow regular users to create a new club, which is why we only login protect it. Let's see how we can make a route for this.
对于第一个路线/new-club
,我们还希望允许普通用户创建一个新的俱乐部,这就是我们仅登录保护它的原因。 让我们看看如何为此做一个路线。
Breaking down the code. In line 1, we declare where the route will be accessible. For example, it will be on the club
blueprint at the sub-route /new-club.
The full URL it can be accessed at is basedomain.com/club/new-club
.
分解代码。 在第1行中,我们声明可在何处访问该路由。 例如,它将位于子路线/new-club.
的club
蓝图上/new-club.
可以访问的完整URL是basedomain.com/club/new-club
。
We then put a route decorator @login_required
on the route, this decorator will throw a 403 error if the user isn't logged in but will also allow the user to view the route if they are logged in.
然后,我们在路由上放置一个路由装饰器@login_required
,如果用户未登录,该装饰器将引发403错误,但如果用户登录,还将允许用户查看路由。
Next, we define a method to handle requests to the route (note that this name must be unique). This method can be referred to by club.new_club
in Jinja templating.
接下来,我们定义一个方法来处理对路由的请求(请注意,该名称必须是唯一的)。 该方法可以由Jinja模板中的club.new_club
。
We then instantiate our NewClubForm
we created earlier. In the following line, we check to see if the form submission was valid (note that this route will also accept POST requests to it) via the form.validate_on_submit()
method. If it is, then we create a new Club
instance with name
, description
, and categories
corresponding to the form fields. Note for is_confirmed
we set it equal to whether the current user is an administrator or not (because if a regular user submits to this form, we want the new club to not appear to everyone, hence we set is_confirmed
to False). We then add the new club instance to the database session and commit the session.
然后,我们实例化我们NewClubForm
创建的NewClubForm
。 在下一行中,我们通过form.validate_on_submit()
方法检查表单提交是否有效(请注意,此路由还将接受对其的POST请求form.validate_on_submit()
。 如果是,那么我们将创建一个新的Club
实例,其name
, description
和categories
与表单字段相对应。 请注意,对于is_confirmed
我们将其设置为等于当前用户是否为管理员(因为如果常规用户提交此表单,我们希望新俱乐部不会出现在所有人is_confirmed
,因此将is_confirmed
设置为False)。 然后,我们将新的club实例添加到数据库会话中并提交该会话。
Lastly, if the user submitting the form is not an admin, we generate a link to send to the administrator of the form via email. This link should go directly to the admin change_club_details
route which will allow the admin to toggle is_confirmed
. We then look through the database for all users with an administrator role and add an emailing task to our redis queue. Within the get_queue()
method, we enqueue the send_email
job specifically, setting the recipient to the admin email, the subject equal to
最后,如果提交表单的用户不是管理员,我们将生成一个链接,以通过电子邮件发送给表单管理员。 该链接应直接转到admin change_club_details
路由,这将允许管理员切换is_confirmed
。 然后,我们通过数据库查找具有管理员角色的所有用户,并将电子邮件任务添加到我们的Redis队列中。 在get_queue()
方法中,我们专门使send_email
作业入队,将收件人设置为管理员电子邮件,主题等于
add the club instance (to be used as a templating variable), and the link (also to be used as a templating variable).
添加club实例(用作模板变量)和链接(也用作模板变量)。
We also pass the template
which we create in app/templates/club/email/suggested_club.html
and .txt
. The content is as follows for the html file:
我们还将在app/templates/club/email/suggested_club.html
和.txt
创建的template
传递给我们。 html文件的内容如下:
and for the .txt file
和.txt文件
Next we will take care of the /clubs
route that renders all the clubs in a table. For the route handler, we can just pass in all the clubs into a template.
接下来,我们将处理/clubs
路由,该路由将所有俱乐部显示在表中。 对于路由处理程序,我们可以将所有俱乐部都传递到模板中。
And the club template we render is located at app/templates/club/clubs.html
with the following content.
我们渲染的俱乐部模板位于app/templates/club/clubs.html
具有以下内容。
Most of this is fairly straightforward if you know Jinja (or any templating language). Basically, the for loop {% for c in clubs %} ... {% endfor %}
will go through all the clubs and for each club, it will render the club name {{ c.name }}
and the club categories.
如果您了解Jinja(或任何模板语言),那么大多数内容都非常简单。 基本上,for循环{% for c in clubs %} ... {% endfor %}
将遍历所有俱乐部,并且对于每个俱乐部,它将呈现俱乐部名称{{ c.name }}
和俱乐部类别。
Note that for each of the clubs rendered, we also include a line:
注意,对于每个渲染的俱乐部,我们还包括一行:
This links to the individual club info page for the given club instance that is rendered. Let’s move on to making that route.
这将链接到呈现的给定俱乐部实例的各个俱乐部信息页面。 让我们继续前进。
Note that for this view we only need to pass in the club instance information to the manage_club view. We can do this easily via:
请注意,对于此视图,我们仅需要将俱乐部实例信息传递到manage_club视图。 我们可以通过以下方法轻松完成此操作:
We can also set up a few other routes because our manage_club.html
page actually does display multiple routes.
我们还可以设置其他一些路由,因为我们的manage_club.html
页面实际上确实显示了多个路由。
Let’s set up the /change-club-details
route which just renders and accepts info from the EditClubForm
form.
让我们设置/change-club-details
路由,该路由仅呈现并接受EditClubForm
表单中的信息。
Note that when saving the club.is_confirmed
field, we need to convert the string True
and False
values to their boolean counterparts as stated in the forms.py
specification for EditClubForm
. We do this via a custom defined function bool
which is defined as follows:
请注意,在保存club.is_confirmed
字段时,我们需要将字符串True
和False
值转换为布尔值,如forms.py
规范中EditClubForm
。 我们通过定义如下的自定义函数bool
来做到这一点:
The python default bool
will return True
if any string is defined, including False'
, hence we need to define our own function.
如果定义了任何字符串(包括False'
,则python default bool
将返回True
,因此我们需要定义自己的函数。
We also define the delete
to render the delete page and the _delete
function that actually deletes the club instance.
我们还定义了delete
以呈现删除页面,并定义了_delete
函数,该函数实际上删除了Club实例。
Note that for the _delete
route, we have a redirect towards the clubs
route that lists all the club instances.
请注意,对于_delete
路线,我们有一个指向clubs
路线的重定向,其中列出了所有club实例。
Now we move to the manage_club.html
template at app/templates/club/manage_club.html
. The content of that is as follows:
现在,我们转到app/templates/club/manage_club.html
上的manage_club.html
模板。 其内容如下:
Let’s break down this file. On the first line we are just extending our base layout and then we import form macros. Macros are basically methods in Jinja.
让我们分解这个文件。 在第一行,我们只是扩展基本布局,然后导入表单宏。 宏基本上是Jinja中的方法。
We have a endpoints
variable that will contain links to the different parts of the management page. In the navigation
macro, we render all the individual elements of the list in the endpoints
.
我们有一个endpoints
变量,它将包含指向管理页面不同部分的链接。 在navigation
宏中,我们在endpoints
呈现列表的所有单个元素。
We also create a club_info
macro that will contain the information related to the club and all the answers associated with the club by doing the following
我们还通过执行以下操作,创建了一个club_info
宏,其中包含与俱乐部有关的信息以及与俱乐部相关的所有答案
Lastly, we actually write the logic for rendering the page within the {% block content %} ... {% endblock %}
tags. We switch between the subpages to render by checking the request.endpoint
to see if it is the deletion endpoint or if there is a form (in which case render the form). Otherwise, we just call the club_info
macro. And we’re done with the club routes and views. Most of the other routes for category and questions follow a similar type of logic.
最后,我们实际上编写了在{% block content %} ... {% endblock %}
标签内呈现页面的逻辑。 我们通过检查request.endpoint
在子页面之间进行渲染,以查看它是否是删除终结点或是否存在表单(在这种情况下,呈现表单)。 否则,我们仅调用club_info
宏。 我们已经完成了俱乐部的路线和景观。 关于类别和问题的其他大多数路线都遵循类似的逻辑类型。
The main route is the public facing part of the application. The route behaviors are as follows
主要途径是应用程序面向公众的部分。 路由行为如下
The first route is very straightforward and matches the `/clubs` route we implemented earlier. The only difference is that the `questions` must also be passed in.
第一条路线非常简单,并且与我们之前实现的`/ clubs`路线相匹配。 唯一的区别是还必须传递“问题”。
The most interesting part here is how to calculate the average rating and pass that into the route. I create a list called all_c
and for each of the clubs, I create a club_obj
containing basic information for the club.F or each of the answers for a club, I add a new property of the club_obj
correponding to the question content, if one doesn't exist already. I append each of the ratings to a list and then iterate through each of the properties of the club_obj
. If the property has a value that is of type list, then I replace that list with the average of the ratings in that list. I then append club_obj
to all_c
and pass that into the template.
这里最有趣的部分是如何计算平均评分并将其传递给路线。 我创建一个名为all_c
的列表,并为每个俱乐部创建一个club_obj
其中包含该俱乐部的基本信息。F或俱乐部的每个答案,如果要添加一个与问题内容相对应的club_obj
的新属性还不存在。 我将每个等级附加到列表中,然后遍历club_obj
每个属性。 如果该属性具有列表类型的值,那么我将该列表替换为该列表中评分的平均值。 然后,我将club_obj
附加到all_c
并将其传递到模板中。
For the submit-review
route, I need to create a form dynamically based on the questions that I have in my Question
model. The code is as follows:
对于submit-review
路线,我需要根据我的Question
模型中存在的问题动态创建一个表单。 代码如下:
We first create a dummy form class that inherits from the base Form
class. Then for each of the questions, we ceate new form fields setattr(F, ...)
on the dummy form F
. The setattr
method takes as its second argument the name of the form field. We set this to the id of the question with _q
appended to it corresponding to the rating and _resp
corresponding to a free response if indicated. For the rating form field, we create a SelectField
with choices from 1 to the max_rating
.
我们首先创建一个从基础Form
类继承的虚拟Form
类。 然后针对每个问题,在虚拟表单F
上创建新的表单字段setattr(F, ...)
。 setattr
方法将表单字段的名称作为第二个参数。 我们将其设置为问题的ID,并在其ID后面附加_q
对应等级,并在_resp
后面附加免费响应(如果有指示)。 对于评级表单字段,我们创建一个SelectField
,其选择范围为1到max_rating
。
To handle a form submission, we use the same if statement form.validate_on_submit()
but instead of looking for specific named fields of the form, we instead iterate through all the fields of the form
and create a new answer using the newAnswer
method. This method will delete any previous response before adding a new one for the user if they responded for this club.
为了处理表单提交,我们使用if语句相同form.validate_on_submit()
但不是寻找方式的具体命名字段,我们不是通过迭代所有领域的form
,并使用了新的答案newAnswer
方法。 如果用户对此俱乐部做出了响应,则此方法将删除之前的所有响应,然后再为用户添加新的响应。
Now that most of the App is done, we can launch this app on Heroku.
现在,大多数应用程序都已完成,我们可以在Heroku上启动该应用程序。
If you’ve never signed up with Heroku
如果您从未注册过Heroku
If you haven’t set up a git repo initially, run git init
and log in with your Heroku account.
如果您最初没有设置git repo,请运行git init
并使用您的Heroku帐户登录。
Then, git add
all the relevant files (i.e. anything but config.env
and venv/
) and run pip freeze > requirements.
txt to make sure that all the dependencies you have installed are included.
然后, git add
所有相关文件(即config.env
和venv/
任何文件)并运行pip freeze > requirements.
venv/
pip freeze > requirements.
txt以确保包括所有已安装的依赖项。
Run heroku create
to make a new Heroku instance and run git push heroku master
to add your files to the Heroku repository.
运行heroku create
一个新的Heroku实例,并运行git push heroku master
将文件添加到Heroku存储库。
After that is done running, you’ll need to set some environment variables with the following command
运行完之后,您需要使用以下命令设置一些环境变量
Once that is done, run the following which will create the database on Heroku
完成后,运行以下命令,这将在Heroku上创建数据库
and then the following command will create the administrator account.
然后以下命令将创建管理员帐户。
You’ll also need to create a Redis togo instance to handle the task queue
您还需要创建一个Redis togo实例来处理任务队列
and lastly run the following command which will tell Heroku to spin up a dyno (read as sub-server) that handles our Redis queue.
最后运行以下命令,该命令将告诉Heroku启动处理我们Redis队列的dyno(读取为子服务器)。
You can then run heroku open
to open your running Heroku app in a separate window.
然后,您可以运行heroku open
来在单独的窗口中打开正在运行的Heroku应用程序。
It’s pretty easy to copy the current application structure and extend it to add more information/routes to the App. Just view any of the previous routes that have been implemented. If, for some reason, you want to include a file upload of some type, you’ll need to integrate the App with Amazon S3 if you plan to run the app on Heroku (since it has an ephemeral file system).
复制当前应用程序结构并对其进行扩展以向该应用程序添加更多信息/路由非常容易。 只需查看之前已实施的任何路由即可。 如果出于某种原因想要包括某种类型的文件上传,那么如果您计划在Heroku上运行该应用程序(因为它具有临时文件系统),则需要将该应用程序与Amazon S3集成。
Overall, flask-base provides a great starting point for making your flask application. Of course the backend may be fairly verbose to code, but as a result, it gives you very granular control over your app.
总体而言,烧瓶基可以为您的烧瓶应用提供一个很好的起点。 当然,后端的代码可能相当冗长,但是结果是,它使您可以非常精细地控制应用程序。