Python Flask框架基础(四)表单

生成表单

在HTML中,表单通过<form>标签创建,表单中使用<input>标签表示各种输入字段,<label>标签用来定义标签文字。例如下面的HTML表单:

<form method="post">
    <label for="username">Username</label><br>
    <input type="text" name="username" placeholder="Héctor Rivera"><br>
    <label for="password">Password</label><br>
    <input type="password" name="password" placeholder="19001130"><br>
    <input id="remember" name="remember" type="checkbox" checked>
    <label for="remember"><small>Remember me</small></label><br>
    <input type="submit" name="submit" value="Log in">
</form>

渲染后的效果如下:
请添加图片描述

WTForms是一个使用Python编写的表单库,它使得表单的定义、验证(服务器端)和处理变得非常轻松。

WTForms支持在Python中使用类定义表单,然后直接通过类定义生成对应的HTML代码,这种方式更方便且表单更易于重用。因此,除非是非常简单的程序,否则不会在模板中直接使用HTML编写表单。Flask-WTF集成了WTForms,使用它可以在Flask中更加方便地使用WTForms。

当使用WTForms创建表单时,表单由Python自定义类表示(例如下面的LoginForm类)这个类继承从WTForms导入的Form基类。一个表单由若干输入字段组成,这些字段分别用表单类的类属性(例如username、password)来表示。每个字段属性通过实例化WTForms提供的字段类表示。字段属性的名称将作为对应HTML<input>元素的name属性及id属性值。

例如下面的代码会生成前面一样的表单:

### 文件forms.py ###
from wtforms import Form, StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length
class LoginForm(Form):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
    remember = BooleanField('Remember me')
    submit = SubmitField('Log in')

### 文件app.py ###
from flask import Flask, render_template
import os
from forms import LoginForm

app = Flask(__name__)
app.secret_key = "secret string"
app.secret_key = os.getenv('SECRET_KEY', 'secret string')

@app.route('/basic')
def basic():
    form = LoginForm()
    return render_template('basic.html', form=form)

### 文件basic.html ###
<!DOCTYPE html>
<form method="post">
    {{ form.csrf_token }}
    {{ form.username.label }}<br>
    {{ form.username(placeholder='Your name') }}<br>
    {{ form.password.label }}<br>
    {{ form.password }}<br>
    {{ form.remember }}{{ form.remember.label }}<br>
    {{ form.submit }}<br>
</form>
</html>

渲染效果如下:
请添加图片描述

通过实例化字段类时传入的参数,我们可以对字段进行设置,字段类构造方法接收的常用参数如表:

参数说明
label字段标签<label>的值,也就是渲染后显示在输入字段前的文字
render_kw一个字典,用来设置对应的HTML<input>标签的属性,比如传入{‘placeholder’:‘Your Name’},渲染后的HTML代码会将<input>标签的placeholder属性设为Your Name
validators一个列表,包含一系列验证器,会在表单提交后被逐一调用验证表单数据
default字符串或可调用对象,用来为表单字段设置默认值

验证器从wtforms.validators模块导入,常用的验证器如表所示:

验证器说明
DataRequired(message=None)验证数据是否有效
Email(message=None)验证Email地址
EqualTo(fieldname,message=None)验证两个字段值是否相同
InputRequired(message=None)验证是否有数据
Length(min=-1,max=-1,message=None)验证输入值长度是否在给定范围内
NumberRange(min=None,max=None,message=None)验证输入数字是否在给定范围内
Optional(strip_whitespace=True)允许输入值为空,并跳过其他验证
Regexp(regex,flag=0,message=None)使用正则表达式验证输入值
URL(require_tld=True,message=None)验证URL
AnyOf(values,message=None,values_formatter=None)确保输入值在可选值列表中
NoneOf(values,message=None,values_formatter=None)确保输入值不在可选值列表中

以我们使用WTForms创建的LoginForm为例,实例化表单类,然后直接调用实例属性就可获得表单字段对应的HTML代码:

>>> form = LoginForm()
>>> form.username()
u'<input id='username' name="username" type='text' value="">'
>>> form.username.label()
u'<label for='username'>Username</label>'

