第3章 Django视图

内容来源于《Web接口开发与自动化测试——基于Python语言》虫师编著,如有涉及版权问题,归虫师本人所有。请大家支持虫师的著作:http://www.broadview.com.cn/book/4811
源码下载:https://github.com/defnngj/guest

目标:用Django开发一个发布会签到系统。

3.1 来写个登录功能

打开.../sign/templates/index.html文件,开发一个登录表单。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django Page</title>
</head>
<body>
    <h1>发布会管理</h1>
    <form>
        <input name="username" type="text" placeholder="username" ><br>
        <input name="password" type="password" placeholder="password"><br>
        <button id="btn" type="submit">登录</button>
    </form>

</body>
</html>

启动Django服务,访问:http://127.0.0.1:8000/index/,如下图所示。

登录功能.png

3.1.1 GET和POST请求

当客户端通过HTTP协议向服务器提交请求时,最常用的方法就是GET和POST。

  • GTE:从指定的资源请求数据。
  • POST:向指定的资源提交要被处理的数据。

1. GET请求

先来看看GET方法是如何传参的,给form表单添加属性 method="get"。

......
    <form method="get">
        <input name="username" type="text" placeholder="username" ><br>
        <input name="password" type="password" placeholder="password"><br>
        <button id="btn" type="submit">登录</button>
    </form>
......

保存在index.html文件,刷新登录页面,输入账号密码admin/admin123,单击“登录”按钮。
查看浏览器URL地址栏:
http://127.0.0.1:8000/index/?username=admin&password=admin123
GET方法会将用户提交的数据添加到URL地址中,路径后面跟问号“?”。后面跟的是用户名admin和密码admin123,用“&”分隔开。

2. POST请求

再次将form表单中的属性改为method="post",刷新页面,输入账号密码,发现报错了。


CSRF verification failed. Request aborted.png

这是跨站请求伪造(CSRF)漏洞。Django针对CSRF的保护措施是在生成的每个表单中放置一个自动生成的令牌,通过令牌判断POST请求是否来自同一个网站。
接下来网form表单中添加CSRF令牌,添加{% csrf_token %}

.......
    <form method="post">
        <input name="username" type="text" placeholder="username" ><br>
        <input name="password" type="password" placeholder="password"><br>
        <button id="btn" type="submit">登录</button>
        {% csrf_token %}
    </form>
......

刷新后发现登录页面的报错消失了。
借助工具查看POST请求,发现请求参数中多了一个csrfmiddlewaretoken参数。


查看POST请求.png

如果想忽略该检查,可以在settings.py文件中注释掉csrf。

......
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    #'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
......

3.1.2 处理登录请求

通过form表单的action属性来指定提交的路径。打开index.html,添加内容:

......
    <form method="post" action="/login_action/">
......

当我们写好账号密码时,点击登录按钮,由http://127.0.0.1:8000/login_action/路径来提交登录请求。所以,在usrls.py中添加login_action/的路由。

......
urlpatterns = [
......
    re_path(r'login_action/$', views.login_action),
]

登录请求由views.py视图文件中的login_action函数来处理,打开sign/views.py,创建login_action视图函数。

......
def login_action(request):
    if request.method == 'POST':
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        if username == 'admin' and password == 'admin123':
            return HttpResponse('login success!')
        else:
            return render(request, 'index.html', {'error': 'username or password error!'})

通过login_action函数来处理登录请求。
但是发现登录页面病没有显示错误提示,打开index.html修改:

......
<h1>发布会管理</h1>
    <form method="post" action="/login_action/">
        <input name="username" type="text" placeholder="username" ><br>
        <input name="password" type="password" placeholder="password"><br>
        {{error}}<br>
        <button id="btn" type="submit">登录</button>
        {% csrf_token %}
    </form>
......

可以看出错误提示放在哪个地方了。


登录失败.png

登录成功.png

3.1.3 登录成功页

接下来需要把“login success”字符串换成HTML页面。所以,先创建.../templates/event_manage.html页面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Event Manage Page</title>
</head>
<body>
    <h1>Login Success!</h1>
</body>
</html>

打开views.py文件,修改内容如下:

......
from django.http import HttpResponse, HttpResponseRedirect
......
# 登录动作
def login_action(request):
    if request.method == 'POST':
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        if username == 'admin' and password == 'admin123':
            return HttpResponseRedirect('/event_manage/')
        else:
            return render(request, 'index.html', {'error': 'username or password error!'})

# 发布会管理
def event_manage(request):
    return render(request, "event_manage.html")

此处用了一个新的类HttpResponseRedirect,要先导入,它可以对路径进行重定向,从而将登录成功之后的请求指向/event_manage/目录,即:http://127.0.0.1:8000/event_manage/
创建event_manage函数,用于返回发布会管理页面event_manage.html。
最后不要忘记在urls.py中添加event_manage/的路由。

......
urlpatterns = [
......
    re_path(r'^event_manage/$', views.event_manage),
]

登录成功了!!!


登录成功跳转到event_manage.html.png

3.2 Cookie和Session

  • Cookie机制:Cookie分发通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。
  • Session机制:session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

3.2.1 Cookie的使用

修改views.py文件。

