编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Flask的视图

wxchong 2024-06-27 01:38:12 开源技术 13 ℃ 0 评论

写在文前

养成做演练的习惯。因为我之前从来不在大众面前做演讲,这次我要主持公司的年度大会,我开始写词,从开场、串场到结束,不仅各个环节需要做好衔接,还要把整个演讲词给背诵下来。在这个过程中让我明白了凡事要做计划和演练,否则你就不会达到你所想的目标。

视图函数与URL的绑定(俗称映射)

  • 添加URL与视图函数的映射。你不仅可以以使用app.route(rule, endpoint=None),还可以使用app.add_url_route(url_rule, endpoint=None, view_func=None),其实app.route()装饰器底层的实现逻辑还是app.add_url_route()。
    • rule指的是你输入浏览器地址栏的URL,是字符串的格式。
    • endpoint指的是视图函数的别名,如果没有指定,url_for()则默认使用视图函数的名字,否则url_for()必须使用这个endpoint的名字,url_for()中的参数字符串的格式。
    • view_func指的是视图函数名字,非字符串,而是不带括号的视图函数名字。
# app.py
from flask import Flask, render_template, url_for


app = Flask(__name__)
# 配置自动加载模板文件
app.config['TEMPLATES_AUTO_RELOAD'] = True


@app.route('/')
def hello_world():
    # 输出的是/home/
    print(url_for("back"))
    return "hello world"


@app.route("/index/")
def get_index():
    return render_template('index.html')


def get_home():
    return render_template("home.html")


app.add_url_rule('/home/', endpoint="back", view_func=get_home)


if __name__ == '__main__':
    app.run()

标准类视图

  • 我们之前接触的视图都是函数形式的,其实视图也可以基于类来进行实现,类视图可以支持继承,但是类视图函数需要通过app.add_url_rule()注册才能使用。
  • 标准类视图,必须继承自flask.views.View,必须实现dispatch_request方法,每当前端的请求过来后,这个方法就会自动运行给出返回值(与之前的视图保持相同),这个返回值也是个Response对象。
  • app.add_url_rule()函数中如果指定了endpoint参数,那么在使用url_for()函数反转视图的时候就必须使用endpoint参数指定的值;如果endpoint没有指定值,那么就可以使用as_view("视图名字")指定的视图名字进行反转。
# app.py
from flask import Flask, render_template, views, url_for, jsonify

app = Flask(__name__)
# 配置自动加载模板文件
app.config['TEMPLATES_AUTO_RELOAD'] = True


class JsonView(views.View):
    def get_data(self):
        return "data"

    # 这个方法必须要实现,dispatch是指处理,这里主要是处理请求
    def dispatch_request(self):
        return jsonify(self.get_data())


class Index(JsonView):
    def get_data(self):
        return {'Navigation1': 'home', 'Navigation2': 'book'}


# 类表示实现多个处理方法的视图,
# 类调用as_view()方法把其转换为视图函数
# 传入自定义的端点值(用来生成URL),最后将它赋给view_func参数。
app.add_url_rule('/index/',
                 endpoint="get_index",
                 view_func=Index.as_view("back")
                 )


class AdsView(views.View):
    def __init__(self):
        # 如果子类继承父类不做初始化,那么会自动继承父类属性。
        # 如果子类继承父类做了初始化,且不调用super初始化父类构造函数,那么子类不会自动继承父类的属性。
        # 如果子类继承父类做了初始化,且调用了super初始化了父类的构造函数,那么子类也会继承父类的属性。
        super(AdsView, self).__init__()
        self.context = {
            "variable1": "value1",
            "variable2": "value2"
        }


class ChargeView(AdsView):
    def dispatch_request(self):
        return render_template("charge.html", **self.context)


class LoginView(AdsView):
    def dispatch_request(self):
        self.context.update({
            "variable3": "value3",
        })
        return render_template("login.html",  **self.context)


app.add_url_rule('/charge/', view_func=ChargeView.as_view("get_charge"))
app.add_url_rule('/login/', view_func=LoginView.as_view("get_login"))


@app.route('/')
def hello_world():
    # 输出:/index/
    print(url_for("get_index"))
    # 输出:/charge/,这个是不指定endpoint关键字参数的情况下的输出
    print(url_for('get_charge'))
    return "hello world"


if __name__ == '__main__':
    app.run()