WTForms输出的字段HTML代码只会包含id和name属性,属性值均为表单类中对应的字段属性名称。但是HTML<input>元素通常包括其他属性来对字段进行设置。要添加额外属性,通常有两种方法:

1、使用render_kw属性
username = StringField(‘Username’,render_kw={‘placeholder’: ‘Your Username’})

2、在调用字段时传入
form.username(style=‘width: 200px’, class_=‘bar’)

处理表单数据

在HTML中,当<form>标签声明的表单中类型为submit的提交字段被单击时,就会创建一个提交表单的HTTP请求,请求中包含表单各个字段的数据。为了提交表单数据,需要在app.route()装饰器中增加HTTP的post方法:

@app.route('/basic', methods=['GET', 'POST'])

表单数据的验证是Web表单最重要的主题之一,表单的验证通常分为客户端验证和服务器验证两种形式。

客户端验证是在客户端(比如Web浏览器)对用户输入值进行验证。客户端方式可以实时动态提示用户输入是否正确,只有用户输入正确后,才会将表单数据发送到服务器,降低了服务器负载。

服务器端验证是指用户把输入的数据提交到服务器端,在服务器端对数据进行验证。如果验证出错,就在返回的响应中加入错误信息。Flask程序中使用WTForms实现的就是服务器端验证。

WTForms验证表单字段的方式是在实例化表单类时传入表单数据,然后对表单实例调用validate()方法。这会逐个对字段调用字段实例化时定义的验证器,返回表示验证结果的布尔值。如果验证失败,就把错误消息存储到表单实例的errors属性对应的字典中。

因为现在的basic视图函数同时接收两种类型的请求:GET请求和POST请求。所以我们要根据请求方法的不同执行不同的代码。当请求方法是GET时,会跳过这个if语句,渲染basic.html模板;当请求的方法是POST时(说明用户提交了表单),则验证表单数据,这会逐个字段调用附加的验证器进行验证。

@app.route('/basic', methods=['GET', 'POST'])
def basic():
    form = LoginForm()
    if form.validate_on_submit(): #如果用户提交表单并且数据通过验证
    	... #处理post请求
    return render_template('basic.html', form=form) #处理get请求

注意:使用validate_on_submit函数需要from flask_wtf import FlaskForm来替换wtforms模块的Form。class LoginForm(FlaskForm)语句的Form也要替换成FlaskForm。

1、验证通过,获取数据

表单类的data属性是一个匹配所有字段与对应数据的字典,我们一般直接通过“form.字段属性名.data”的形式来获取对应字段的数据。例如:

@app.route('/basic', methods=['GET', 'POST'])
def basic():
    form = LoginForm()
    if form.validate_on_submit():
        username = form.username.data
        flash('Welcome home, %s!' % username)
        return redirect(url_for('index'))
    return render_template('basic.html', form=form)

form.username.data返回username字段的值。上面的代码,获取到username字段的数据后,发送一条flash消息,最后将程序重定向到index视图。加重定向语句的原因就是执行最后的render_template函数时,会默认发送上一个请求,那么就会再次提交表单,出现再次提交表单弹窗。加了重定向语句后,上一个请求就是GET请求(获取index页面)。

2、验证失败,返回错误

如果form.validate_on_submit()返回false,验证没通过。WTForms会把错误消息添加到表单类的errors属性中,我们一般会通过字段名来获取对应字段的错误消息列表,即“form.字段名.errors”。比如,form.name.errors返回name字段的错误消息列表。

像之前渲染flash消息一样,可以在模板里使用for循环迭代错误消息列表。

...
{{ form.username.label }}<br>
{{ form.username }}<br>
{% if form.username.errors %}
    {% for message in form.username.errors %}
    <small class="error">{{ message }}</small><br>
    {% endfor %}
{% endif %}
{{ form.password.label }}<br>
...

完整示例工程代码如下:

### 文件index.html ###
<!DOCTYPE html>
<main>
{% for message in get_flashed_messages() %}
<div class="alert">
    {{ message }}
</div>
{% endfor %}
</main>
<h1>This is index page</h1>
</html>

