上一篇:
Django 04 :靓号管理【 靓号的增删改 + 搜索 + 分页 + 时间插件 + ModelForm与BootStrap】
1、管理员操作
创建管理员,来管理这些部门用户等
1.1、创建表
class Admin(models.Model):
""" 管理员 """
username = models.CharField(verbose_name="用户名", max_length=32)
password = models.CharField(verbose_name="密码", max_length=64)
def __str__(self):
return self.username
python manage.py makemigrations
python manage.py migrate
造点数据
mysql -u root -p
show databases; # 查看数据库
use 数据库名字; # 进入数据库
show tables;
desc 表的名字;
insert into app01_admin(username, password) values("coderz","123456");
1.2、前端基础效果
-
导航栏 <ul class="nav navbar-nav">
<li><a href="/admin/list/">管理员账户</a></li>
<li><a href="/depart/list/">部门管理</a></li>
<li><a href="/user/list/">用户管理</a></li>
<li><a href="/mobile/list/">靓号管理</a></li>
</ul>
-
admin_list.html {% extends 'layout.html' %}
{% block content %}
{# 用户列表 #}
<div class="container">
<div style="margin-bottom: 10px" class="clearfix">
<a class="btn btn-success" href="#">
{# 可以添加一个 target="_blank" : 使页面在新页面打开,如果不设置,会在原页面打开(这里我们还是在当前页面打开) #}
<span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
新建管理员
</a>
{# 搜索框 #}
<div style="float: right;width: 300px;">
<form method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="Search for..."
value="{{ search_result }}">
<span class="input-group-btn">
<button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</div>
</div>
{# 管理员列表 #}
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
管理员列表
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>密码</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for obj in queryset %}
<tr>
<th>{{ obj.id }}</th>
<td>{{ obj.username }}</td>
<td>***********</td>
{# 密码不显示明文 #}
<td>
<a class="btn btn-primary btn-xs" href="#">编辑</a>
<a class="btn btn-danger btn-xs" href="#">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# 分页 #}
<div class="clearfix">
<ul class="pagination">
{{ page_string }}
</ul>
</div>
</div>
{% endblock %}
-
admin.py from django.shortcuts import render, redirect
from app01 import models
from app01.utils.pagination import Pagination
def admin_list(request):
"""管理员列表"""
data_dict = {}
search_result = request.GET.get('q', "")
if search_result:
data_dict["mobile__contains"] = search_result
queryset = models.Admin.objects.all()
page_object = Pagination(request, queryset)
context = {
"search_data": search_result,
"queryset": queryset,
"page_string": page_object.html()
}
return render(request, "admin_list.html", context)
-
utils/encrypt.py from django.conf import settings
import hashlib
def md5(data_string):
obj = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
obj.update(data_string.encode('utf-8'))
return obj.hexdigest()
效果展示:
错误显示:
原因:
1.3、新建管理员(确定密码)
我们发现,新建部门、新建用户、新建靓号、新建管理员,这些界面太相似了,就几处文字显示不太,有木有办法可以减少我们反复复制粘贴堆*山??
当然,我们创建一个HTML文件newly_create.html ,通过传部分参数,满足需求
{% extends 'layout.html' %}
{% block content %}
{# 用户列表 #}
<div class="container">
<div style="margin-bottom: 10px" class="clearfix">
<a class="btn btn-success" href="/admin/add/">
{# 可以添加一个 target="_blank" : 使页面在新页面打开,如果不设置,会在原页面打开(这里我们还是在当前页面打开) #}
<span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
新建管理员
</a>
{# 搜索框 #}
<div style="float: right;width: 300px;">
<form method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="Search for..."
value="{{ search_result }}">
<span class="input-group-btn">
<button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</div>
</div>
{# 管理员列表 #}
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
管理员列表
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>密码</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for obj in queryset %}
<tr>
<th>{{ obj.id }}</th>
<td>{{ obj.username }}</td>
<td>***********</td>
{# 密码不显示明文 #}
<td>
<a class="btn btn-primary btn-xs" href="#">编辑</a>
<a class="btn btn-danger btn-xs" href="#">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# 分页 #}
<div class="clearfix">
<ul class="pagination">
{{ page_string }}
</ul>
</div>
</div>
{% endblock %}
-
urls.py path("admin/list/", admin.admin_list),
path("admin/add/", admin.admin_add),
-
admin.py from django.shortcuts import render, redirect
from app01 import models
from app01.utils.pagination import Pagination
from app01.utils.bootstrap import BootStrapModelForm
from django import forms
from django.core.exceptions import ValidationError
from app01.utils.encrypt import md5
class AdminModelForm(BootStrapModelForm):
confirm_password = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(render_value=True)
)
class Meta:
model = models.Admin
fields = ["username", 'password', "confirm_password"]
widgets = {
"password": forms.PasswordInput(render_value=True)
}
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
def clean_confirm_password(self):
pwd = self.cleaned_data.get("password")
confirm = md5(self.cleaned_data.get("confirm_password"))
if confirm != pwd:
raise ValidationError("密码不一致")
return confirm
class AdminEditModelForm(BootStrapModelForm):
class Meta:
model = models.Admin
fields = ['username']
def admin_list(request):
"""管理员列表"""
data_dict = {}
search_result = request.GET.get('q', "")
if search_result:
data_dict["mobile__contains"] = search_result
queryset = models.Admin.objects.filter(**data_dict)
page_object = Pagination(request, queryset)
context = {
"search_data": search_result,
"queryset": queryset,
"page_string": page_object.html()
}
return render(request, "admin_list.html", context)
def admin_add(request):
""" 添加管理员 """
title = "新建管理员"
if request.method == "GET":
form = AdminModelForm()
return render(request, 'newly_create.html', {'form': form, "title": title})
form = AdminModelForm(data=request.POST)
if form.is_valid():
form.save()
return redirect('/admin/list/')
return render(request, 'newly_create.html', {'form': form, "title": title})
?
效果展示:
正确输入后:
不知道大家有没有碰到,点击【新建管理员】,死活进不去/admin/add/ ,且代码无误
可尝试重启项目
1.4、编辑
-
admin.py def admin_edit(request, nid):
""" 编辑管理员 """
row_object = models.Admin.objects.filter(id=nid).first()
if not row_object:
return redirect('/admin/list/')
title = "编辑管理员"
if request.method == "GET":
form = AdminEditModelForm(instance=row_object)
return render(request, 'newly_create.html', {"form": form, "title": title})
form = AdminEditModelForm(data=request.POST, instance=row_object)
if form.is_valid():
form.save()
return redirect('/admin/list/')
return render(request, 'newly_create.html', {"form": form, "title": title})
-
urls.py path("admin/<int:nid>/edit/", admin.admin_edit),
-
admin_list.html <td>
<a class="btn btn-primary btn-xs" href="/admin/{{ obj.id }}/edit/">编辑</a>
<a class="btn btn-danger btn-xs" href="#">删除</a>
</td>
效果展示:
1.5、删除和重置密码
1.5.1、删除
-
admin.py def admin_delete(request, nid):
""" 删除管理员 """
models.Admin.objects.filter(id=nid).delete()
return redirect('/admin/list/')
-
urls.py path("admin/<int:nid>/detele/", admin.admin_delete),
-
admin_list.html <td>
<a class="btn btn-primary btn-xs" href="/admin/{{ obj.id }}/edit/">编辑</a>
<a class="btn btn-danger btn-xs" href="/admin/{{ obj.id }}/detele">删除</a>
</td>
效果展示:
1.5.2、重置密码
-
admin.py class AdminResetModelForm(BootStrapModelForm):
confirm_password = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(render_value=True)
)
class Meta:
model = models.Admin
fields = ['password', 'confirm_password']
widgets = {
"password": forms.PasswordInput(render_value=True)
}
def clean_password(self):
pwd = self.cleaned_data.get("password")
md5_pwd = md5(pwd)
exists = models.Admin.objects.filter(id=self.instance.pk, password=md5_pwd).exists()
if exists:
raise ValidationError("不能与以前的密码相同")
return md5_pwd
def clean_confirm_password(self):
pwd = self.cleaned_data.get("password")
confirm = md5(self.cleaned_data.get("confirm_password"))
if confirm != pwd:
raise ValidationError("密码不一致")
return confirm
def admin_reset(request, nid):
""" 重置密码 """
row_object = models.Admin.objects.filter(id=nid).first()
if not row_object:
return redirect('/admin/list/')
title = "重置密码 - {}".format(row_object.username)
if request.method == "GET":
form = AdminResetModelForm()
return render(request, 'newly_create.html', {"form": form, "title": title})
form = AdminResetModelForm(data=request.POST, instance=row_object)
if form.is_valid():
form.save()
return redirect('/admin/list/')
return render(request, 'newly_create.html', {"form": form, "title": title})
-
urls.py path('admin/<int:nid>/reset/', admin.admin_reset),
-
admin_list.html <td>
<a href="/admin/{{ obj.id }}/reset/">重置密码</a>
</td>
效果展示:
先瞅一眼数据库
2、用户认证(登录)
实现用户登录前,我们先补充些知识
2.1、Cookie 和 Session
会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。
下面段解释转自: Cookie和Session是什么?它们的区别是什么?_qichangjian的博客-CSDN博客_cookie和session
什么是Cookie?
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端会把Cookie保存起来。
当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。 信息保存的时间可以根据需要设置.
如果没有设置Cookie失效日期,它们仅保存到关闭浏览器程序为止.
如果将Cookie对象的Expires属性设置为Minvalue,则表示Cookie永远不会过期.
Cookie存储的数据量很受限制,大多数浏览器支持最大容量为4K,因此不要用来保存数据集及其他大量数据.
由于并非所有的浏览器都支持Cookie,并且数据信息是以明文文本的形式保存在客户端的计算机中,
因此最好不要保存敏感的,未加密的数据,否则会影响网站的安全性
什么是Session?
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
每个用户访问服务器都会建立一个session,那服务器是怎么标识用户的唯一身份呢?事实上,用户与服务器建立连接的同时,服务器会自动为其分配一个SessionId。
Session和Cookie的区别?
1、数据存储位置:cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、安全性:cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
3、服务器性能:session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
4、数据大小:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
5、信息重要程度:可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。
下面我们简单从
【http无状态短连接】
左:浏览器,右:网站
- 短链接 ==> 浏览器发送【请求】,网站返回【响应】,然后链接就断开了
- 之后再发送请求,网站就不知道你是谁了
那么咋整呢? Cookie 和 Session
http://127.0.0.1:8000/admin/list/
https://127.0.0.1:8000/admin/list/
我们基于Cookie 和 Session 就可以实现用户登录和认证
2.2、用户认证—基本实现
-
login.html {% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
</style>
</head>
<body>
<div class="account">
<h2>用户登录</h2>
<form method="post" novalidate> {# 以POST形式提交 #}
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.password.errors.0 }}</span>
</div>
<input type="submit" value="登 录" class="btn btn-primary">
</form>
</div>
</body>
</html>
-
urls.py path('login/', account.login),
-
account.py from django.shortcuts import render, HttpResponse, redirect
from django import forms
from app01 import models
from app01.utils.bootstrap import BootStrapForm
from app01.utils.encrypt import md5
class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput,
required=True
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(render_value=True),
required=True
)
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
def login(request):
""" 登录 """
if request.method == "GET":
form = LoginForm()
return render(request, 'login.html', {'form': form})
form = LoginForm(data=request.POST)
if form.is_valid():
admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
if not admin_object:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {'form': form})
request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
request.session.set_expiry(60 * 60 * 24 * 7)
return redirect("/admin/list/")
return render(request, 'login.html', {'form': form})
效果展示:
我们先自己创建一个账号密码
输入个错误密码
输入正确账户密码后,重定向至admin/list
BUG:以往设置的账号【coderz02】无法登录了,【重置密码】后又可以用了
【补充:】
我们session信息存哪去了呢?
2.3、体验中间件
若用户未登录,那么除了【登录界面】,访问其他界面都会自动跳转到【登录界面】
目标:在所有视图函数前面统一加入判断。
info = request.session.get("info")
if not info:
return redirect('/login/')
十几个视图函数,一个一个加不太合实际,怎么办呢?
使用Django的【中间件】
实际上我们发送请求到视图函数def index(request): ... 会经过几个中间件(类),类中定义了process_request 方法
也就是,请求来了要执行一个一个类中的process_request 方法
我们app01创个文件夹体验一下【中间件】
-
定义中间件 app01/middleware/auth.py from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
""" 中间件1 """
def process_request(self, request):
print("M1.process_request")
return HttpResponse("无权访问")
def process_response(self, request, response):
print("M1.process_response")
return response
class M2(MiddlewareMixin):
""" 中间件2 """
def process_request(self, request):
print("M2.process_request")
def process_response(self, request, response):
print("M2.process_response")
return response
-
应用中间件(在 setings.py 中注册中间件) 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',
'app01.middleware.auth.M1',
'app01.middleware.auth.M2',
]
-
在中间件的process_request方法
效果展示:
访问http://127.0.0.1:8000/depart/list/
2.4、中间件实现【登录校验】
-
编写中间件 app01/middleware/auth.py from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
if request.path_info == "/login/":
return
info_dict = request.session.get("info")
print(info_dict)
if info_dict:
return
return redirect('/login/')
PS : 不要忘了【排除那些不需要登录就能访问的页面】,否则就会看到那么多次的请求 访问未成功,返回,再访问,再返回… ,所以出现了上面的情况 -
应用中间件 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',
'app01.middleware.auth.AuthMiddleware',
]
然后,我们删掉以前的Cookie(勿忘!!!!!!!)
然后访问http://127.0.0.1:8000/depart/list ,就会自动跳转到【登录界面】
2.5、注销
-
account.py def logout(request):
""" 注销 """
request.session.clear()
return redirect('/login/')
-
layout.html <li><a href="/logout/">注销</a></li>
-
urls.py path('logout/', account.logout),
2.6、图片验证码
前面我们实现了用户登录的基础功能,但这样容易被【暴力破解】,所以我们引入【图片验证码】
2.6.1、生成图片验证码
pip install pillow
下面我们写个脚本测试一下(提前准备好字体文件)
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28):
code = []
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
def rndChar():
"""
生成随机字母
:return:
"""
return chr(random.randint(65, 90))
def rndColor():
"""
生成随机颜色
:return:
"""
return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(0, 4)
draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
for i in range(5):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=rndColor())
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img, ''.join(code)
if __name__ == '__main__':
img, code_str = check_code()
print(code_str)
with open('code.png', 'wb') as f:
img.save(f, format='png')
效果展示:
2.6.2、图片验证码【显示】
-
account.py (1)在class LoginForm(BootStrapForm): 加入code = forms.CharField (在前端显示输入框) (2再定义函数def img_code(request): class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput,
required=True
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(render_value=True),
required=True
)
code = forms.CharField(
label="验证码",
widget=forms.TextInput,
required=True
)
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
from io import BytesIO
from app01.utils.img_verification import check_code
def img_code(request):
""" 生成图片验证码 """
img, code_string = check_code()
request.session['image_code'] = code_string
stream = BytesIO()
img.save(stream, 'png')
return HttpResponse(stream.getvalue())
-
login.html {% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
</style>
</head>
<body>
<div class="account">
<h2>用户登录</h2>
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.password.errors.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<span style="color: red;">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img id="image_code" src="/image/code/" style="width: 125px;"> {# 通过url访问图片,达到动态生成的效果 #}
</div>
</div>
</div>
<input type="submit" value="登 录" class="btn btn-primary">
</form>
</div>
</body>
</html>
-
urls.py path('image/code/', account.img_code),
-
auth.py 勿忘修改auth.py ,而且那个"/image/code/" 不可写为"image/code/" ,少一个反斜杠,图片验证码也不能正常显示 if request.path_info in ["/login/", "/image/code/"]:
效果展示:
点击刷新即可更换验证码
2.6.3、图片验证码【校验】
-
account.py user_input_code = form.cleaned_data.pop('code')
code = request.session.get('image_code', "")
if code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {'form': form})
request.session.set_expiry(60 * 60 * 24 * 7)
整合起来: def login(request):
""" 登录 """
if request.method == "GET":
form = LoginForm()
return render(request, 'login.html', {'form': form})
form = LoginForm(data=request.POST)
if form.is_valid():
user_input_code = form.cleaned_data.pop('code')
code = request.session.get('image_code', "")
if code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {'form': form})
admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
if not admin_object:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {'form': form})
request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
request.session.set_expiry(60 * 60 * 24 * 7)
return redirect("/admin/list/")
return render(request, 'login.html', {'form': form})
-
login.html <div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<span style="color: red;">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img id="image_code" src="/image/code/" style="width: 125px;"> {# 通过url访问图片,达到动态生成的效果 #}
</div>
</div>
</div>
输入正确的账号密码后,即可进入系统:
|