March 2, 2023

Django

安装

pip install django,这是命令行安装
pycharm中直接在setting里安装
image.png
image.png

创建项目

先进入一个指定的目录,在执行命令:
image.png
这边是python的安装目录下,在安装django后会有一个django-admin.exe

左上角点File再点New Project:
image.png
选第二个django,然后选择项目创建在哪儿即可
image.png

pycharm创建出来的会多一个template目录
settings文件开头也有点不同:
image.png

默认文件介绍

默认项目里的文件有以下6个:
template是空的,暂时不管
image.png

接受网络信息的文件,头部请求,不需要动

也是接受网络请求,异步请求,不需要动

项目配置文件

路由设置

app的创建和说明

每一个大项目有很多小功能,这些小功能拆分开来就是app了
通过manage.py去创建app
在pycharm的终端输入:
python manage.py startapp app01:
image.png
这边假如报错:
image.png
在settings.py里添加:
image.png

app里会自动创建一些文件:
image.png

这个固定不动

单元测试的,固定不动

固定不动,数据库字段变更

django默认提供了amdin后台管理,固定不动

重要内容,和函数有关

重要内容,对数据库进行操作

启动运行django

首先确保app注册,上面只是创建了app,并没注册,找到settings.py:
image.png
在末尾加上一句就注册成功了
接下来编写url和视图函数对应关系,在urls.py去编写:
image.png
这一句话是默认的,我们注释掉
image.png
表示如果用户URL+/index之后就会请求后面的函数
image.png
导入app01下的views.py后,访问index就是去请求views.py里面的index函数

1
2
3
4
5
6
7
from django.shortcuts import render,HttpResponse

# Create your views here.
def index(request):#默认需要这个参数
return HttpResponse("欢迎使用")


index函数就这样做好了
接下来启动django程序:
可以通过命令行或者pycharm启动
命令行:python manage.py runserver

pycharm:image.png
点击运行文件即可
image.png
点Django进去可以配置,8000端口
image.png
image.png
这样就直接给我们返回了函数

模板和静态文件

如果我们想要返回一个字符串用httpresponse即可,但是如果我们想要给用户返回一个html文件,那咱们就得用template目录了
image.png
大概就如上
在template文件夹下放这个html文件
image.png
image.png
这边查找到html文件的顺序为,如果app01注册了,那就在app01目录下去找template,如果没有找到就在下一个app里的template找,如果都找不到就在上一级目录里的template去找
image.png
这是因为上面的DIRS的效果,如果删除了那就是去根据app的注册顺序去查找!

静态文件:
静态文件指的就是js,css,图片这些,这些文件也有讲究,不能乱放
静态文件要放在static文件夹下!
image.png
这是在settings的这里设置的
image.png
image.png这个static文件夹放在app或者是外面都可以,最好还是app下面,更整洁
image.png
接下来我们把js和css等等都创建:
image.png
image.png
用如上的方式去引入文件
image.png

Django模板语法

引用变量

新建一个tpl函数和tpl.html
image.png
image.png
我们在函数内定义了一个变量,那我们怎么把这个变量放在页面里呢?
image.png
后面跟一个字典,然后再html中:
image.png
2个花括号就是引用:
image.png
这里面也可以放列表,字典
image.pngimage.png
image.png

语句

{%%}中可以写判断和循环语句

循环语句

image.png
image.png
endfor表示结束循环

image.png
image.png
列表内套用字典形式大概就像n4.0.name或者是n4.0.itmes之类的,都一样用的

条件语句

1
2
3
4
5
{% if n1 == "Boogipop" %}
<h3>嘻嘻</h3>
{% else %}
<h3>嘿嘿</h3>
{% endif %}

image.png

请求和响应

请求

先定义一个函数request来演示:

1
2
3
4
5
6
7
8
9
10
def request(request):
#request是一个对象,封装了用户发送过来的所有请求
# 1.获取请求方式GET/POST
print(request.method)
# 2.获取URL上的GET参数,大小写要区分一下
print(request.GET)
# 3.获得POST参数
print(request.POST)

return HttpResponse("返回数据")

request.method:
image.png
request.GET:
image.png
image.png
request.POST:
目前没办法,因为有些东西没设置,所以没法演示,和GET一样的其实是

响应

这边演示一个重定向:
image.png
这里要导入一下redirect模块:
image.png
image.png
访问之后就过来了,这边处理方式为:
浏览器->Django,然后让浏览器自己去访问百度:
image.png
为第二种方式

小案例:用户登录

概况如下:
image.png
image.png
代码分别为:

1
2
3
4
5
6
7
8
9
10
11
12
def login(request):
if request.method=="GET":
return render(request,"login.html")
else:
#如果是POST请求,获取提交的数据
username=request.POST.get("user")
password=request.POST.get("pwd")
print("用户名:%s,密码:%s"%(username,password))
if username=="root" and password=="root":
return redirect("http://43.140.251.169/")
else:
return render(request,'login.html',{"error_msg":"用户名或密码错误"})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登入</title>
</head>
<body>
<form method="post" action="/login/">
{% csrf_token %} <!--设置这个csrftoken为了防止非法访问-->
<input type="text" name="user" placeholder="用户名">
<input type="password" name="pwd" placeholder="密码">
<input type="submit" value="提交">
<span style="color: blue">{{ error_msg }}</span>

</form>
</body>
</html>

这样就完成了一个简单的登录页面:
image.png
image.png
后台会显示输入了什么:
image.png

ORM-连接mysql的模块

image.png
orm比pymysql更加的简洁
先要安装mysqlclient:
由于我是python3.7然后pycharm又是2020版本的,所以安装不到最新版,好像是这么一回事,安装了一个2.1版本,安装方法也有点不同
参考:
https://blog.csdn.net/cn_1937/article/details/81533544
安装包:
https://pypi.org/project/mysqlclient/2.1.0/#files
安装完后即可,用pip install 下载的文件名

ORM-创建数据库

orm可以帮助我们做两件事情

1.自己创建数据库

现在windows下载一下mysql:

遇到的问题:

1
2
3
4
mysqld.exe --initialize --console          // 初始化
mysqld.exe install mysql // 安装服务
net start mysql // 启动服务
net stop mysql // 停止命令

create databse boogipop:
image.png

orm-连接mysql

在settings如下设置:

1
2
3
4
5
6
7
8
9
10
11
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',#数据库文件
'NAME': 'boogipop',#数据库名称
'USER':'root',#用户名
'PASSWORD':'jiayou357753',#密码
'HOST':'127.0.0.1',#数据库地址
'PORT':3306,
}
}

orm-操作表

接下来的操作就是在models.py里了,之前也说了这是对数据库进行操作的py文件

1
2
3
4
5
from django.db import models
class Userinfo(models.Model): #新建了一个类,相当于创建了app01_userinfo表
name=models.CharField(max_length=32) #新建了name字段,varchar型
password=models.CharField(max_length=64) #新建了password字段,varchar型
age=models.IntegerField() #新建了age字段,为int型