### 文件basic.html ###
<!DOCTYPE html>
<form method="post">
    {{ form.csrf_token }}
    {{ form.username.label }}<br>
    {{ form.username }}<br>
    {% if form.username.errors %}
        {% for message in form.username.errors %}
        <small class="error">{{ message }}</small><br>
        {% endfor %}
    {% endif %}
    {{ form.password.label }}<br>
    {{ form.password }}<br>
    {% if form.password.errors %}
        {% for message in form.password.errors %}
        <small class="error">{{ message }}</small><br>
        {% endfor %}
    {% endif %}
    {{ form.remember }}{{ form.remember.label }}<br>
    {{ form.submit }}<br>
</form>
</html>
### 文件forms.py ###
from flask_wtf import FlaskForm
from wtforms import  StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length,ValidationError
class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
    remember = BooleanField('Remember me')
    submit = SubmitField('Log in')

### 文件app.py ###
from flask import Flask, render_template,flash,redirect,url_for
import os
from forms import LoginForm

app = Flask(__name__)
app.secret_key = "secret string"
app.secret_key = os.getenv('SECRET_KEY', 'secret string')



@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template('index.html')

@app.route('/basic', methods=['GET', 'POST'])
def basic():
    form = LoginForm()
    if form.validate_on_submit():
        username = form.username.data
        flash('Welcome home, %s!' % username)
        return redirect(url_for('index'))
    return render_template('basic.html', form=form)

初识表单
请添加图片描述
输入数据
请添加图片描述
提交表单,返回结果
请添加图片描述

文件上传

在HTML中,渲染一个文件上传字段只需要将<input>标签的type属性设为file,即<input type=“file”>。这会在浏览器中渲染成一个文件上传字段,单击文件选择按钮会打开文件选择窗口,选择对应的文件后,被选择的文件名会显示在文件选择按钮旁边。

在服务器端,可以和普通数据一样获取上传文件数据并保存。不过需要考虑安全问题,注意验证文件类型、验证文件大小、过滤文件名。

创建上传表单的代码如下:

from flask_wtf.file import FileField, FileRequired, FileAllowed
# upload form
class UploadForm(FlaskForm):
    photo = FileField('Upload Image', validators=[FileRequired(), FileAllowed(['jpg', 'jpeg', 'png', 'gif'])])
    submit = SubmitField()

代码中创建了一个用来上传图片的photo字段,和其他字段类似,我们也需要对文件上传字段进行验证。flask_wtf.file提供了两个文件相关的验证器。FileRequired(message=None)验证是否包含文件对象。FileAllowed(upload_set, message=None)验证文件类型,upload_set参数代表允许的文件后缀名列表。

除了验证文件类型,我们还需要对文件大小进行验证,过大的文件会拖垮服务器。通过设置Flask内置的配置变量MAX_CONTENT_LENGTH,可以限制请求报文的最大长度,单位是字节。比如下面限制最大长度为3M:

app.config['MAX_CONTENT_LENGTH'] = 3 * 1024 * 1024

当上传文件大小超出限制,会返回413错误响应。

在新创建的upload视图里,我们实例化表单类UploadForm,然后传入模板:

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = UploadForm()
    if form.validate_on_submit():
        # 文件处理 #
        f = form.photo.data
        filename = random_filename(f.filename)
        f.save(os.path.join(app.config['UPLOAD_PATH'], filename))
        flash('Upload success.')
        session['filenames'] = [filename]
        return redirect(url_for('show_images'))
    # 文件处理结束 #    
    return render_template('upload.html', form=form)

在模板(upload.html)中渲染表单

<form method="post" enctype="multipart/form-data">
    {{ form.csrf_token }}
    {{ form_field(form.photo) }}
    {{ form.submit }}
</form>

需要注意的是,当表单中包含文件上传字段时(即type属性为file的input标签),需要将表单的enctype属性设为“multipart/form-data”,这会告诉浏览器将上传数据发送到服务器,否则仅会把文件名作为表单数据提交。

当表单验证通过后,我们通过form.photo.data获取存储上传文件的FileStorage对象。由于文件名中可能包含恶意代码,因此使用统一处理的方式(random_filename随机文件名对文件重命名)对所有上传的文件重新命名。