调度方法视图

  • 基于请求方法的类视图,是根据请求的方法来执行不同的方法,如果前端发送的是get方法,那么就是执行这个类的get方法,其它类推。
  • 基于请求方法的类视图,可以让代码更简洁,所有和get请求相关的代码都放在get方法中,所有和post请求相关的代码都放在类的post方法中,这样就不需要通过请求的方法来进行判断。
# app.py
from flask import Flask, views, render_template, request

app = Flask(__name__)
# 配置自动加载模板文件
app.config['TEMPLATES_AUTO_RELOAD'] = True


@app.route('/')
def hello_world():
    return "hello world"


class LoginView(views.MethodView):
    # __error
    def __error(self, error=None):
        # 必须以error=error传递error这个参数,因为前端模板有{{ error }}这个变量
        return render_template('login.html', error=error)

    def get(self):
        return self.__error()

    def post(self):
        # 必须对应前端的form表单的提交方式为:method="post"
        username = request.form.get('username')
        password = request.form.get('password')
        if username == "link" and password == "123":
            return "登录成功"
        else:
            return self.__error(error="用户名或密码错误!")


# /login/必须加斜杠
# 否则会出现错误:ValueError: urls must start with a leading slash
app.add_url_rule('/login/', view_func=LoginView.as_view("login"))