如果需要django帮我们去创建表,还需要2个命令:
终端输入:
python manage.py makemigrations;
python manage.py migrate
image.png
image.png
这边遇到了一些问题,如果出现image.png
就说明你的app目录下没有migration文件夹,并且里面要有init文件,我之前不小心弄出来了所以报错了!!!!
image.png
在mysql中可以看到出来了很多表,其中就包含了我们的app01_userinfo
为什么会有其他的很多表呢?因为我们注册了很多app:
image.png
django在注册表的时候,会去每个app的models.py文件下去查找,其他app里也有models.py,所以后面多出来了一些表

增删改查

image.png

1
2
#新建数据 相当于 insert into app01_department(title)values("娱乐部")
Department.objects.create( title="娱乐部")

可以一次性给多个字段添加数据,后面加逗号分割
接下来我们来测试orm,首先添加一下orm路由和函数:
image.png
然后在views.py导入models.py:
image.png
之后就用models.Userinfo.xxxx去进行增删改查操作
image.png
再访问网址:
image.png
这里要注意一下,一定要把department表注册了哦
然后在数据库中查看:
image.png
成功添加数据

测试的表如下:
image.png

1
print(models.Userinfo.objects.all()) #获取userinfo表里的所有数据

image.png
说明里面有两行数据,每一行都是一个对象,如何取数据呢?
用循环语句去获取
如下:

1
2
3
4
data=models.Userinfo.objects.all() #获取userinfo表里的所有数据
for i in data:
print(i.id,i.age,i.name,i.password)
#相当于 print(data[0].id,data[0].name)

image.png
还可以用filter筛选:

1
data=models.Userinfo.objects.filter(id=1)

得到的也是一个对象,是第一行数据

1
2
3
4
5
a=models.Userinfo.objects.filter(id=1).first
print(a.id)
#等价于
a=models.Userinfo.objects.filter(id=1)[0]
print(a.id)

ORM案例-用户管理

功能1-展示用户列表

image.png
新建一个infolist函数并添加路由
再写一个html文件如下:
image.png
源码我最后放
image.png
可以看到成功显示!

功能2-添加用户

是自己手打的哦

1
2
3
4
5
6
7
8
9
def adduser(request):
if request.method=='GET':
return render(request,"user_add.html")
if request.method=='POST':
username=request.POST.get("user")
password=request.POST.get("pwd")
age=request.POST.get("age")
models.Userinfo.objects.create(name=username,password=password,age=age)
return HttpResponse("添加成功!!")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<h1>添加用户</h1>
<form method="post" action="/info/add/">
{% csrf_token %}
<input type="text" name="user" placeholder="用户名">
<input type="password" name="pwd" placeholder="密码">
<input type="text" name="age" placeholder="年龄">
<input type="submit" value="提交">
</form>
</body>
</html>

image.pngimage.png
image.png

进一步美化:
image.png
image.png

功能3-删除用户

1
2
3
4
def deluser(request):
uid=request.GET.get("uid")
models.Userinfo.objects.filter(id=uid).delete()
return redirect("/info/list")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<h1>Information</h1>
<a href="http://localhost:8000/info/add">添加用户</a>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>密码</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for obj in data_list %}
<tr>
<td>{{ obj.id }}</td>
<td>{{ obj.name }}</td>
<td>"{{ obj.password }}"</td>
<td>{{ obj.age }}</td>
<td><a href="http://127.0.0.1:8000/info/del/?uid={{ obj.id }}">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

至此就结束了:
image.png

员工管理系统

创建项目和app

image.png点run manage.py task:
image.png
输入startapp app01,在这之前也需要settings里improt os:
image.png
创建成功QWQ
别忘了注册

表结构的创建

image.png
创建表的信息如上,foreignkey表示depart_id只可以在department中的id的值范围里取,另外在数据库中foreignkey会在depart后面自动加上一个_id,最后是depart_id
为什么Department里为什么没有id字段呢?这是因为django会自己帮你创建一个

但这个还不是最终的表,假如我们删除一个部门,那底下的用户是不是也该删除呢?
要在ForeignKey后面加:
image.png
另外如果我们不想删除这些部门的人,我们把他的部门ID滞空,可以这样写:
image.png

再加上性别,最终的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

from django.db import models
class Department(models.Model):
'''部门表'''
title=models.CharField(verbose_name="标题",max_length=64)
#verbose_name表示备注信息一样
class UserInfo(models.Model):
'''员工表'''
name=models.CharField(verbose_name="名字",max_length=32)
password=models.CharField(verbose_name="密码",max_length=64)
age=models.IntegerField(verbose_name="年龄")
account=models.DecimalField(verbose_name="账户余额",max_digits=10,decimal_places=2,default=0)
#账户余额为小数,最大长度为10,小数点后面占2个,默认是0余额
create_time=models.DateTimeField(verbose_name="入职时期")
#表示日期类型
# depart_id=models.CharField(verbose_name="所属部门",max_length=32)
# depart_id=models.BigIntegerField(verbose_name="部门ID")
#BigIntegerField表示大整数也就是long
#无约束
# depart=models.ForeignKey(to='Department',to_field='id',on_delete=models.CASCADE())
#有约束,to表示与哪张表关联,to_field表示和表里什么字段关联,CASCADE表示的是级联删除
depart=models.ForeignKey(to='Department',to_field='id',null=True,blank=True,on_delete=models.SET_NULL)
#null,blank=true,on_delete=models.SET_NULL表示这一列可以为空
gender_choice=(
(1,"男"),
(2,"女")
)
gender=models.SmallIntegerField(verbose_name="性别",choices=gender_choice)
#这是DJango中做的约束,表示1代表男,2代表女,上面的滞空和级联都是数据库里的约束,这里表示以后输入1就代表男
#Smallintergerfield表示小整数

MYSQL生成数据库

create database OA:
image.png
再用django去创建一下表:
先修改配置文件连接mysql:
image.png
再运行指令即可:
image.png
image.png

部门列表展示

先加入一些静态文件
然后准备个html文件,做页面展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
{% 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>
.navbar {
border-radius: 0;
}
</style>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"> 联通用户管理系统 </a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/depart/list/">部门管理</a></li>
<li><a href="/user/list/">用户管理</a></li>
<li><a href="#">Link</a></li>


</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">登录</a></li>

<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Boogipop <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人资料</a></li>
<li><a href="#">我的信息</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">注销</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>

<div>
<div style="margin-bottom: 10px;">
<a class="btn btn-success" href="#">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
新建部门
</a>
</div>

<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
部门列表
</div>

<!-- Table -->
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>部门名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for data in Infolist%}
<tr>
<td>{{ data.id }}</td>
<td>{{ data.title }}</td>
<td>
<a class="btn btn-primary btn-xs">编辑</a>
<a class="btn btn-danger btn-xs">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>


<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
</body>
</html>


image.pngimage.png
最后效果如下:
image.png
还挺不错的哈

数据库数据

insert into app_department(title) values("心理部");
insert into app01_department(title) values("CTF部");
然后在html页面添加模板语句:
image.png
image.png
成功显示

新建部门