处理完文件名后,再将文件保存到文件系统中。我们在form目录下创建了一个uploads文件夹,用于保存上传后的文件。指向这个文件夹的绝对路径存储在自定义配置变量UPLOAD_PATH中:

app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')

app.root_path是程序实例所在脚本(app.py)的绝对路径。

对FileStorage对象调用save()方法即可保存,传入包含目标文件夹绝对路径和文件名作为参数。

在保存文件后,我们使用flash发送一个提示,将文件名保存到session中,最后重定向到show_images视图,show_images视图返回的uploaded.html模板将从session获取文件名,渲染出上传后的图。show_images视图函数如下:

@app.route('/uploaded-images')
def show_images():
    return render_template('uploaded.html')

为了让上传后的文件能通过URL获取,我们还需要创建一个视图函数来返回上传后的文件,如下所示:

@app.route('/uploads/<path:filename>')
def get_file(filename):
    return send_from_directory(app.config['UPLOAD_PATH'], filename)

这个视图函数通过传入的文件路径返回对应的静态文件,send_from_directory函数用来获取文件,传入文件的路径和文件名作为参数。

在uploaded.html模板中,我们将传入的文件名作为URL变量,通过上面的get_file视图获取文件URL,作为<img>标签的src属性值,如下所示:

<img src="{{ url_for('get_file', filename=filename) }}">

单个表单多个提交按钮

在某些情况下,我们可能需要为一个表单添加多个提交按钮。当用户提交表单时,我们需要在视图函数中根据按下的按钮来做出不同的处理。例如:

# multiple submit button
class NewPostForm(FlaskForm):
    title = StringField('Title', validators=[DataRequired(), Length(1, 50)])
    body = TextAreaField('Body', validators=[DataRequired()])
    save = SubmitField('Save') # 保存按钮
    publish = SubmitField('Publish') # 发布按钮

当我们对表单类实例或特定的字段属性调用data属性时,被点击的提交字段的值是True,未被点击的值则是False。基于这个机制,我们可以通过提交按钮字段的值来判断当前被点击的按钮。

@app.route('/two-submits', methods=['GET', 'POST'])
def two_submits():
    form = NewPostForm()
    if form.validate_on_submit():
        if form.save.data:
            # save it...
            flash('You click the "Save" button.')
        elif form.publish.data:
            # publish it...
            flash('You click the "Publish" button.')
        return redirect(url_for('index'))
    return render_template('2submit.html', form=form)

单个页面多个表单

有时我们还需要在单个页面上创建多个表单。当同一个页面有多个表单时,我们要解决的就是在视图函数中判断当前被提交的是哪个表单。

解决问题的第一步就是为两个表单的提交字段设置不同的名称。

class SigninForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(1, 20)])
    password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
    submit1 = SubmitField('Sign in')


class RegisterForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(1, 20)])
    email = StringField('Email', validators=[DataRequired(), Email(), Length(1, 254)])
    password = PasswordField('Password', validators=[DataRequired(), Length(8, 128)])
    submit2 = SubmitField('Register')

在视图函数中,我们分别实例化这两个表单,根据提交字段的值来区分被提交的表单。

@app.route('/multi-form', methods=['GET', 'POST'])
def multi_form():
    signin_form = SigninForm()
    register_form = RegisterForm()

    if signin_form.submit1.data and signin_form.validate():
        username = signin_form.username.data
        flash('%s, you just submit the Signin Form.' % username)
        return redirect(url_for('index'))

    if register_form.submit2.data and register_form.validate():
        username = register_form.username.data
        flash('%s, you just submit the Register Form.' % username)
        return redirect(url_for('index'))

    return render_template('2form.html', signin_form=signin_form, register_form=register_form)

在视图函数中,我们为两个表单添加了各自的if判断,在这两个if语句的内部,我们分别执行各自的代码逻辑。