......
# 登录动作
def login_action(request):
    if request.method == 'POST':
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        if username == 'admin' and password == 'admin123':
            response = HttpResponseRedirect('/event_manage/')
            response.set_cookie('user', username, 3600)  # 添加浏览器cookie
            return response
        else:
            return render(request, 'index.html', {'error': 'username or password error!'})

# 发布会管理
def event_manage(request):
    username = request.COOKIES.get('user', '')  # 读取浏览器cookie
    return render(request, "event_manage.html", {"user": username})

当用户登录成功后,在跳转到event_manage视图函数的过程中,通过set_cookie()方法向浏览器中添加Cookie信息。
set_cookie传了三个参数:第一个参数"user"用于表示写入浏览器的Cookie名,第二个参数是用户输入的用户名,第三个参数3600表示设置Cookie信息在浏览器中的保持时间,单位是秒。
在event_manager视图函数中,通过request.COOKIES来读取Cookie名为“user”的值,并且通过render将它和event_manage.html页面一起返回。
接下来修改event_manage.html。

......
<body>
    <div style="float: right;">
        <a>嘿!{{user}} 欢迎</a><hr/>
    </div>
    <h1>Login Success!</h1>
</body>
......

重新登录,界面如下图:


登录页面显示Cookie里的用户名.png

Cookie信息.png

3.2.2 Session的使用

在Django中使用Session和Cookie类似,但是Session更安全。
修改views.py文件。

......
# 登录动作
def login_action(request):
    if request.method == 'POST':
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        if username == 'admin' and password == 'admin123':
            response = HttpResponseRedirect('/event_manage/')
            # response.set_cookie('user', username, 3600)  # 添加浏览器cookie
            request.session['user'] = username  # 将Session信息记录到浏览器
            return response
        else:
            return render(request, 'index.html', {'error': 'username or password error!'})

# 发布会管理
def event_manage(request):
    # username = request.COOKIES.get('user', '')  # 读取浏览器cookie
    username = request.session.get('user', '')  # 读取浏览器session
    return render(request, "event_manage.html", {"user": username})

还需要创建django_session表。

\guest > python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK

因为Django已经默认设置SQLite3,可以通过查看settings.py文件中查看SQLite3数据库的配置。

# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

此时在guest项目根目录下已经生成了一个db.sqlite3的数据库文件。来看看验证Session功能是否生效,重新登录。


查看Sessionid.png

3.3 Django认证系统

本节会使用Django的认证系统来实现真正的用户信息验证。之前的验证都是写死的,只是简单判断用户名和密码是不是admin/admin123。

3.3.1 登录Admin后台

先创建登录Admin后台的管理员账号。

(venv) liujindeMacBook-Pro:guest liujin$ python3 manage.py createsuperuser
Username (leave blank to use 'liujin'): admin
Email address: admin@email.com
Password: 
Password (again): 
Superuser created successfully.

创建的超级管理员账号密码是:admin/admin123456。
Admin的后台地址是:http://127.0.0.1:8000/admin/
通过创建的超管账号登录,并用新创建的用户再次登录后台(要给权限才能登录)。

Admin管理后台登录.png

Admin管理后台.png

3.3.2 引用Django认证登录

Django已经给我们封装好了用户认证和登录的相关办法,只需要拿来用即可。并且,同样适用auth_user表中的数据进行验证。
修改views.py文件。

# 登录动作
def login_action(request):
    if request.method == 'POST':
        username = request.POST.get('username', '')
        password = request.POST.get('password', '')
        user = auth.authenticate(username=username, password=password)
        if user is not None:
            auth.login(request, user)  # 登录
            request.session['user'] = username  # 将Session信息记录到浏览器
            response = HttpResponseRedirect('/event_manage/')
            return response
        else:
            return render(request, 'index.html', {'error': 'username or password error!'})

使用authenticate()函数认证给出的用户名和密码,它接受两个参数:username和password,并且会在用户名密码正确的情况下返回一个user对象,否则authenticate()返回None。
通过if语句判断authenticate()返回对象,如果不为None,则说明用户认证通过,调用login()函数进行登录。login()函数接收HttpRequest对象和一个user对象。
使用之前创建的账号就能登录了。在这里:http://http://127.0.0.1:8000/index/

3.3.3 关上窗户

现在访问http://127.0.0.1:8000/event_manage/是不是发现直接登录成功了,那为什么还需要账号登录呢?因此需要把有些窗户关上,让用户只能登录才能访问。
修改views.py。

......
from django.contrib.auth.decorators import login_required
.......
# 发布会管理
@login_required()
def event_manage(request):
    # username = request.COOKIES.get('user', '')  # 读取浏览器cookie
    username = request.session.get('user', '')  # 读取浏览器session
    return render(request, "event_manage.html", {"user": username})
......

只需要在这个函数前加上@login_required()即可。
此时清浏览器缓存,再次访问http://127.0.0.1:8000/event_manage/发现报Page not found 404。

Page not found.png

仔细看这个URL,发现默认跳转到了包含“/accounts/login/”,为什么不让它直接到登录页面呢?
接下来修改urls.py文件,新增路径配置。

urlpatterns = [
......
    re_path(r'^$', views.index),
    re_path(r'^account/login/$', views.index)
]

此时,你访问:
http://127.0.0.1:8000/
http://127.0.0.1:8000/index/
http://127.0.0.1:8000/event_manage/
都会跳转到登录界面。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容