事先说好,这些模板都是在https://v3.bootcss.com/css/#forms-horizontal直接拿的
image.png
先添加一条a标签,跳转到添加页面
image.png
添加路由
image.png
添加函数
然后html页面从之前的部门列表界面,把导航条拿过来:
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{% 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>
.navbar {
border-radius: 0;
}
</style>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"> 联通用户管理系统 </a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/depart/list/">部门管理</a></li>
<li><a href="/user/list/">用户管理</a></li>
<li><a href="#">Link</a></li>


</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">登录</a></li>

<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Boogipop <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人资料</a></li>
<li><a href="#">我的信息</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">注销</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">新建部门</h3>
</div>
<div class="panel-body">
<form>
<div class="form-group">
<label >标题</label>
<input type="text" class="form-control" placeholder="标题" name="title">
</div>

<button type="submit" class="btn btn-primary">提交</button>
</form> </div>
</div>
</div>
</div>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
</body>
</html>

部门添加

image.png
设置一下提交方式,然后再改一下函数:
image.png
测试:
image.png
image.png
成功添加!

删除

image.png
添加函数跟路由

1
<a class="btn btn-primary btn-xs" href="http://127.0.0.1:8000/depart/del/?uid={{ data.id }}">删除</a>

修改HTML代码
image.png
成功删除咯

编辑

image.png
添加路由和函数,以及html页面
接下来获取ID咱们就用一种不一样的方式:
image.png
image.png
酱紫修改就是一种船新的方式,可以达到上面删除界面中同样的效果!
image.png
这样就可以拿到对应的数据:
image.png
image.png
再往下这么写就可以出现默认值
image.png

1
<input type="text" class="form-control" placeholder="标题" name="title" value="{{ data.title }}">

image.png
默认值已经出来了~
接下来要做的事情就是更新了,对提交的数据做出处理
image.png表单里不写action默认提交到当前页面

1
2
3
if request.method=='POST':
obj=models.Department.objects.filter(id=nid).update(title=request.POST.get("title"))
return redirect('/depart/list')

这样就完成了修改功能
image.png
image.png
yaho~成功拉

模板的继承

在django中,我们是可以html模板的,具体操作如何实现呢,接下来就来演示
先准备layout.html,内容就从depart_list.html直接复制过来:
image.png
重点看这里,这边是div分的区域,在这里可以进行继承:
image.png
修改为这样,其中content是们自定义的,然后再修改depart_list.html:
image.png
layout也就是depart_list的模板,是父类,这样界面不会改变:
image.png
同理也可以修改depart_edit页面:
image.png
image.png
新建部门页面也是同理啊~
image.png
这很方便qwq!!!另外注意了,可以定义多个模板点,也就是多个block,修改content为其他的东西即可
block content,block content1这样就行

用户列表

老方法先添加路由,函数,html:
image.png
再编辑一下html界面:
image.png
大致为这样:
image.png
然后修改一下部门列表里的超链接:
image.png
用户数据库信息如下:
image.png
我们先手动插入几条数据:

1
2
3
insert into app01_userinfo values(1,"boogipop","root",15,"100.00","2018-10-15",2,1)
insert into app01_userinfo values(2,"kino","root",14,"120.00","2018-12-15",2,2)
insert into app01_userinfo values(3,"Alex","root",18,"130.00","2018-11-15",1,5)

image.png
目前数据库就如上

1
2
3
4
5
6
userinfo=models.UserInfo.objects.all()
for data in userinfo:
print(data.id,data.name,data.password,data.age,data.account,data.create_time.strftime("%Y-%m-%d"),data.get_gender_display(),data.depart.title)
#data.create_time.strftime("%Y-%m-%d")表示将datatime类型转换为字符串类型,略去后面的00000
#ata.get_gender_display() 表示直接获得1对应的选择,也就是男或者女
#data.depart.title 表示直接获得关联表的title,我们depart是foreignkey类型的

函数如上:image.png
数据都会打印出来,这样就获取了数据,接下来就展示出来
image.png
这样写会有一些报错,因为那是python的函数,我们再html写会报错
image.png
django中默认会给你加括号,所以不需要括号,如果括号里有参数就用|隔开,data就表示参数

image.png
这样界面就出来了

用户添加(原始方法)

image.png
路由!
html!:
image.png
user_list里的href!:
image.png
最后的html界面如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{% extends 'layout.html' %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">新建用户</h3>
</div>
<div class="panel-body">
<form method="post">
{% csrf_token %}
<div class="form-group">
<label >名称</label>
<input type="text" class="form-control" placeholder="名称">
</div>

<div class="form-group">
<label >密码</label>
<input type="text" class="form-control" placeholder="密码">
</div>

<div class="form-group">
<label >年龄</label>
<input type="text" class="form-control" placeholder="年龄">
</div>

<div class="form-group">
<label >账户余额</label>
<input type="text" class="form-control" placeholder="账户余额">
</div>

<div class="form-group">
<label >入职时间</label>
<input type="text" class="form-control" placeholder="入职时间">
</div>

<div class="form-group">
<label >性别</label>
<select class="form-control">
{% for choice in gender %}
<option value="{{ choice.0 }}">{{choice.1}}</option>
{% endfor %}
</select>
</div>

<div class="form-group">
<label >部门</label>
<select class="form-control">
{% for choice in departid %}
<option value="{{ choice.id }}">{{ choice.title }}</option>
{% endfor %}
</select>
</div>




<button type="submit" class="btn btn-primary">提交</button>
</form> </div>
</div>
{% endblock %}

函数为:
image.png
这样就可以把所有用户展示出

Form和ModelForm

image.png
假如我们按照如上操作获取参数,那么会有以下几个缺点

参数校验繁琐
错误提示繁琐
页面每个字段都需要重新写一遍
如果有关联的数据,还要先取出来再循环得出,麻烦

初始Form

form可以在上面的基础上进一步的去优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django import forms
class MyForm(forms.Form):
user = forms.CharField(widget=forms.Input)
pwd = forms.CharFiled(widget=forms.Input)
email = forms.CharFiled(widget=forms.Input)
account = forms.CharFiled(widget=forms.Input)
create_time = forms.CharFiled(widget=forms.Input)
depart = forms.CharFiled(widget=forms.Input)
gender = forms.CharFiled(widget=forms.Input)


def user_add(request):
if request.method == "GET":
form = MyForm()
return render(request, 'user_add.html',{"form":form})

views.py内容如上,我们只需在函数上边儿定义一个类,继承Form类即可
然后user,pwd这些怎么利用呢?HTML界面中这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form method="post">
{% for field in form%}
{{ field }}
{% endfor %}
<!-- <input type="text" placeholder="姓名" name="user" /> -->
</form>

<!-- 上下等价 -->
<form method="post">
{{ form.user }}
{{ form.pwd }}
{{ form.email }}
<!-- <input type="text" placeholder="姓名" name="user" /> -->
</form>

这就相当于我们的input标签!是不是很赞

ModelForm

可以理解为Form组件的升级版,这样会更加轻松:

1
2
3
4
5
6
7
8
9
10
11
from django import forms
class MyForm(forms.ModelForm):
class Meta:
model = UserInfo
fields = ["name","password","age"]
#你想呈现什么内容,就往fields里面写

def user_add(request):
if request.method == "GET":
form = MyForm()
return render(request, 'user_add.html',{"form":form})
1
2
3
4
5
6
<form method="post">
{% for field in form%}
{{ field }}
{% endfor %}
<!-- <input type="text" placeholder="姓名" name="user" /> -->
</form>

是不是更简洁了

用户添加-ModelForm版本

form.name.label表示取一开始的verbose_name
实操如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% for field in form %}
{{ field.label }}: {{ field }}
{% endfor %}
</body>
</html>
1
2
3
4
5
6
7
8
from django import forms
class Userform(forms.ModelForm):
class Meta:
model=models.UserInfo
fields=["name","password","age","account","create_time","gender","depart"]
def user_modelform_add(request):
form=Userform()
return render(request, 'user_modelform_add.html',{"form":form})
1
2
3
4
5
6
from django.db import models
class Department(models.Model):
'''部门表'''
def __str__(self):
return self.title
title=models.CharField(verbose_name="标题",max_length=64)

假如不使用str魔术方法的结果为:
image.png
这个原因是为什么应该懂吧,一行数据就是对象,用str魔术方法,让他print的时候输出title,最后效果如下:
image.png
是不是很方便呢?

原理弄懂了就把他逐步完善一下:
image.png
界面样式为:
image.png
感觉有点丑啊
image.png
定义widgets加载css:
image.png
这样还是很麻烦:
image.png
image.png
这里输出的name和field应该都不陌生吧
image.png
所以这样表示在field中都加上一个class
image.png
最后再加上一个placeholder在class后边儿:
image.png
image.png
继续完善

1
2
3
4
5
6
7
8
9
10
11
12
13
def user_modelform_add(request):
if request.method=='GET':
form=Userform()
return render(request, 'user_modelform_add.html',{"form":form})
#将提交的参数检验是否合法,若合法保存数据库
form=Userform(data=request.POST)
if form.is_valid():
# print(form.cleaned_data)#合法信息
form.save()#自动将输入的参数保存到数据库中!
return redirect('/user/list/')
else:
print(form.errors)#错误信息

image.png
这里就是参数校验环节
image.png
image.png
成功完成添加用户

设置错误信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django import forms
class Userform(forms.ModelForm):
#名称至少3个字符
name=forms.CharField(min_length=3,label='名称')#这里要重新添加label,这里相当于重写,这里重写不是代表重写数据库的字段
password=forms.CharField(min_length=8,label='密码')#这里也可以添加正则
class Meta:
model=models.UserInfo
fields=["name","password","age","account","create_time","gender","depart"]
# widgets={
# "name": forms.TextInput(attrs={"class": "form-control"}),
# "password": forms.PasswordInput(attrs={"class": "form-control"}),
# "age": forms.TextInput(attrs={"class": "form-control"}),
# }
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
#super表示调用父类,这里表示在父类原有的基础上补充
#循环体里找到所有插件,添加class
for name,field in self.fields.items():
field.widget.attrs={"class":"form-control","placeholder":field.label}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def user_modelform_add(request):
if request.method=='GET':
form=Userform()
return render(request, 'user_modelform_add.html',{"form":form})
#将提交的参数检验是否合法,若合法保存数据库
form=Userform(data=request.POST)
if form.is_valid():
# print(form.cleaned_data)#合法信息
form.save()#自动将输入的参数保存到数据库中!
return redirect('/user/list/')
else:
#此form非彼form,这个form已经传了POST的参数,蕴含error信息
return render(request,'user_modelform_add.html',{"form":form})
# print(form.errors)#错误信息

image.png
filed.errors.0表示错误信息的第一条,如果不加0结果会很乱
然后novalidate是不需要前端给我们验证参数是否为空,这是为了体验错误信息嘛

然后最后不顺眼的地方就是错误信息是英文,我们如何改为中文呢?
image.png
改为:
image.png
image.png
成功!

用户编辑和删除

咳咳,从现在开始就简短的写了,都很熟练了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def user_edit(request,nid):
row_obj = models.UserInfo.objects.filter(id=nid).first() # 取出第一个对象
if request.method=='GET':
form = Userform(instance=row_obj)#添加instance就可以输入的时候显示默认值
return render(request,'user_edit.html',{"form":form})
form=Userform(data=request.POST,instance=row_obj)
#这儿的instance必须要加,表示是更新数据,而不是新增数据,要不然save无法识别
if form.is_valid():
form.save()
#默认保存的是输入的数据,如果想要增加输入以外的值
#可以这样 form.instance.字段名=值,这个写在save上面
return redirect('/user/list/')
return render(request,'user_edit.html',{"form":form})

def user_del(request,nid):
models.UserInfo.objects.filter(id=nid).delete()
return redirect('/user/list/')
1
2
path('user/<int:nid>/edit/',views.user_edit),
path('user/<int:nid>/del/',views.user_del),

image.png
页面就是直接抄user_add的,改一点文字就可以

靓号管理-查看

也是直接上路由,HTML,函数了

1
2
3
4
def pretty_list(request):
prettynum=models.pretty.objects.all().order_by('-level')
#相当于select * from 表 order by level desc 倒序输出
return render(request,'pretty_list.html',{"info":prettynum})
1
path('pretty/list/',views.pretty_list),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{% extends 'layout.html' %}
{% block content %}
<div style="margin-bottom: 10px;">
<a class="btn btn-success" href="/user/add/" target="_blank">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
新建靓号
</a>
</div>

<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
靓号列表
</div>

<!-- Table -->
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>号码</th>
<th>价格</th>
<th>级别</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for data in info %}
<tr>
<td>{{ data.id }}</td>
<td>{{ data.mobile }}</td>
<td>{{ data.price }}</td>
<td>{{ data.get_level_display }}</td>
<td>{{ data.get_status_display }}</td>
<td>
<a class="btn btn-primary btn-xs" href="/user/{{ data.id }}/edit/">编辑</a>
<a class="btn btn-danger btn-xs" href="/user/{{ data.id }}/del/">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