参考资料:《Flask Web开发实战入门、进阶与原理解析》李辉 著

  • 17
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 Flask 框架编写论坛系统需要以下步骤: 1. 安装 Flask 框架:可以使用 pip 工具进行安装,命令为 `pip install Flask`。 2. 设计数据库模型:根据论坛系统的需求,设计数据库模型,包括用户、帖子、评论等数据表。 3. 编写视图函数:Flask 框架中的视图函数用于处理不同的 URL 请求。可以根据论坛系统的需求编写不同的视图函数,例如注册、登录、发帖、评论等。 4. 编写模板:使用 Jinja2 模板引擎编写 HTML 模板,用于渲染视图函数返回的数据。 5. 配置路由:使用 Flask 框架中的路由管理器,将 URL 请求映射到对应的视图函数上。 6. 运行程序:使用 Flask 框架自带的开发服务器,运行论坛系统。 Flask 框架的代码结构可以按照以下方式组织: ``` forum/ __init__.py views.py models.py templates/ base.html index.html login.html register.html post.html ... static/ css/ js/ images/ config.py ``` 其中,`__init__.py` 文件是 Flask 应用的入口文件,`views.py` 文件包含视图函数的定义,`models.py` 文件包含数据库模型的定义,`templates` 目录包含 HTML 模板,`static` 目录包含静态资源文件,例如 CSS 样式表、JavaScript 脚本和图片等。`config.py` 文件包含 Flask 应用的配置信息。 在 `__init__.py` 文件中,可以编写以下代码: ```python from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_pyfile('config.py') db = SQLAlchemy(app) from forum import views, models ``` 其中,`Flask` 类用于创建 Flask 应用对象,`SQLAlchemy` 类用于创建数据库连接对象。`config.from_pyfile()` 方法用于从配置文件中加载配置信息。最后,通过 `from forum import views, models` 导入视图函数和数据库模型。 在 `views.py` 文件中,可以编写以下代码: ```python from flask import render_template, request, redirect, url_for, flash from flask_login import login_user, logout_user, current_user, login_required from forum import app, db from forum.models import User, Post, Comment from forum.forms import LoginForm, RegisterForm, PostForm, CommentForm @app.route('/') def index(): posts = Post.query.all() return render_template('index.html', posts=posts) @app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is not None and user.check_password(form.password.data): login_user(user) return redirect(request.args.get('next') or url_for('index')) flash('Invalid username or password') return render_template('login.html', form=form) @app.route('/register', methods=['GET', 'POST']) def register(): form = RegisterForm() if form.validate_on_submit(): user = User(username=form.username.data, email=form.email.data) user.set_password(form.password.data) db.session.add(user) db.session.commit() flash('Congratulations, you are now a registered user!') return redirect(url_for('login')) return render_template('register.html', form=form) @app.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('index')) @app.route('/post', methods=['GET', 'POST']) @login_required def post(): form = PostForm() if form.validate_on_submit(): post = Post(title=form.title.data, body=form.body.data, author=current_user) db.session.add(post) db.session.commit() flash('Your post is now live!') return redirect(url_for('index')) return render_template('post.html', form=form) @app.route('/post/<int:id>', methods=['GET', 'POST']) @login_required def comment(id): post = Post.query.get_or_404(id) form = CommentForm() if form.validate_on_submit(): comment = Comment(body=form.body.data, post=post, author=current_user) db.session.add(comment) db.session.commit() flash('Your comment has been published.') return redirect(url_for('comment', id=post.id)) return render_template('comment.html', post=post, form=form) ``` 其中,`@app.route()` 装饰器用于指定 URL 和对应的视图函数。`render_template()` 函数用于渲染 HTML 模板。`request` 对象用于获取请求信息,`redirect()` 函数用于重定向到其他 URL,`url_for()` 函数用于生成 URL。`flash()` 函数用于向用户显示消息。`LoginForm`、`RegisterForm`、`PostForm` 和 `CommentForm` 分别是用户登录、注册、发帖和评论的表单。 在 `models.py` 文件中,可以编写以下代码: ```python from werkzeug.security import generate_password_hash, check_password_hash from flask_login import UserMixin from forum import db, login_manager class User(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) posts = db.relationship('Post', backref='author', lazy='dynamic') comments = db.relationship('Comment', backref='author', lazy='dynamic') def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def __repr__(self): return '<User {}>'.format(self.username) class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(140)) body = db.Column(db.Text) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) comments = db.relationship('Comment', backref='post', lazy='dynamic') def __repr__(self): return '<Post {}>'.format(self.body) class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) post_id = db.Column(db.Integer, db.ForeignKey('post.id')) def __repr__(self): return '<Comment {}>'.format(self.body) @login_manager.user_loader def load_user(id): return User.query.get(int(id)) ``` 其中,`User`、`Post` 和 `Comment` 分别是用户、帖子和评论的数据模型,`db.Column()` 和 `db.relationship()` 分别用于定义表字段和表关系。`set_password()` 和 `check_password()` 方法用于设置和验证密码。`login_manager.user_loader` 装饰器用于加载用户,`load_user()` 函数根据用户 ID 加载用户。 在 `templates` 目录中,可以编写 HTML 模板,例如: `base.html` ```html <!doctype html> <html> <head> <title>{% block title %}{% endblock %}</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"> </head> <body> <header> <nav> <ul> {% if current_user.is_authenticated %} <li><a href="{{ url_for('index') }}">Home</a></li> <li><a href="{{ url_for('logout') }}">Logout</a></li> {% else %} <li><a href="{{ url_for('index') }}">Home</a></li> <li><a href="{{ url_for('login') }}">Login</a></li> <li><a href="{{ url_for('register') }}">Register</a></li> {% endif %} </ul> </nav> </header> <main> {% with messages = get_flashed_messages() %} {% if messages %} <ul class="flashes"> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} {% block content %}{% endblock %} </main> </body> </html> ``` `index.html` ```html {% extends 'base.html' %} {% block content %} {% for post in posts %} <article class="post"> <header> <h1 class="post-title"><a href="{{ url_for('comment', id=post.id) }}">{{ post.title }}</a></h1> <div class="post-meta"> <span class="post-author">{{ post.author.username }}</span> <span class="post-date">{{ post.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</span> </div> </header> <div class="post-body"> {{ post.body }} </div> </article> {% endfor %} {% endblock %} ``` `login.html` ```html {% extends 'base.html' %} {% block title %}Sign In{% endblock %} {% block content %} <h1>Sign In</h1> <form method="post"> {{ form.hidden_tag() }} <p> {{ form.username.label }}<br> {{ form.username(size=32) }} </p> <p> {{ form.password.label }}<br> {{ form.password(size=32) }} </p> <p>{{ form.submit() }}</p> </form> {% endblock %} ``` `register.html` ```html {% extends 'base.html' %} {% block title %}Register{% endblock %} {% block content %} <h1>Register</h1> <form method="post"> {{ form.hidden_tag() }} <p> {{ form.username.label }}<br> {{ form.username(size=32) }} </p> <p> {{ form.email.label }}<br> {{ form.email(size=32) }} </p> <p> {{ form.password.label }}<br> {{ form.password(size=32) }} </p> <p>{{ form.submit() }}</p> </form> {% endblock %} ``` `post.html` ```html {% extends 'base.html' %} {% block title %}New Post{% endblock %} {% block content %} <h1>New Post</h1> <form method="post"> {{ form.hidden_tag() }} <p> {{ form.title.label }}<br> {{ form.title(size=80) }} </p> <p> {{ form.body.label }}<br> {{ form.body(rows=10, cols=80) }} </p> <p>{{ form.submit() }}</p> </form> {% endblock %} ``` `comment.html` ```html {% extends 'base.html' %} {% block title %}{{ post.title }}{% endblock %} {% block content %} <article class="post"> <header> <h1 class="post-title">{{ post.title }}</h1> <div class="post-meta"> <span class="post-author">{{ post.author.username }}</span> <span class="post-date">{{ post.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</span> </div> </header> <div class="post-body"> {{ post.body }} </div> </article> {% for comment in post.comments %} <article class="comment"> <header> <div class="comment-meta"> <span class="comment-author">{{ comment.author.username }}</span> <span class="comment-date">{{ comment.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</span> </div> </header> <div class="comment-body"> {{ comment.body }} </div> </article> {% endfor %} <h2>Add a Comment</h2> <form method="post"> {{ form.hidden_tag() }} <p>{{ form.body(rows=10, cols=80) }}</p> <p>{{ form.submit() }}</p> </form> {% endblock %} ``` 以上就是使用 Flask 框架编写论坛系统的基本步骤和代码示例。可以根据实际需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值