if __name__ == '__main__':
    app.run()
{#login.html#}
{% extends "common/base.html" %}

{% block title %}
    Chain
{% endblock %}

{% block body_block %}
    <form action="" method="post">
        <table>
            <tbody>
                <tr>
                    <td>用户名:</td>
                    <td><input type="text" name="username"></td>
                </tr>
                <tr>
                    <td>密码:</td>
                    <td><input type="password" name="password"></td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="登录"></td>
                </tr>
            </tbody>
        </table>
    {% if error %}
        <p style="color: red">{{ error }}</p>
    {% endif %}
    </form>
{% endblock %}

类视用装饰器

  • 对视图函数使用装饰器,那么自己定义的装饰器必须在app.route装饰器下面,否则自己定义的装饰器就不会起作用。
  • 在类视图中的装饰器,需要重写类视图的decorators类属性,给这个类属性赋列表或者元组,里面就是自己定义的装饰器。
# app.py
from flask import Flask, request, views
from functools import wraps

app = Flask(__name__)
# 配置自动加载模板文件
app.config['TEMPLATES_AUTO_RELOAD'] = True


# 装饰器的作用:在不改变原有功能代码的基础上,添加额外的功能,如用户验证等
# @wraps(view_func)的作用:不改变使用装饰器原有函数的结构(如__name__, __doc__)
def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        username = request.args.get("username")
        if username and username == 'chain':
            # *的变量名会存放所有未命名的变量参数,并且会把位置参数转换成元组的形式
            # 关键字参数**kwargs:把N个关键字参数,转换成了字典。
            return func(*args, **kwargs)
        else:
            return "请先登录"
    return wrapper


@app.route('/')
def hello_world():
    return "hello world"


@app.route('/setting/')
# login_required不用加括号,其次是要放在@app.route('/setting/')的下面
# 正常前端访问该url,界面显示请先登录,如果输入
# http://127.0.0.1:5000/setting/?username=chain,则会正常的显示设置页面
@login_required
def setting():
    return "设置页面"


class Information(views.View):
    decorators = [login_required]

    def dispatch_request(self):
        return "个人中心"


app.add_url_rule('/info/', view_func=Information.as_view("info"))


if __name__ == '__main__':
    app.run()

蓝图(蓝本)

  • 如果项目庞大繁杂,我们就需要了解项目的组织技巧,蓝图的目的就是让Flask项目更加模块化,结构更加清晰,蓝图可以将相同的模块视图函数放在同一个蓝图下,同一个文件中,更加方便开发者管理。
  • 蓝图的基本语法:首先在项目根目录下建立包文件blueprints;其次建立单个蓝图py文件;在蓝图文件中from flask import Blueprint,建立user_bp = Blueprint('admin', __name__, url_prefix="/info");最后要在app.py文件中注册蓝图。
  • 如果想要在某个蓝图下的url前都要一个相同的前缀,那么在定义蓝图的时候,指定关键字参数url_prefix="/info",如果/info后面不增加/,那么在定义视图函数的时候,就不需要再增加/了。
  • 蓝图中模板文件寻找规则:如果项目中的templates文件夹中有相应的模板文件可直接使用;如果templates文件夹没有相应的模板文件,那么就会到定义蓝图的指定的template_folder路径中寻找,并且在蓝图中指定的路径可以为相对路径,相对指的是当前这个蓝图文件所在的目录。
  • 蓝图中静态文件寻找规则:在模板文件中,加载静态的文件,如果使用url_for(),那么就只会在app指定的静态文件加目录下查找静态文件;如果指定蓝图的名字,比如"admin.static",那么Flask就会到这个蓝图指定的static_folder下查找静态文件。
  • url_for()反转蓝图中的视图函数为url:项目使用了蓝图,在模板或视图函数中使用url_for()函数的时候,必须要加上蓝图的名字.视图函数名字,否则就找不到这个endpoint。
项目结构
demo4
│          
├─static
│      admin_css.css
│      
├─templates
│      index.html
│      
├─user
│  │  admin.py
│  │  __init__.py
│  │  
│  ├─chain
│  │  │  home.html
│  │  │  
│  │  └─blue_static
│  │          admin_css.css
# chain/demo4/app.py
from flask import Flask, url_for
from user.admin import user_bp

app = Flask(__name__)
app.register_blueprint(user_bp)


@app.route('/')
def hello_world():
    print(url_for('users.user_info'))
    return 'Hello World!'


if __name__ == '__main__':
    app.run()
# demo4/user/admin.py
from flask import Blueprint, render_template

# name是为蓝本的名字,这个参数必须存在
# import_name是蓝本所在的包或模块
# 如果blue_static文件夹在chain文件下,则static_folder="chain/blue_static"
# 如果blue_static文件夹在user文件下,则static_folder="blue_static"
# users也是模板文件中的url_for('users.static', filename='admin_css.css')
user_bp = Blueprint('users', __name__,
                    url_prefix="/info",
                    template_folder="chain",
                    static_folder="blue_static",
                    )


@user_bp.route("/users_list/")
def user_info():
    # 返回的是user/chain/home.html文件
    return render_template('home.html')


@user_bp.route('/books/')
def book_list():
    return "图书列表"
<!-- demo4\user\chain\home.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- users.static中的users对应的是蓝本文件中name参数,而不是文件的名称 -->
    <!-- filename='admin_css.css'中的admin_css.css文件的名字必须和blue_static的名字相同 -->
    <link rel="stylesheet" href="{{ url_for('users.static', filename='admin_css.css') }}">
</head>
<body>
    <p>蓝本-网站的主页</p>
    <a href="{{ url_for("users.book_list") }}">url_for反转蓝图</a>
</body>
</html>

子域名的实现方式

  • 如果你使用蓝图,那么在创建蓝图对象的时候,需要传递一个subdomain参数,来指定这个子域名的前缀crm_bp = Blueprint('crm', __name__, subdomain="crm");接着你需要在app.py这个主文件中配置app.config['SERVER_NAME'] = "chain.com:5000";最后要在Windows下找到C:\Windows\System32\drivers\etc文件夹中的hosts文件,添加域名与本机的映射,例如127.0.0.1 chain.com和127.0.0.1 crm.chain.com。
  • 需要注意的是IP地址和localhost是不能有子域名的。
  • Windows生成目录树的cmd命令:首先进入到项目的盘d:;其次是通过cd命令进入到项目的根文件;最后使用tree /f >list.txt生成目录树。
项目的结构
│  app.py
│  list.txt
│          
├─blue_prints
│  │  crm.py
│  │  __init__.py
│  │  
│  └─__pycache__
│          crm.cpython-36.pyc
│          __init__.cpython-36.pyc
│          
├─static
├─templates
│      index.html
│      
└─__pycache__
        app.cpython-36.pyc
        
# chain\demo5\app.py
from flask import Flask
from blue_prints.crm import crm_bp

app = Flask(__name__)
# 必须要加上端口号
app.config['SERVER_NAME'] = "chain.com:5000"
app.register_blueprint(crm_bp)


@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.run()
# chain\demo5\blue_prints\crm.py
from flask import Blueprint, render_template

crm_bp = Blueprint('crm', __name__, subdomain="crm")


# 这个/是必须的,不能有其它的字符了
@crm_bp.route("/")
def crm():
    return render_template('index.html')

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表