image.png
修改了一点
image.png

新建靓号

代码分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.core.validators import RegexValidator
class Prettyadd(forms.ModelForm):
mobile=forms.CharField(label='号码',validators=[RegexValidator(r'^158[0-9]+$','号码必须以158开头,并且至少4位数')])
class Meta:
model=models.pretty
fields='__all__'#就表示所有字段
# exclude=['id']表示除了某个字段
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
#super表示调用父类,这里表示在父类原有的基础上补充
#循环体里找到所有插件,添加class
for name,field in self.fields.items():
field.widget.attrs={"class":"form-control","placeholder":field.label}
def pretty_add(request):
if request.method=='GET':
form=Prettyadd()
return render(request,'pretty_add.html',{"form":form})
form=Prettyadd(data=request.POST)
if form.is_valid():
form.save()
return redirect('/pretty/list/')
else:
return render(request,'pretty_add.html',{"form":form})

image.png

image.png

image.png
还有一种方法二
image.png
self.cleaned_data就是用户输入的,我们加一个if对他判断即可

1
2
from django.core.exceptions import ValidationError
raise ValidationError('msg')就是抛出msg错误信息

靓号编辑和删除

不允许重复:
添加函数:

1
2
3
4
5
6
7
# [obj,obj,obj]
queryset = models.PrettyNum.objects.filter(mobile="1888888888")

obj = models.PrettyNum.objects.filter(mobile="1888888888").first()

# True/False
exists = models.PrettyNum.objects.filter(mobile="1888888888").exists()

编辑函数(手机号不能存在):

1
2
3
4
排除自己以外,其他的数据是否手机号是否重复?

# id!=2 and mobile='1888888888'
models.PrettyNum.objects.filter(mobile="1888888888").exclude(id=2)

mobile=forms.CharField(disabled=True,label='号码')
此条语句一出,就说明某个数据不能变动:
image.png

最后就简简单单的上个源码拉:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Prettyadd(forms.ModelForm):
mobile=forms.CharField(label='号码',validators=[RegexValidator(r'^158[0-9]+$','号码必须以158开头,并且至少4位数')])
class Meta:
model=models.pretty
fields='__all__'#就表示所有字段
# exclude=['id']表示除了某个字段
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
#super表示调用父类,这里表示在父类原有的基础上补充
#循环体里找到所有插件,添加class
for name,field in self.fields.items():
field.widget.attrs={"class":"form-control","placeholder":field.label}
def clean_mobile(self):#此处名字必须使用固定的,否则不行
'''检测手机号是否重复 '''
number=self.cleaned_data['mobile']#cleaned_data就是用户输入
exists=models.pretty.objects.filter(mobile=number).exists()
if exists:
raise ValidationError('手机号已存在')
return number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Prettyedit(forms.ModelForm):
# mobile=forms.CharField(disabled=True,label='号码')
#不允许改动mobile数据
mobile=forms.CharField(label='号码',validators=[RegexValidator(r'^158[0-9]+$','号码必须以158开头,并且至少4位数')])
class Meta:
model=models.pretty
fields=['mobile','price','level','status']
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
#super表示调用父类,这里表示在父类原有的基础上补充
#循环体里找到所有插件,添加class
for name,field in self.fields.items():
field.widget.attrs={"class":"form-control","placeholder":field.label}

def clean_mobile(self): # 此处名字必须使用固定的,否则不行
'''检测手机号是否重复 '''
number = self.cleaned_data['mobile'] # cleaned_data就是用户输入
#self.instance.pk表示的是当前的id,由于这是在函数里没有nid
exists = models.pretty.objects.filter(mobile=number).exclude(id=self.instance.pk).exists()
if exists:
raise ValidationError('手机号已存在')
return number



def pretty_edit(request,nid):
row_obj = models.pretty.objects.filter(id=nid).first()
if request.method == 'GET':
form = Prettyedit(instance=row_obj) # 添加instance就可以输入的时候显示默认值
return render(request, 'pretty_edit.html', {"form": form})
form = Prettyedit(data=request.POST, instance=row_obj)
# 这儿的instance必须要加,表示是更新数据,而不是新增数据,要不然save无法识别
if form.is_valid():
form.save()
# 默认保存的是输入的数据,如果想要增加输入以外的值
# 可以这样 form.instance.字段名=值,这个写在save上面
return redirect('/pretty/list/')
return render(request, 'pretty_edit.html', {"form": form})

def pretty_del(request,nid):
models.pretty.objects.filter(id=nid).delete()
return redirect('/pretty/list/')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends 'layout.html' %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">编辑用户</h3>
</div>
<div class="panel-body">
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label >{{ field.label }}</label>
{{field}}
<span style="color: red;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
{% endblock %}
1
2
path('pretty/<int:nid>/edit/',views.pretty_edit),
path('pretty/<int:nid>/del/',views.pretty_del),

image.png

搜索

先介绍一下其实filter还可以传入字典:

1
2
3
dict={"id":1
,"name":"kino"}
models.pretty.objects.filter(**dict)

这样也可以进行筛选
filter还有其他用法,还有其他的筛选用法如下:

1
2
3
4
5
6
7
8
models.pretty.objects.filter(id=1)#id=1
models.pretty.objects.filter(id__gt=12)#id>12
models.pretty.objects.filter(id__gte=12)#id>=12
models.pretty.objects.filter(id__lt=12)#id<12
models.pretty.objects.filter(id__lte=12)#id<=12
models.pretty.objects.filter(mobile__startwith="158")#以158开头的号码
models.pretty.objects.filter(mobile__endwith="666")#以158结尾的号码
models.pretty.objects.filter(mobile__contains="158666")#包含158666的号码

image.png
这样可以来获取我们URL输入的,再检查,比如输入?q=0:
image.png
image.png
这里的if value并不是指的是value为0就不通过判断,而是判断是否为空
给源码了:

1
2
3
4
5
6
7
8
9
def pretty_list(request):
data={}
value=request.GET.get('q',"")
if value:
data["mobile__contains"]=value
res=models.pretty.objects.filter(**data)
prettynum=models.pretty.objects.filter(**data).order_by('-level')
#相当于select * from 表 order by level desc 倒序输出
return render(request,'pretty_list.html',{"info":prettynum,"search":value})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div style="margin-bottom: 10px;" class="clearfix">
<a class="btn btn-success" href="/pretty/add/" target="_blank">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
新建靓号
</a>

<div style="float: right;width: 300px;">
<form method="get">
<div class="input-group">
<input type="text" class="form-control" name="q" placeholder="Search for..." value="{{ search }}">
<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>

也不难

分页(原理)

诶,四十多分钟的教学,看的头皮发麻,做开发的真难
首先先来讲一下整体思路吧,思路:

思路说完了就来实操吧,你已经会打水泥了来造房子吧!
这里比较繁琐就一步步介绍了,首先先添加300条数据来做测试:
image.png
在这之后:

1
2
3
4
5
6
page=int(request.GET.get("page",1))#这个1是默认值为1,也就是第一页
step=10#设置一页展示多少行
start=(page-1)*step
end=page*step

prettynum=models.pretty.objects.filter(**data).order_by('-level')[start:end]

和思路里介绍的一样,那接下来就是设置分页框了,去bootstrap拿下来

1
2
3
4
5
<nav aria-label="Page navigation">
<ul class="pagination">
{{ change }}
</ul>
</nav>

再设置一下总页数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
page=int(request.GET.get("page",1))#这个1是默认值为1,也就是第一页
step=10#设置一页展示多少行
start=(page-1)*step
end=page*step
#页码
page_str_list=[]
total_list=models.pretty.objects.all().count()#找出总数据条数
total_page,div=divmod(total_list,step)
if div!=0:
total_page=total_page+1
for i in range(1,total_page+1):
ele='<li><a href="?page={}">{}</a></li>'.format(i,i)
page_str_list.append(ele)
page_string="".join(page_str_list)#转化为字符串
page_string=mark_safe(page_string)#变为HTML代码

image.png
效果就如下了
下面是对分页效果的优化,展示11个页码,然后临界点问题,不能有负数
也直接拿源码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def pretty_list(request):
# for i in range(300):
# models.pretty.objects.create(mobile='666',price='555',level=1,status=2)

data={}
value=request.GET.get('q',"")
if value:
data["mobile__contains"]=value
res=models.pretty.objects.filter(**data)

page=int(request.GET.get("page",1))#这个1是默认值为1,也就是第一页
step=10#设置一页展示多少行
start=(page-1)*step
end=page*step

#页码
page_str_list=[]
total_list=models.pretty.objects.all().count()#找出总数据条数
total_page,div=divmod(total_list,step)
if div!=0:
total_page=total_page+1
#前五页后五页
plus=5
start_page=page-plus if(page-plus)>1 else 1
end_page=page+plus+1 if(page+plus+1)<total_page else total_page
#首页
up='<li><a href="?page=1">首页</a></li>'
page_str_list.append(up)
#上一页
if page>1:
prev='<li><a href="?page={}">上一页</a></li>'.format(page-1)
else:
prev ='<li><a href="?page=1">上一页</a></li>'
page_str_list.append(prev)

for i in range(start_page,end_page+1):
if i==page:
ele='<li class="active"><a href="?page={}">{}</a></li>'.format(i,i)
else:
ele='<li><a href="?page={}">{}</a></li>'.format(i,i)
page_str_list.append(ele)
# 下一页
if page < total_page:
suffix = '<li><a href="?page={}">下一页</a></li>'.format(page + 1)
else:
suffix = '<li><a href="?page={}">下一页</a></li>'.format(total_page)
page_str_list.append(suffix)
page_string = "".join(page_str_list) # 转化为字符串
page_string=mark_safe(page_string)#变为HTML代码



prettynum=models.pretty.objects.filter(**data).order_by('-level')[start:end]
#相当于select * from 表 order by level desc 倒序输出

分页组件

image.png
先把上题的再完善一下做个跳转
image.png

做好基本分页后,会发现点了另一页之后参数p就会被page覆盖,这2个参数是不能同时存在的QWQ
首先要知道一些函数:

1
2
3
4
5
test=request.GET
test._mutable=True
test.setlist('xx',[11,12])
print(test)
print(test.urlencode())

通常来说request.GET是获得GET所有的参数,是个字典,之后是不能使用setlist去添加参数的,要用_mutable,开启这个选项之后就可以添加
image.png
接下来直接上源码,可能有点绕,我绕了好久啊。。。
真的发现开发其实挺难的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
'''
分页组件
'''
from django.utils.safestring import mark_safe #mark_safe模块可以让字符串变为HTML代码

class jump(object):
def __init__(self,request,queryset,step=10,page_param="page",plus=5):
query_dict=request.GET
query_dict._mutable=True
self.query_dict=query_dict
page=request.GET.get(page_param,"1")#这里的1一定要用字符串,因为isdecimal需要字符串类型
if page.isdecimal():
page=int(page)
else:
page=1
self.step=step
self.page=page
self.page_param=page_param
self.start = (page - 1) * step
self.end = page * step
self.queryset=queryset[self.start:self.end]
self.plus=plus
total_list=queryset.count()
print(queryset)
total_page,div=divmod(total_list,step)
if div:
self.total_page=total_page+1
else:
self.total_page=total_page
print(self.total_page, total_list,div)
def HTMl(self):
page_str_list = []
# 前五页后五页
start_page = self.page - self.plus if (self.page - self.plus) > 1 else 1
end_page = self.page + self.plus + 1 if (self.page + self.plus + 1) < self.total_page else self.total_page
# 首页
print(end_page)
self.query_dict.setlist(self.page_param,[1])
up = '<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode())
page_str_list.append(up)
# 上一页
if self.page > 1:
self.query_dict.setlist(self.page_param, [self.page - 1])
prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
else:
self.query_dict.setlist(self.page_param, [1])
prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
page_str_list.append(prev)
print(self.page,self.plus,start_page,end_page)
for i in range(start_page, end_page + 1):
if i == self.page:
self.query_dict.setlist(self.page_param,[i])
ele = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
else:
self.query_dict.setlist(self.page_param,[i])
ele = '<li><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
page_str_list.append(ele)
# 下一页
if self.page < self.total_page:
self.query_dict.setlist(self.page_param, [self.page+1])
suffix = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
else:
self.query_dict.setlist(self.page_param, [self.total_page])
suffix = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
page_str_list.append(suffix)
jump = '''
<li>
<form method="get" style="float: left;margin-left: -1px">
<div class="input-group" style="width: 200px">
<input type="text" name="page" class="form-control" placeholder="页码">
<span class="input-group-btn">
<button class="btn btn-default" tyoe="submit">跳转</button>
</span>
</div>
</form>
</li>
'''
page_str_list.append(jump)
page_string = "".join(page_str_list) # 转化为字符串
page_string = mark_safe(page_string) # 变为HTML代码
return page_string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def pretty_list(request):
# for i in range(300):
# models.pretty.objects.create(mobile='666',price='555',level=1,status=2)
data={}
value=request.GET.get('q',"")
if value:
data["mobile__contains"]=value
res=models.pretty.objects.filter(**data)
#开始使用自己做的组件
prettynum=models.pretty.objects.filter(**data).order_by('-level')
jump_obj=jump(request,prettynum,step=15)
queryset=jump_obj.queryset
page_string=jump_obj.HTMl()
content={
"info":queryset,#数据
"search":value,#检索
"change":page_string#分页
}
#相当于select * from 表 order by level desc 倒序输出
return render(request,'pretty_list.html',content)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def user_list(request):
'''用户列表'''
#获取用户信息
userinfo=models.UserInfo.objects.all()
page_object=jump(request,userinfo,5)
content={
"userinfo":page_object.queryset,
"page_string":page_object.HTMl()
}

# for data in userinfo:
#print(data.id,data.name,data.password,data.age,data.account,data.create_time.strftime("%Y-%m-%d"),data.get_gender_display(),data.depart.title)
#data.create_time.strftime("%Y-%m-%d")表示将datatime类型转换为字符串类型,略去后面的00000
#ata.get_gender_display() 表示直接获得1对应的选择,也就是男或者女
#data.depart.title 表示直接获得关联表的title,我们depart是foreignkey类型的
return render(request,'user_list.html',content)

用户页表的html:
image.png
最终展示:
image.png
image.png

时间挂件

首先先引入时间插件
image.png
这东西去官网下载吧,之后再更改一下模板文件和用户添加文件就好了:
image.pngimage.png
模板文件多添加2个block分别来放css和js文件,js的block一定放下面,因为有定义函数
用户编辑页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{% extends 'layout.html' %}

{% load static %}

{% block css %}
<link rel="stylesheet" href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datepicker.min.css' %}">
{% endblock %}

{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">ModelForm新建用户</h3>
</div>
<div class="panel-body">
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label >{{ field.label }}</label>
{{field}}
<span style="color: red;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
{% endblock %}

{% block js %}
<script src="{% static 'plugins/bootstrap-datepicker/js/bootstrap-datepicker.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js' %}"></script>
<script>
$(function () {
$('#id_create_time').datepicker({
format: 'yyyy-mm-dd',
startDate: '0',
language: "zh-CN",
autoclose: true
});

})
</script>
{% endblock %}

image.png
这里的id_create_time是django的modelform模板自己默认的值:
image.png

Bootstrap样式父类

之前我们给输入框添加属性是通过添加插件:

拆分功能(优化)

知道了上面的自定义类,我们就来优化一下:
首先先创建父类模板
image.png
再将之前定义的ModelForm类里的init方法删干净,继承BootStrapModelForm
image.png
image.pngimage.png

这样算是完成了第一部的封装,我们看一下views.py的缩略图:
image.png
会发现还是有一些类,我们的类可以单独的去放在一个文件内,就比如是forms.py内,这样可以更加简介:
image.png
灰色的没用到的模块就删除掉
image.png
现在只剩下了函数,我们的函数也可以进一步进行拆分,就比如放在views文件夹内:
里面分别放置User,Pretty,depart这三个模块的py文件:
image.pngimage.pngimage.png
views的py文件就删除了,接下来需要改的也就只是路由:
image.png
之后页面还是照常运行QWQ
image.png

管理员数据库

image.png
makemigrations+migrate创建数据库

管理员列表

先自行插入数据库两条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{% extends 'layout.html' %}
{% block content %}
<div style="margin-bottom: 10px;" class="clearfix">
<a class="btn btn-success" href="/admin/add/" target="_blank">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
新建管理员
</a>

<div style="float: right;width: 300px;">
<form method="get">
<div class="input-group">
<input type="text" class="form-control" name="q" placeholder="Search for..." value="{{ search }}">
<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">
<!-- Default panel contents -->
<div class="panel-heading">
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
管理员列表
</div>

<!-- Table -->
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>密码</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for data in info %}
<tr>
<td>{{ data.id }}</td>
<td>{{ data.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>
<nav aria-label="Page navigation">
<ul class="pagination">
{{ change }}
</ul>
</nav>
</div>
{% endblock %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.shortcuts import redirect,render
from app01 import models
from app01.utils.Jumpage import jump
from app01.utils.forms import AdminForm
def admin_list(request):
search={}
value=request.GET.get('q',"")
if value:
search['username__contains']=value
data=models.Admin.objects.filter(**search)
Jumpage=jump(request,data)
content={
"info":Jumpage.queryset,
"change":Jumpage.HTMl(),
"search":value,
}
return render(request,'admin_list.html',content)

添加管理员

添加管理员需要以下几个步骤:

我们还会发现,不管是我们的user,pretty还是admin的添加界面,用的都是同一个模板,所以为了避免文件过多,我们就添加一个公用模板add.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{% extends 'layout.html' %}

{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ title }}</h3>
</div>
<div class="panel-body">
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label >{{ field.label }}</label>
{{field}}
<span style="color: red;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
{% endblock %}

函数就长这样:跟我们之前说好的用法一样

1
2
3
4
5
6
7
8
9
10
def admin_add(request):
if request.method == 'GET':
form = AdminForm()
return render(request, 'add.html', {"form": form,"title":"新建管理员"})
form = AdminForm(data=request.POST)
if form.is_valid():
form.save()
return redirect('/admin/list/')
else:
return render(request, 'add.html', {"form": form,"title":"新建管理员"})

效果如下:
image.png
确认密码
新建密码应该还要有个确认密码才标准对吧,然后密码输入的时候不能被看见输入了什么东西,所以我们来创建!
image.png
只需要在类里添加个字段即可,然后设置不可见就用插件widgets
image.pngimage.png
添加一下字段即可,最终效果:
image.png
接下来添加验证2次输入是否一致:

1
2
3
4
5
6
def clean_confirm_passwd(self): #添加Hook钩子函数验证
pwd=self.cleaned_data['password']
password=self.cleaned_data['confirm_passwd']
if pwd==password:
return pwd
raise ValidationError('两次输入的密码不一致!')

image.png

最后为了安全性,我们一般对输入的密码进行加盐md5加密!,现在让我们来实现
首页的clean_xxx的意思都知道,是个HOOK函数,对指定输入的参数检验的,利用这个函数来进行加密和判断

1
2
3
4
5
6
7
from django.conf import settings  #来拿secret_key当盐值
import hashlib
def md5(data):
str=data+settings.SECRET_KEY
md5=hashlib.md5(str.encode('utf-8')).hexdigest()
return md5

这就是我们的md5加密,hashlib.md5里必须放一个字节型的,所以要encode
里面的SECRET_KEY是django自动生成的:
image.png
接下来就是修改函数了

1
2
3
4
5
6
7
8
9
10
11
from app01.utils.md5 import md5
def clean_confirm_passwd(self): #添加Hook钩子函数验证
pwd=self.cleaned_data['password']
print(self.cleaned_data)
password=self.cleaned_data['confirm_passwd']
if pwd!=md5(password):
raise ValidationError('两次输入的密码不一致!')

def clean_password(self):
pwd=self.cleaned_data['password']
return md5(pwd)

image.png
在数据库看看:
image.png
成功加盐加密!告一段落

编辑和删除和重置密码管理员

这个就照葫芦画瓢就好了,我们之前在新建靓号的时候已经做过很多次了,要求大致如下

先来编辑一下管理员吧:

接下来设置一下删除界面,这就很简单了:

1
2
3
def admin_del(request,nid):
models.Admin.objects.filter(id=nid).delete()
return redirect('/admin/list/')

最后就是重置一下密码了,这也是比较有难度的

Cookie和Session

老生常谈
image.png

用户认证-基本实现方式

先添加路由,页面和函数,内容如下:

1
2
3
4
5
6
7
class LoginForm(forms.Form):
username=forms.CharField(label='用户名',widget=forms.TextInput(attrs={"class":"form-control"}))
password=forms.CharField(label='密码',widget=forms.PasswordInput(attrs={"class":"form-control"}))

def login(request):
form=LoginForm()
return render(request,'login.html',{"form":form})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{% 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>
<input type="submit" value="登 录" class="btn btn-primary">
</form>
</div>

</body>
</html>

image.png
加入我们字段很多的话,我们也是可以直接把BootstrapModelForm类的init方法拿过来的,即使一个是form一个是modelform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LoginForm(forms.Form):
username=forms.CharField(label='用户名',widget=forms.TextInput)
password=forms.CharField(label='密码',widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 循环ModelForm中的所有字段,给每个字段的插件设置
for name, field in self.fields.items():
# 字段中有属性,保留原来的属性,没有属性,才增加。
if field.widget.attrs:
field.widget.attrs["class"] = "form-control"
field.widget.attrs["placeholder"] = field.label
else:
field.widget.attrs = {
"class": "form-control",
"placeholder": field.label}
def login(request):
form=LoginForm()
return render(request,'login.html',{"form":form})

image.png
都是可以滴~
然后关于BootStrapModelForm类,我们可以进一步简化,由于我们这次用的是forms,所以用不了ModelForm,那我们再创建一个Form用的就好了:
image.png
然后init重复了,我们再定义一个类来放init,再让我们BoostarpModelForm和BootstapForm都继承这个类就好:
image.png
们的页面仍然是一样的

设置错误信息:
image.png
image.png

为什么会有必填呢,因为我们的LoginForm类定义的字段里其实默认有个require参数,他默认为True:
image.png

接下来就来验证输入的密码是否和输入的密码一样:
image.png
image.png
接下来就是添加session了
只要写上一句request.session['info']=xxx,django就会处理:
image.png
首先返回客户端一个session,然后再服务端储存这个session的值为info=xxxx

1
2
request.session['info']= {"id":row_obj.id,"name":row_obj.username}#存储了用户名,id到session中
return redirect('/admin/list/')

image.png
然后服务端的session数据存到数据库里了:
image.png
expire_date表示到期时间
现在是如果有些没有登录,就需要设置权限了:
这时候要在adminlist页面判断session了,下一P就讲中间件来处理这个

用户认证-中间件处理权限

可以再django内部检查用户有没有登录

1
2
info=request.session['info']
print(info)

image.png
image.png
如果我们已经登录了,会返回sessionid,然后后台会获得数据
如果没有:
image.png
这就是个雷区了,假如我们info=request.session['info'],假如没有session是不会返回none的,所以要改成info=request.session.get('info'),这样就不会报错了

我们发现还有一个问题,就是并没有进行鉴权,一般来说我们进行鉴权的流程如下:

1
2
3
info = request.session['info']
if not info:
return redirect('/login/')

这样没登录的用户就会跳转到登录界面了,我们的设计理念就是假如没有登录,不能使用所有功能,一律跳转到登录界面,如果用上述方法鉴权,那要复制粘贴20多份,一个函数一份,就很麻烦,对于这个问题,django给出了答案,那就是中间件了,在django中,中间件就是一个类
image.png
我们先创建一个MiddleWare文件夹来存放中间件类,创建auth.py文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
def process_request(self,request):
print('M1 in')
def process_response(self,request,response):
print('M1 Out')
return response
#如果方法没有返回值就返回none

class M2(MiddlewareMixin):
def process_request(self, request):
print('M2 in')

def process_response(self, request,response):
print('M2 Out')
return response

和app,数据库一样,中间件也是要去注册的:
image.png
这样的话我们随便访问一个界面:
image.png
流程就是上面的图片所对应的,这样我们就对中间件有个比较好的认知了,我们删掉一个,目前一个就够了
接下来就写个鉴权的中间件:

1
2
3
4
5
6
7
8
9
10
11
12
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class Auth(MiddlewareMixin):
def process_request(self,request):
info=request.session.get('info')
if info:
return
return redirect('/login/')
def process_response(self,request,response):
return response
#如果方法没有返回值就返回none

但是这里会有个小BUG:
在未登入的状态下访问一个页面:
image.png
会无限重定向,仔细想想也是,因为既然没登录,每次重定向都又要再次经过中间件,我们重定向的模式图:

是第二种方式,会一直重复去递归,为了解决这个问题,就得去写个白名单,也就是不需要登录就可以访问的界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class Auth(MiddlewareMixin):
def process_request(self,request): #固定函数名
#排除不需要登录就能访问的页面,防止无限302
#request.path_info获取当前url
if request.path_info=='/login/':
return
info=request.session.get('info')
if info:
return
return redirect('/login/')#有bug
def process_response(self,request,response):#固定函数名
return response #必须有response否则报错
#如果方法没有返回值就返回none

告一段落

账号注销

其实很简单,就是一个指令request.sesssion.clear()就可以清除当前的session,和之前的删除用户是一个原理:

1
2
3
def logout(request):
request.session.clear()
return redirect('/login/')

image.pngimage.png
还有一个小地方,就是我们登录后要显示用户名,这下我们一直没用着的request起作用了
image.png
image.png

图片验证码(显示)

1
2
3
4
5
6
7
8
9
10
11
12
<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="{%static 'img/code.jpg' "%}" style="width: 125px;">
</div>
</div>
</div>

先加一下验证码的HTML代码:再把验证码图片拿过来大致就有个效果:
image.png
但是这样肯定是不可以的,我们的验证码应该是动态的,这时候就得用找pillow模块去生成验证码了,pip insatll pillow即可安装该模块,然后该模块的一些用法:
https://www.cnblogs.com/xuyaping/p/7155088.html

1
2
3
4
5
6
7
from PIL import Image

img = Image.new(mode='RGB', size=(120, 30), color=(255, 255, 255))

# 保存在本地
with open('code.png', 'wb') as f:
img.save(f, format='png')

image.png
就创建了一个图片
接下来一些功能自己跟着博客学一下就好了,接下来直接把生成随机验证码的函数拿过来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from PIL import Image,ImageFont,ImageFilter,ImageDraw

import random


def check_code(width=120, height=30, char_length=5, font_file='Badaboom BB.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__':
# 1. 直接打开
img,code = check_code()
print(img)

# 2. 写入文件
# img,code = check_code()
# with open('code.png','wb') as f:
# img.save(f,format='png')

# 3. 写入内存(Python3)
# from io import BytesIO
# stream = BytesIO()
# img.save(stream, 'png')
# stream.getvalue()

# 4. 写入内存(Python2)
# import StringIO
# stream = StringIO.StringIO()
# img.save(stream, 'png')
# stream.getvalue()

大致的意思挺好看懂的,之后我们来看看返回值给了些什么:
image.png
image.png
还挺好的是吧,接下来就是如何让他放在浏览器里了
接下来吧HTML中加载图片的地址改为一个URL,一个路由:
image.png
之后编写函数:
同样的,验证码也是我们不登录也能查看的,所以得改一下验证系统:
image.png
将之前的函数内容复制到:
image.png
字体要留在根目录要记得,坑区
定义一个函数:

1
2
3
4
5
6
def image_code(request):
img,code_str=check_code()
# 3. 写入内存(Python3)
stream = BytesIO()
img.save(stream, 'png')
return HttpResponse(stream.getvalue())

image.png
最后的效果就挺不错了

图片验证码(校验)

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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():
input_code = form.cleaned_data.pop('code')
# pop是为了提出code这个字段,因为数据库中没有code,要不然下文filter内就会报错
code = request.session.get('image_code', "")
if code.upper() != input_code.upper():
form.add_error('code', '验证码错误') # 手动添加错误信息
return render(request, 'login.html', {"form": form})
row_obj=models.Admin.objects.filter(**form.cleaned_data).first()
if not row_obj:
form.add_error('password','用户名或密码错误')#手动添加错误信息
return render(request,'login.html',{"form":form})
request.session['info']= {"id":row_obj.id,"name":row_obj.username}#存储了用户名,id到session中
request.session.set_expiry(60*60*24*7)#因为前面设置了60s过期,这里要重新设置,不然这个也会过期
return redirect('/admin/list/')
return render(request,'login.html',{"form":form})
1
2
3
4
5
6
7
8
9

def image_code(request):
img,code_str=check_code()
# 3. 写入内存(Python3)
stream = BytesIO()
img.save(stream, 'png')
request.session['image_code']=code_str
request.session.set_expiry(60)#设置session的过期时间为60s
return HttpResponse(stream.getvalue())

ajax

暂时只能到这了,这里有点就涉及盲区了,没学过前端啊

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#开发