1.初识Django
安装Django
pip install django
python37
-python.exe 【解释器】
-Scripts
-pip.exe
-django-admin.exe 【工具,创建django项目,创建django项目的文件和文件夹】
-Lib
-内置模块
-site-packages
-openpyxl
-python-docx
-flask
-django 【框架的源码】
2.创建项目
django项目会有一些默认的文件和默认的文件夹
2.1基于终端创建
- 打开终端
- 进入项目所在目录
- 执行命令创建项目:复制环境中django_admin.exe的地址,然后加上startproject 项目名称
(TestEnv) E:\TestFile\django_demo>"E:\TestEnv\Scripts\django-admin.exe" startproject my_django_demo
- 也可以把目录加入到环境变量当中,然后通过django_admin startproject 项目名称直接添加
(TestEnv) E:\TestFile\django_demo>django_admin startproject my_django_demo
2.2基于软件创建(Pycharm企业版)
2.3创建文件说明
标准模式:通过命令行创建
通过pycharm:额外添加两个东西(目前均删除)
-templates目录
-setting.py文件下的TEMPLATES列表中第一个元素(字典)有一个DIRS的value添加了一些数据
2.4文件含义
默认项目的文件介绍:
(TestEnv) E:\TestFile\django_demo\Django_test>dir /b /s
E:\TestFile\django_demo\Django_test\Django_test
E:\TestFile\django_demo\Django_test\manage.py "项目管理脚本,启动项目,创建app,数据管理,全部都要基于 这个文件(无需修改)"
E:\TestFile\django_demo\Django_test\Django_test\__init__.py
E:\TestFile\django_demo\Django_test\Django_test\asgi.py "接收本地网路请求,异步(无需修改)"
E:\TestFile\django_demo\Django_test\Django_test\wsgi.py "接收本地网路请求,同步(无需修改)"
E:\TestFile\django_demo\Django_test\Django_test\urls.py "url和函数的对应关系(经常修改)"
E:\TestFile\django_demo\Django_test\Django_test\settings.py "项目配置文件,链接数据库,注册app等(经常修改)"
3.APP
3.1APP创建
Django支持将大型项目拆分成多个小型APP
- 通过命令行在该目录下创建一个APP
(TestEnv) E:\TestFile\django_demo\Django_test>python manage.py startapp app01
3.2文件的含义
E:\TestFile\django_demo\Django_test\app01\__init__.py
E:\TestFile\django_demo\Django_test\app01\apps.py "apps启动类,固定文件,不用动"
E:\TestFile\django_demo\Django_test\app01\tests.py "写单元测试用到,一般不用动"
E:\TestFile\django_demo\Django_test\app01\admin.py "Django默认提供admin管理内容,一般也不用动"
E:\TestFile\django_demo\Django_test\app01\models.py "⭐对数据库进行操作"
E:\TestFile\django_demo\Django_test\app01\views.py "⭐urls.py文件中定义的函数具体内容写在views.py中"
E:\TestFile\django_demo\Django_test\app01\migrations "记录数据库字段变更记录,固定文件,不用动"
E:\TestFile\django_demo\Django_test\app01\migrations\__init__.py
4.快速上手
4.1创建页面
-
确保APP已注册
- 先创建一个APP
- 在这个app的目录文件中找到apps文件【apps.py】中找到
class App01Config(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'app01'
- 在django项目的setting文件【setting.py】中找到
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
- 添加一条关于目标app的路径
'app01.apps.App01Config' #'app01'或者'app01.apps.App01config'
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01config' ]
-
编写url和视图函数的对应关系【urls.py】
- 修改urls文件中默认的初始页面映射
urlpatterns = [ path('admin/', admin.site.urls), ]
urlpatterns = [ #path('admin/', admin.site.urls), #用户访问 www.xxxx.com/index/ --> 执行函数 path('index/', admin.site.urls), ]
- 添加app01的引用
from app01 import views
- 添加对views文件中对应函数的引用
urlpatterns = [ #path('admin/', admin.site.urls), #用户访问 www.xxxx.com/index/ --> 到app01的views.py文件中找到index函数并执行 path('index/', views.index), ]
-
添加对应的函数到app01下的views文件【views.py】
- 找到views文件
from django.shortcuts import render # Create your views here.
- 添加index函数
from django.shortcuts import render, HttpResponse#默认添加 # Create your views here. #添加index函数,默认要有一个request参数 def index(rquest): return HttpResponse("xxx")
-
xxxxxxxxxx @app.route('/login', methods=['GET', 'POST'])def login(): if request.method == "GET": return render_template('login.html') else: return 'login success'python
- 通过命令行启动
python manage.py runserver
- 通过pycharm启动,基于创建的django程序点击启动
- 通过VSCode启动: 安装Django插件 -> 打开运行和调试 -> 添加配置:Python:Django ->在launch.json文件中添加django项目的地址
{ // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Python: Django", "type": "python", "request": "launch", "program": "${workspaceFolder}\\django_demo\\Django_test\\manage.py",//在此处添加 "args": [ "runserver" ], "django": true, "justMyCode": true } ] }
注意:
当有多个app文件时,若要在urls中为函数配置路径,必须在import后面为每个app的views设置别称,否则会重名报错
from django.contrib import admin from django.urls import path from mainmenu import views as mainmenuviews from firstpage import views as firstpageviews urlpatterns = [ path('main/', mainmenuviews.main), path('main/firstpage/', firstpageviews.firstpage), ]
4.2templates模板
使用html文件的方式
def user_list(request):
return render(request, "user_list.html")
那么,Django所需要的html文件应该存储的位置是哪里?
--app目录文件下的templates目录文件,实际上,并非直接去views文件所在的app目录下,而是根据app的注册顺序逐一去每个app下寻找对应的templates文件,并且,如果初始创建时候setting文件设置了DIR中的一些内容(由Pycharm创建时候)
import os TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], #<--这里如果是这样,则优先到根目录下的templates文件中 寻找,如果找不到,再到app文件下的templates中寻找 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
总结:如果根目录下没有templates文件且项目文件夹下的setting.py文件中DIR的value是空,则app目录下的views中的函数在找模板时候会按照app注册顺序依次去每一个app目录下的templates中寻找
4.3静态文件
4.3.1创建static目录
开发过程中一般将图片,css文,js文件一般一起当作静态文件处理,静态文件放在app目录下的static目录中
- 在app目录下创建static文件夹
<img src="/static/img/xxx.png" alt=""> <!--这是一般方式引用静态文件-->
4.3.2使用Django提供的方式引用静态文件
Django的引用方式
在html文件头部
{%load static%}
引用文件的时候
{% static '文件相对地址'%}
一个使用Django的基本html样式:
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Django_Test</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
<div>
<input type="text" class="btn btn-primary" value="新建">
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
</script>
</body>
</html>
5.模板语法
本质上:在HTML中写一些占位符,由数据对这些占位符进行替换和处理。
列表
def user_add(request):
return HttpResponse("User_add")
def tpl(request):
name = "xxx"
roles = ['管理员', 'CEO', '保安']
user_info = {
"name":"Yu Nian",
"salary":10000,
"role":"CEO"
}
datalist = [
{"name":"Zhang He", "salary":10000, "role":"CFO"},
{"name":"Zhang Chunhua", "salary":12000, "role":"CTO"},
{"name":"Zhang Xincai", "salary":9000, "role":"CEO"}
]
return render(
request,
"tpl.html",
{
"n1":name,
"n2":roles,
"n3":user_info,
"n4":datalist
}
)
<div>{{ n1 }}</div> <!--xxx-->
<div>{{ n2 }}</div> <!--['管理员', 'CEO', '保安']-->
<div>{{ n2.0 }}</div> <!--管理员-->
<div>{{ n2.1 }}</div> <!--CEO-->
<div>{{ n2.2 }}</div> <!--保安-->
<div>
{% for item in n2%}
<span>{{ item }}</span>
{% endfor %}
</div> <!--管理员 CEO 保安-->
</hr>
<div>{{ n3 }}</div> <!--{'name': 'Yu Nian', 'salary': 10000, 'role': 'CEO'}-->
<div>{{ n3.name }}</div> <!--Yu Nian-->
<div>{{ n3.salary }}</div> <!--10000-->
<div>{{ n3.role }}</div> <!--CEO-->
<div>
{% for item in n3.items%}
<span>{{ item }}</span>
{% endfor %}
</div> <!--('name', 'Yu Nian') ('salary', 10000) ('role', 'CEO')-->
<div>
{% for item in n3.keys%}
<span>{{ item }}</span>
{% endfor %}
</div> <!--name salary role-->
<div>
{% for item in n3.values%}
<span>{{ item }}</span>
{% endfor %}
</div> <!--Yu Nian 10000 CEO-->
<div>
{% for k,v in n3.items%}
<span>{{ k }}{{ v }}</span>
{% endfor %}
</div> <!--nameYu Nian salary10000 roleCEO-->
<hr/>
<div>{{ n4.0 }}</div> <!--{'name': 'Zhang He', 'salary': 10000, 'role': 'CFO'}-->
<div>{{ n4.0.name }}</div> <!--Zhang He-->
<div>
{% for item in n4%}
<span>{{ item.name }}</span>
<span>{{ item.salary }}</span>
<span>{{ item.role }}</span>
{% endfor %}
</div> <!--Zhang He 10000 CFO Zhang Chunhua 12000 CTO Zhang Xincai 9000 CEO-->
<hr/>
<div>
{% if n1 == 'xxx' %}
<span>xxx<span>
{% else %}
<span>yyy</span>
{% endif %}
</div> <!--xxx-->
案例:消息中心
def news(request):
#定义一些新闻
#找到新闻页面
#利用第三方模块爬取:requests
import requests
res = requests.get("https://www.chinaunicom.com.cn/43/menu01/1/column05?pageNo=0&pageSize=10&year=2024&month=")
datalist = res.json()
return render(request, "news.html", {"newsList":datalist})
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Django_Test</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
<h1>NeWS Center</h1>
<ul>
{% for item in newsList %}
<li>{{item.news_title}} 时间:{{ item.post_time }}</li>
{% endfor %}
</ul>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
</script>
</body>
</html>
6.请求和响应
def something(request):
#request是一个对象,封装了用户通过浏览器发送过来请求的所有数据
#1.获取请求方式GET/POST
print(request.method)
#2.在URL上传递值 /something/?oid=937929511&bvid=BV1rT4y1v7uQ
print(request.GET)
#3.在请求体中提交数据
print(request.POST)
#4.HttpResponse("返回内容")字符串内容返回给请求者
#return HttpResponse("返回内容")
#5.读取HTML内容 + 渲染(替换) -> 渲染新的字符串
#return render(request, 'something.html', {"title":"来了"})
#6.【响应】浏览器重定向
return redirect("https://www.baidu.com")
浏览器重定向的过程:重定向的地址发送给浏览器,由浏览器发起对目标地址的访问
案例:用户登录
在输入密码的表单中要加上{% csrf_token %},否则会触发403forbidden
def login(request):
if request.method == 'GET' :
return render(request, "login.html")
username = request.POST.get("username")
password = request.POST.get("password")
# return HttpResponse("登录成功")
if username == "root" and password == "123":
#return HttpResponse("登录成功")
return redirect("https://www.baidu.com")
#return HttpResponse("登录失败")
return render(request, "login.html", {"login_fail":"账号或密码错误"})
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Django_Test</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
<h1>用户登录</h1>
<form method="POST" action="/login/">
{% csrf_token %}<!--带上随机字符串的返回,否则会触发403Forbidden-->
<input type="text" name="username" placeholder="用户名">
<input type="password" name="password" placeholder="密码">
<input type="submit" value="提交"><span style="color:red;">{{ login_fail }}</span>
</form>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
</script>
</body>
</html>
7.数据库操作
- MySQL + pymysql :有些繁琐
- Django开发操作数据库更为简单,内部提供ORM框架:介于代码与数据库操作插件之间,提供翻译
7.1安装第三方模块(底层交互插件)
pip install mysqlclient
注意:mysqlclient已不再支持3.8版本以下的python,请确保当前环境的python版本至少为3.8.0
若无法通过pip安装请转至mysqlclient官网找到对应的pp3x版本下载
7.2ORM
ORM作用:
- 创建,修改,删除数据库中的表,且无需使用SQL语句(无法创建数据库)
- 操作表中的数据,无需使用SQL语句
1.创建数据库
- 启动MySQL
- 创建数据库
2.Django连接数据库
在setting.py文件中进行配置和修改
DATABASES = {
'default':{
'ENGINE':'django.db.backends.mysql', #使用django连接mysql,如此还可以连接orcale, postgresql等数据库
'NAME':'dbname', #数据库的名字
'USER':'root', #用户名字
'PASSWORD':'xxx', #密码
'HOST':'', #数据库安装的服务器,本机即localhost或者127.0.0.1
'PORT':'', #my.ini -- 3306
}
}
DATABASES = {
'default':{
'ENGINE':'django.db.backends.mysql',
'NAME':'testdb',
'USER':'root',
'PASSWORD':'123',
'HOST':'127.0.0.1',
'PORT':'3306',
}
}
3.基于Django操作数据表
- 创建表
- 删除表
- 修改表
创建表:基于models.py文件
from django.db import models
# Create your models here.
class UserInfo(models.Model):
name = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField()
"""
create table app01_user_info(
id bigint auto_increment primary key,
name varchar(32),
password varchar(64),
age int
)
"""
执行命令:
python3.x manage.py makemigrations
python3.x manage.py migrate
注意,需要当前app已经注册,并mysql版本应该至少为8.0,否则请关闭版本检测E:\MyEnv\Devenv312\Lib\site-packages\django\db\backends\base\base.py中line239的self.check...注释掉
- 添加表
class Department(models.Model):
title = models.CharField(max_length=16)
class Role(models.Model):
caption = models.CharField(max_length=16)
- 删除表(注释掉即可)
#class Role(models.Model):
# caption = models.CharField(max_length=16)
- 删除字段(注释掉即可)
class UserInfo(models.Model):
name = models.CharField(max_length=32)
password = models.CharField(max_length=64)
# age = models.IntegerField()
- 新建字段(要提供default设置默认值,因为表中可能有一些数据,或者将该字段设为允许空字段)
class UserInfo(models.Model):
name = models.CharField(max_length=32)
password = models.CharField(max_length=64)
# age = models.IntegerField()
size = models.CharField(max_length=16, default='xxl')
size_num = models.IntegerField(default=32)
#也可以允许为空
data = models.IntegerField(null=True, blank=True)
- 增加数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):
#测试ORM
Department.objects.create(title = "销售部")
Department.objects.create(title = "运营部")
Department.objects.create(title = "开发部")
Department.objects.create(title = "广告部")
return HttpResponse("Success")
- 删除数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):
UserInfo.objects.filter(id=2).delete() #filter是过滤出要删除的数据
#UserInfo.objects.all().delete() 删除所有数据
return HttpResponse("Success")
- 筛选(获取)数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):
queryset = UserInfo.objects.all()
#获取所有数据,返回一个queryset类型 [行, 行, 行, 行 ...],每一行是一个对象,封装了对应的属性
for obj in queryset:
print(obj.id, obj.name, obj.size)# 1 Zhang He xxl
row_obj = UserInfo.objects.filter(id=1).first() #first取对应queryset对象中的第一个元素,即第一行
print(row_obj.id, row_obj.name, row_obj.size)
return HttpResponse("Success")
- 更新数据
#from app01 import models 然后使用models.Department.objects.create("...")
from app01.models import Department, UserInfo #可以直接使用Department.objects.create("...")
def orm(request):
UserInfo.objects.filter(id=1).update(name = "Zhang Chunhua")#指定修改
UserInfo.object.all().update(password='111')#全部修改
return HttpResponse("Success")
案例:用户管理
1.展示用户列表
- url
- 函数
- 获取用户所有信息
- HTML渲染
def info_list(request):
data_list = UserInfo.objects.all()
return render(request, "info_list.html",{"datalist":data_list})
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Django_Test</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
<h1>用户列表</h1>
<table border="1">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>password</th>
<th>size</th>
</tr>
</thead>
<tbody>
{% for item in datalist %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.password }}</td>
<td>{{ item.size }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/info/add/">返回</a>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
</script>
</body>
</html>
2.添加用户
- url
- 函数
- GET看到页面,输入内容
- POST,提交,写入到数据库
def info_add(request):
if request.method == 'GET':
return render(request, "info_add.html")
# 获取用户提交的数据
user = request.POST.get("user")
psw = request.POST.get("psw")
size = request.POST.get("size")
#添加到数据库
UserInfo.objects.create(name=user, password=psw, size=size)
# return HttpResponse("添加成功")
#自动跳转
return redirect("/info/list/")
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Django_Test</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
</head>
<body>
<h1>用户添加</h1>
<form method="post" action="/info/add/"> <!--如果表单提交的地址与当前页面一致,则可以省略action="/info/add/"-->
{% csrf_token %}
<input type="text" name="user" placeholder="用户名">
<input type="password" name="psw" placeholder="密码">
<div>
大小:
<input type="checkbox" name="size" value="l">l
<input type="checkbox" name="size" value="xl">xl
<input type="checkbox" name="size" value="s">s
<input type="checkbox" name="size" value="m">m
</div>
<input type="submit" value="提交">
</form>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
</script>
</body>
</html>
3.删除用户
http://127.0.0.1:8000/info/delete/?nid=1
def 函数():
nid = request.GET.get("nid")
UserInfo.objects.filter(id=nid).delete()
return HttpResponse("删除成功")
通过键入列表行所在对象的id实现操作表格来删除对应的数据
def info_delete(request):
nid = request.GET.get("nid")
UserInfo.objects.filter(id=nid).delete()
return redirect("/info/list/")
<tbody>
{% for item in datalist %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.password }}</td>
<td>{{ item.size }}</td>
<td><a href="/info/delete/?nid={{ item.id }}">删除</a></td><!--拼凑get来获取对应的数据-->
</tr>
{% endfor %}
</tbody>
Django开发:员工管理系统
1.新建项目
检查setting.py文件中的DIR的内容,默认为空
(Devenv312) E:\TestFile\project6-Django>"E:\MyEnv\Devenv312\Scripts\django-admin.exe" startproject System_Manage_User
'DIRS': [],
2.创建&注册APP
- 创建app
命令行:
(Devenv312) E:\TestFile\project6-Django\System_Manage_User>python manage.py startapp app01
Pycharm
输入:startapp app01即可创建app
- 注册app
'app01.apps.App01Config'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config'
]
3.设计表结构
mdoels.py中添加表
from django.db import models
# Create your models here.
class Department(models.Model):
#部门表
id = models.BigAutoField(verbose_name='ID', primary_key=True) #类型为BigInt,默认自增的id初值为1,则可写可不写
title = models.CharField(verbose_name='标题', max_length=32)
class User_Info(models.Model):
#员工表
name = models.CharField(verbose_name='姓名', max_length=16)
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)
create_time = models.DateTimeField(verbose_name='入职时间', )
"""
1.有约束,使用外键,这样此处只能填入部门表中已存在的ID
- to :表示与对应的表关联
- to_field :表示与对应的属性关联
2.django自动
- 写的depart
- 实际表中生成的属性为depart_id
3.部门表中的数据被删除 -
-级联删除
depart = models.ForeignKey(to='Department', to_field='id', on_delete = models.CASCADE)
-修改对应外键的属性值为空
depart = models.ForeignKey(to='Department', to_field='id', null=True, blank=True, on_delete=models.SET_NULL)
"""
depart = models.ForeignKey(to='Department', to_field='id', on_delete=models.CASCADE)
4.在MySQL中生成表
- 通过工具连接到MySQL,生成数据库
create database emplo_manage_system DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
- 修改配置文件,连接MySQL
DATABASES = {
'default':{
'ENGINE':'django.db.backends.mysql',
'NAME':'emplo_manage_system',
'USER':'root',
'PASSWORD':'123',
'HOST':'127.0.0.1',
'PORT':'3306',
}
}
- Django命令生成数据表
python manage.py makemigrations
python manage.py migrate
- Pycharm生成数据表
然后依次输入makemigrations, 回车, migrate, 回车
5.静态文件的管理
static, templates放入app01目录下面
6.部门管理
Django中提供了Form和ModelForm的组件,可以更为方便的制作
6.1部门列表
def depart_list(request):
"""部门列表"""
department_list = Department.objects.all()
return render(request, 'depart_list.html', {'department_list':department_list})
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Manage System</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
<style>
.navbar{
border-radius:0;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-9" 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-9">
<ul class="nav navbar-nav">
<li><a href="#">主页</a></li>
<li class="active"><a href="#">部门管理</a></li>
<li><a href="#">用户管理</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">切换用户</a></li>
<li><a href="#">登出</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">张郃<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人资料</a></li>
<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 style="margin-bottom:10px;">
<a class="btn btn-primary" href="#" >新建部门</a>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-list-ul"></i>部门列表</h3>
</div>
<table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
<thead>
<tr>
<th>ID</th>
<th>部门名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in department_list %}
<tr>
<td class="col-xs-3">{{ item.id }}</td>
<td class="col-xs-5">{{ item.title }}</td>
<td>
<a class="btn btn-primary btn-xs" href="#">
<i class="fa fa-edit" aria-hidden="true"></i> 编辑
</a>
<span> </span>
<a class="btn btn-danger btn-xs" href="#">
<i class="fa fa-trash" aria-hidden="true"></i> 删除
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}">
</script>
</body>
</html>
6.2添加部门
添加页面
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Add Department</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
<style>
.navbar{
border-radius:0;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse">...</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 action="/depart/add/" method="post">
{% csrf_token %}
<div class="form-group">
<label>标题</label>
<input type="text" class="form-control" placeholder="标题" name="title">
</div>
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
<span style="color:red;">{{ error_type }}</span>
</form>
</div>
</div>
</div>
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
</script>
</body>
</html>
添加功能
def depart_add(request):
"""添加部门"""
if request.method == 'GET':
return render(request, "depart_add.html")
if request.POST.get('title') == "":
error = "标题不可以为空"
return render(request, "depart_add.html",{'error_type':error})
title = request.POST.get('title')
models.Department.objects.create(title=title)
return redirect("/depart/list/")
6.3删除部门
删除功能
def depart_del(request):
"""删除部门"""
tid = request.GET.get("tid")
models.Department.objects.filter(id=tid).delete()
return redirect("/depart/list/")
<a class="btn btn-danger btn-xs" href="/depart/del/?tid={{ item.id }}">
<i class="fa fa-trash" aria-hidden="true"></i> 删除
</a>
6.4编辑部门
一种新的页面向后端传参的方式
urls.py
path('depart/<int:tid>/edit/', views.depart_edit),
#<int:tid>是类似正则一样,tid会以参数的方式传递到函数中,访问该页面地址会类似 127.0.0.1:8000/depart/3/edit
#也可以改成类似depart/tid=<int:tid>/edit/等等类似形式
views.py
def depart_edit(request, tid):
#在此处加入了新的参数,如果当前的页面地址为127.0.0.1:8000/depart/3/edit,则tid会直接等于3
"""修改部门"""
if request.method == 'GET':
#根据tid获取对应的数据
depart_row = models.Department.objects.filter(id=tid).first()
return render(request, "depart_edit.html", {'depart':depart_row})
update_title = request.POST.get('title')
if update_title == "":
error = "新的标题不可以为空"
return render(request, "depart_edit.html", {'depart':depart_row, 'error_type':error})
models.Department.objects.filter(id=tid).update(title=update_title)
return redirect("/depart/list/")
编辑按钮
<a class="btn btn-primary btn-xs" href="/depart/{{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑
</a>
编辑页面
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Department Edit</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
<style>
.navbar{
border-radius:0;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse">...</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 method="post"><!--没有action默认提交到本页面-->
{% csrf_token %}
<div class="form-group">
<label>标题</label>
<input type="text" class="form-control" value="{{ depart.title }}" name="title"> <!--value相当于默认值-->
</div>
<button type="submit" class="btn btn-primary">
<i class="fa fa-save" aria-hidden="true"></i> 保 存
</button>
<span style="color:red;">{{ error_type }}</span>
</form>
</div>
</div>
</div>
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
</script>
</body>
</html>
7.模板的继承
Django支持模板的继承,通过使用{%%}可以创建一些模板供对应的html文件使用
通过block content创建一个模板
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %} {% endblock %}</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
<style>
.navbar{
border-radius:0;
}
{% block css %}{% endblock %}
</style>
</head>
<body>
<nav class="navbar navbar-inverse">...</nav>
<div>
{% block content %}{% endblock %}
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
{% block js %}{% endblock %}
</script>
</body>
</html>
在部门列表中使用这个模板
{% extends "layout.html" %}
{% block title %}
部门列表
{% endblock %}
{% block content %}
<div class="container">
<div style="margin-bottom:10px;">
<a class="btn btn-success" href="/depart/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建部门</a>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-list-ul"></i> 部门列表</h3>
</div>
<table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
<thead>
<tr>
<th>ID</th>
<th>部门名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in department_list %}
<tr>
<td class="col-xs-3">{{ item.id }}</td>
<td class="col-xs-5">{{ item.title }}</td>
<td>
<a class="btn btn-primary btn-xs" href="/depart/tid={{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
<span> </span>
<a class="btn btn-danger btn-xs" href="/depart/del/?tid={{ item.id }}"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
由此,通过block可以为一个html创建多个可填充部分:
{%load static%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %} {% endblock %}</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
{% block css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse">...</nav>
<div>
{% block content %}{% endblock %}
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
{% block js %}{% endblock %}
</body>
</html>
使用这个模板时候:
{% extends "layout.html" %}
{% block css %}
<style>
.navbar{
border-radius:0;
}
</style>
{% endblock %}
{% block title %}
xxx
{% endblock %}
{% block content %}
yyy
{% endblock %}
{% block js %}
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}">
{% block js %}{% endblock %}
</script>
{% endblock %}
8.用户的管理
8.1用户列表
def user_list(request):
"""用户列表"""
#获取所有的用户列表
user_list = models.User_Info.objects.all()
"""
for obj in user_list:
print(obj.name)
print(obj.age)
print(obj.create_time.strftime("%Y-%m-%d"))#通过strftime转换日期类型为字符串
# print(obj.gender)只会显示1或者2
print(obj.get_gender_display())# get_字段名_display()直接显示约束的对应值1-男, 2-女
# print(obj.depart_id)只会显示对应字段的值:1 2 3但不会显示外键所连接的表的title
#print(models.Department.objects.filter(id=obj.depart_id).first().title)太麻烦
print(obj.depart.title)
"""
return render(request, "user_list.html", {'user_list':user_list})
{% extends "layout.html" %}
{% block title %}
用户列表
{% endblock %}
{% block 用户管理 %}class="active"{% endblock %}
{% block content %}
<div class="container">
<div style="margin-bottom:10px;">
<a class="btn btn-success" href="#"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建用户</a>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-list-ul"></i> 部门列表</h3>
</div>
<table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>密码</th>
<th>年龄</th>
<th>账户余额</th>
<th>创建时间</th>
<th>性别</th>
<th>部门</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in user_list %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.password }}</td>
<td>{{ item.age }}</td>
<td>{{ item.account }}¥</td>
<td>{{ item.create_time|date:"Y-m-d" }}</td>
<!--
模板语法规则:
> 不允许出现(), 有()的地方会自动补上无需写
>括号有内容一般参考对应的模板函数,如strftime("%Y-%m-%d"), 模板语言形式为date:"Y-m-d H:i:s"
-->
<td>{{ item.get_gender_display }}</td>
<td>{{ item.depart.title }}</td>
<td>
<a class="btn btn-primary btn-xs" href="#"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
<span> </span>
<a class="btn btn-danger btn-xs" href="#"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!--panel-->
</div><!--container-->
{% endblock %}
8.2新建用户
原始思路(不会采用的原因:麻烦,难以提供错误数据,难以校验)
def user_add(request):
#提供展示页面所需要的信息
context = {
'depart_list':models.Department.objects.all(),
'gender_choices':models.User_Info.gender_choices,
'error_type':"",
}
if request.method =='GET':
return render(request, "user_add.html", context)
#获取用户提交的数据
querydata ={
'name' : request.POST.get('name'),
'password' : request.POST.get('password'),
'age' : request.POST.get('age'),
'account' : request.POST.get('account'),
'create_time' : request.POST.get('create_time'),
'gender' : request.POST.get('gender'),
'depart_id' : request.POST.get('depart_id'),
}
for i in querydata.values():
if i == "":
context['error_type'] = "用户名、密码、年龄和余额不可以为空"
return render(request, "user_add.html", context)
print(querydata)
#将数据添加到数据库
models.User_Info.objects.create(
name=querydata['name'],
password=querydata['password'],
age=querydata['age'],
account=querydata['account'],
create_time=querydata['create_time'],
gender=querydata['gender'],
depart_id=querydata['depart_id'],
)
return redirect("/user/list/")
{% extends "layout.html" %}
{% block title %}
添加用户
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">新建用户</h3>
</div>
<div class="panel-body">
<form action="/user/add/" method="post">
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
<input type="text" class="form-control" placeholder="用户名" name="name">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" class="form-control" placeholder="密码" name="password">
</div>
<div class="form-group">
<label>年龄</label>
<input type="text" class="form-control" placeholder="年龄" name="age">
</div>
<div class="form-group">
<label>账户余额</label>
<input type="text" class="form-control" placeholder="账户余额" name="account">
</div>
<div class="form-group">
<label>入职时间</label>
<input type="date" class="form-control" name="create_time">
</div>
<div class="form-group">
<label>性别</label>
{% comment %} <input type="radio" value="1" name="gender" style="cursor: pointer;"> 男
<input type="radio" value="2" name="gender" style="cursor: pointer;"> 女 {% endcomment %}
<select name="gender" class="form-control">
{% for item in gender_choices %}
<option value="{{ item.0 }}">{{ item.1 }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>部门</label>
<select name="depart_id" class="form-control">
{% for item in depart_list %}
<option value="{{ item.id }}">{{ item.title }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
<span style="color:red;">{{ error_type }}</span>
</form>
</div>
</div>
</div>
{% endblock %}
怎么办?使用Django提供的组件
-
Django组件
-
Form组件(简便)
-
ModelForm组件(超级简便)
-
1.Form组件
views.py(注意不是models.py)
class MyForm(Form):
name = forms.CharField(widget=forms.Input)
password = forms.CharField(widget=forms.Input)
age = forms.CharField(widget=forms.Input)
def info_show(request):
if reuqest.method == 'GET':
form = MyForm()
context = {
'form':form,
}
return render(request, 'info_show.html', context)
info_show.html
<form method='post'>
{% for field in form %}
{{ field }}
{% endfor %}
</form>
等价于
<form method='post'>
{{ form.name }}
{{ form.password }}
{{ form.age }}
</form>
<form method='post'>
<input type="text" name="name">
<input type="text" name="password">
<input type="text" name="age">
</form>
2.ModelFrom组件(推荐)
models.py
class Info(mdoels.Model):
name = models.CharField(verbose_name='姓名', max_length=16)
password = models.CharField(verbose_name='密码', max_length=64)
age = models.IntegerField(verbose_name='年龄')
views.py
class MyForm(ModelForm):
xx = form.CharField(widget=forms.Input)
class Meta:
model = Info
fields = ["name", "password", "age", "xx"]
def info_show(request):
if reuqest.method == 'GET':
form = MyForm()
context = {
'form':form,
}
return render(request, 'info_show.html', context)
info_show.html
<form method='post'>
{% for field in form %}
{{ field }}
{% endfor %}
</form>
3.新建用户(ModelForm版本)
models.py
class Department(models.Model):
#部门表
id = models.BigAutoField(verbose_name='ID', primary_key=True) #类型为BigInt,默认自增的id初值为1,则可写可不写
title = models.CharField(verbose_name='标题', max_length=32)
def __str__(self):
return self.title #当调用Department类的时候,返回其title属性的值
"""
class Obj():
#__init__:类实例初始化函数 定义类自身的属性:
def __init__(self, name):
self.name = name #其中name是外部初始化对象需要的参数,self.name是对象自身的属性
#__str__:类实例字符串化函数 调用类的时候返回的值:
def __str__(self):
return self.name #返回类自身的属性
#即,调用这个类需要向其传递一个name的属性值,然后输出类的结果是类的name属性值
obj = Obj('ABC')
print(obj)
>>>ABC
"""
depart在UserInfo表中是外键,自动生成input时候调用Department得到的是object:Department,因此需要设置此类的返回值,这样才能直接生成对应的选择框
views.py
#+---------------------------------------------------------------------------------------------------+#
from django import forms
class UserModelForm(forms.ModelForm):
name = forms.CharField(min_length=3, label="用户名") #设定一个最小长度用于校验值合法
# password = forms.CharField(validators=正则表达式, label="密码")
class Meta:
model = models.User_Info
fields = ['name', 'password', 'age', 'account', 'create_time', 'age', 'gender', 'depart']
#可以通过fields = "__all__"
#depart默认返回的是
#通过widgets为某些特定的类型生成指定的输入框模式(不推荐)
widgets = {
'password':forms.PasswordInput(),
'account':forms.TextInput(),
'create_time':forms.DateTimeInput(),
}
#快速为所有的输入框添加样式
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# #重新部分input框的type --失效,为何?
# self.fields['password'].widget.attrs.update({'type': 'password'})
#循环找到所有的插件
# print(self.fields)
for name, field in self.fields.items():
# if name == 'password':
# field.widget.attrs = {"class":"form-control"}
# continue
field.widget.attrs = {"class":"form-control", "placeholder":field.label}
def user_add(request):
"""添加用户--ModelForm版"""
if request.method == 'GET':
form = UserModelForm()
return render(request, "user_add.html", {'form':form})
#用户POST提交数据,对数据进行空值校验
form = UserModelForm(data=request.POST) #取数据
if form.is_valid():#form.is_vaild()可以检验所有的输入值,如果均合法则返回True
"""form.cleaned_data是对通过校验的值的形式,保存为字典格式
print(form.cleaned_data)
#>>>{'name': '3123', 'password': '312323', 'age': 32,
# 'account': Decimal('3123'),
#'create_time': datetime.datetime(2022, 11, 2, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC')),
#'gender': 1, 'depart': <Department: IT部门>}
"""
form.save() #自动保存在数据库 数据库连接的来源:<model = models.User_Info>
return redirect("/user/list/")
else:
# print(form.errors) form.errors包含了页面返回的全部的错误信息
return render(request, "user_add.html", {'form':form})
为何会失效?
self.fields['password'].widget.attrs.update({'type': 'password'})
校验产生的错误会直接包含在form中每一项中,因此错误返回直接为return render(request, "user_add.html", {'form':form})即可
user_add.html
{% extends "layout.html" %}
{% block title %}
添加用户
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">新建用户</h3>
</div>
<div class="panel-body">
<form action="/user/add/" method="post" novalidate>
{% comment %}novalidate 关闭浏览器自带的校验{% endcomment %}
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label>{{ field.label }}</label>
{{ field }}
{% comment %}此处的label即为定义属性时候的verbose_name值{% endcomment %}
<span style="color:red;">{{ field.errors.0 }}</span>
{% comment %}errors.0即为返回的错误信息{% endcomment %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">
<i class="fa fa-save" aria-hidden="true"></i> 保 存
</button>
</form>
</div>
</div>
</div>
{% endblock %}
错误报告为英文
打开setting.py, 注释掉英文模式,添加如下内容即可
# LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'zh-hans'
8.3编辑用户
-
点击编辑,跳转到编辑页面(将编辑行的ID携带过去)
-
编辑页面(默认数据,根据ID获取并设置到)
-
提交:
- 错误提示
- 数据校验
- 在数据库更新
models.UserInfo.filter(id=4).update(...)
urls.py
path('user/uid=<int:uid>/edit/', views.user_edit),
views.py
def user_edit(request, uid):
"""编辑用户"""
if request.method == 'GET':
#根据ID去数据库获取要编辑的那一行数据
row_object = models.User_Info.objects.filter(id=uid).first()
form = UserModelForm(instance=row_object) #instance标记原来的对象,此时保留原本的值为value
return render(request, 'user_edit.html', {'form':form})
row_object = models.User_Info.objects.filter(id=uid).first()
form = UserModelForm(data=request.POST, instance=row_object)
#将页面返回的数据传递给form用于校验和保存
#当instance为一个Object对象时候,标记为更新的位置,.save更新到该对象中,没有时.save默认新建数据
if form.is_valid():#校验通过
#默认保存的是用户输入的所有数据,如果想要在用户输入以外增加值 form.instance.字段名 = 值 保存的就会是此处的值
form.save()
return redirect("/user/list/")
return render(request, "user_list.html", {'form':form})#校验未通过
user_edit.html
{% extends "layout.html" %}
{% block title %}
编辑用户
{% endblock %}
{% block 用户管理 %}class="active"{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">编辑用户</h3>
</div>
<div class="panel-body">
<form method="post" novalidate>{% comment %}novalidate 关闭浏览器自带的校验{% endcomment %}
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label>{{ field.label }}</label>
{{ field }} {% comment %}此处的label即为定义属性时候的verbose_name值{% endcomment %}
<span style="color:red;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">
<i class="fa fa-save" aria-hidden="true"></i> 保 存
</button>
</form>
</div>
</div>
</div>
{% endblock %}
8.4删除用户
views.py
def user_del(request, uid):
models.User_Info.objects.filter(id=uid).delete()
return redirect("/user/list/")
urls.py
path('user/uid=<int:uid>/del',views.user_del)
9.靓号管理
9.1创建靓号表
models.py
class Cool_Number(models.Model):
#靓号表
mobile = models.CharField(
verbose_name='移动号码',
validators=[
MinLengthValidator(11, message='最小长度为11'), # message参数是不符合验证器的返回值
MaxLengthValidator(11, message='最大长度为11')
],
unique=True,
)
price = models.DecimalField(verbose_name='价格', max_digits=4, decimal_place=2, default=0)
level_choice = (
(1, "普通"),
(2, "稀有"),
(3, "史诗"),
(4, "传说"),
(5, "传家宝"),
)
level = models.SmallIntegerField(verbose_name='等级', choices=level_choice, default=1)
status_choice = (
(0, "未占用"),
(1, "占用"),
)
status = models.SmallIntegerField(verbose_name='状态', choices=status_choice. default=0)
创建一些初始数据
insert into app01_cool_number(mobile, price, level, status) values
("16666666666", "99998", "5", "0" ),
("17766667777", "9997", "4", "1");
+----+-------------+-------+-------+--------+
| id | mobile | price | level | status |
+----+-------------+-------+-------+--------+
| 1 | 13733767633 | 998 | 3 | 0 |
| 2 | 17677676776 | 997 | 3 | 1 |
+----+-------------+-------+-------+--------+
9.2靓号列表
-
URL
-
函数
-
获取所有的靓号
-
通过render结合HTML将靓号展示出来
id 号码 价格 级别(中文) 状态(中文)
-
9.3新建靓号
-
列表跳转:
/num/add/
-
URL
-
ModelFrom类
from django import forms class NumModelForm(forms.ModelForm): ...
-
函数
- 实例化类对象
- 通过render将对象传入到HTML中
- 模板的循环展示所有的字段
-
点击提交
- 数据校验
- 保存到数据库
- 跳转回靓号列表
from django.core.validators import RegexValidator class NumModelForm(forms.ModelForm): #使用正则模块规定mobile字段的格式 方法一 mobile = forms.CharField( label='移动号码', validators=[RegexValidator(r'^1\d{10}$', '号码格式错误,必须是以1开头的11位数字'), ], ) class Meta: model = models.Cool_Number fields = '__all__' #所有字段 # exclude = ['level'] 去除某些字段 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs = {"class":"form-control", "placeholder":field.label} def num_add(request): """添加靓号""" if request.method == 'GET': form = NumModelForm() return render(request, "num_add.html", {'form':form}) form = NumModelForm(data=request.POST) if form.is_valid(): form.save() return redirect("/num/list/") return render(request, "num_add.html", {'form':form})
验证号码格式的另一种方法(钩子函数)
class NumModelForm(forms.ModelForm): ... #验证:方式2 钩子方法,这里的函数名必须是<clean_字段名>的格式 def clean_mobile(self): #cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典 input_mobile = self.cleaned_data['mobile'] if len(input_mobile) != 11 or input_mobile[0] !="1": #验证不通过 raise ValidationError("格式错误,移动号码应该是以1开头的11位数字") #验证通过,将用户的输入的值返回 return input_mobile
9.4编辑靓号
-
列表页面:
/num/nid=数字/edit
-
URL
-
函数
- 根据ID获取当前编辑的对象
- ModelForm配合,默认显示数据
- 提交修改
#移动号码字段在编辑中应该是不可以修改或者隐藏 class NumEditModelForm(forms.ModelForm): mobile = forms.CharField(disabled=True, label="手机号")#方法一,直接修改对应的属性来调整显示的效果为不可修改 class Meta: model = models.Cool_Number # fields =['price', 'level', 'status']#方法二,去掉对应字段不显示 fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs = {"class":"form-control", "placeholder":field.label} def num_edit(request, nid): """编辑靓号""" #进入编辑页面,获取原本的内容,填充字段 row_object = models.Cool_Number.objects.filter(id=nid).first() if request.method == 'GET': form = NumEditModelForm(instance=row_object) return render(request, "num_edit.html",{'form':form}) #获取编辑的内容并验证 form = NumEditModelForm(data=request.POST, instance=row_object)#data获取编辑后的数据,instance标记原本数据,保证结果是覆盖而非创建新的靓号 if form.is_valid(): form.save() return redirect("/num/list/") #验证未通过,返回错误信息,错误信息已包含在form中 return render(request, "num_edit.html",{'form':form})
不允许手机号重复
-
添加:【正则表达式】【手机号不能存在 警告内容:不允许添加已存在的移动号码】
#判断符合条件的内容是否存在 exits = models.Cool_Number.objects.filter(mobile="xxx xxxx xxxx").exits() #exits #>>> True/False
class NumModelForm(forms.ModelForm): ... def clean_mobile(self): input_mobile = self.cleaned_data['mobile'] #cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典 exits = models.Cool_Number.objects.filter(mobile=input_mobile).exists() if exits: raise ValidationError("不允许添加已存在的移动号码") if len(input_mobile) != 11 or input_mobile[0] !="1": #验证不通过 raise ValidationError("格式错误,移动号码应该是以1开头的11位数字") #验证通过,将用户的输入的值返回 return input_mobile
-
编辑:【正则表达式】【若要编辑移动号码,除了本号码以外的移动号码不能存在相同的】
排除自己以外,其他数据是否存在重复的号码 mdoels.Cool_Num.objects.filter(mobile="xxx xxxx xxxx").exclude(id=2)
class NumEditModelForm(forms.ModelForm): ... def clean_mobile(self): # 当前编辑的哪一行ID # print(self.instacne.pk) input_mobile = self.cleaned_data['mobile'] #cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典 exits = models.Cool_Number.objects.exclude(id=self.instacne.pk).filter(mobile=input_mobile).exists() if exits: raise ValidationError("不允许添加已存在的移动号码") if len(input_mobile) != 11 or input_mobile[0] !="1": #验证不通过 raise ValidationError("格式错误,移动号码应该是以1开头的11位数字") #验证通过,将用户的输入的值返回 return input_mobile
复杂的校验在ModelForm的钩子函数中实现
9.5搜索手机号
models.Cool_Num.objects.filter(mobile=xxx xxxxx xxxx, id=yy)
data_dict = {"mobile":"xxx xxxx xxxx", "id":123}
models.Cool_Num.objects.filter(**data_dict)
#选择id大于yy
models.Cool_Num.objects.filter(id__gt=yy)
#选择id大于等于yy
models.Cool_Num.objects.filter(id__gte=yy)
#选择id小于yy
models.Cool_Num.objects.filter(id__lt=yy)
#选择id小于等于yy
models.Cool_Num.objects.filter(id__lte=yy)
#选择mobile以zzz开头
models.Cool_Num.objects.filter(mobile__startwith=zzz)
#选择mobile包含zzz
models.Cool_Num.objects.filter(mobile__contains=zzz)
#选择mobile以zzz结尾
models.Cool_Num.objects.filter(mobile__endwith=zzz)
views.py
def num_list(request):
data_dict={}
value = request.GET.get('query_info')
status = request.GET.get('status')
if value:
data_dict['mobile__contains'] = value
if status:
data_dict['status'] = status
num_list = models.Cool_Number.objects.filter(**data_dict)
return render(request, "num_list.html", {'num_list':num_list, 'search_data':value, 'status':status})
if status and not value:
data_dict['status'] = status
num_list = models.Cool_Number.objects.filter(**data_dict)
return render(request, "num_list.html", {'num_list':num_list, 'status':status})
num_list = models.Cool_Number.objects.all().order_by("-level")# select * from 表 order by level desc
return render(request, "num_list.html", {'num_list':num_list})
num_list.html(搜索框)
<div style="float:right;width:400px">
<form method='get'>
<div class="input-group">
<input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
<span class="input-group-btn">
<button type="submit" class="btn btn-default" >
<i class="fa fa-search" aria-hidden="true"></i> 搜索
</button>
</span>
<select name="status" class="form-control">
<option {% if status == None %}selected{% endif %} disabled>筛选</option>
<option value="0" {% if status == 0 %}selected{% endif %}>未占用</option>
<option value="1" {% if status == 1 %}selected{% endif %}>已占用</option>
<option value="">全部</option>
</select>
</div><!-- /input-group -->
</form>
</div>
9.6分页
import random
def generate_phone_number():
phone_number = '1' # 以1开头,符合电话号码的常见规则
for _ in range(10): # 生成后续10位数字
phone_number += str(random.randint(0, 9))
return phone_number
def num_list(request):
"""靓号列表"""
# 随机生成一些电话号码
for i in range(300):
number = generate_phone_number()
models.Cool_Number.objects.create(mobile=number, price=10, level=1, status=0)
#获取全部数据
queryset = models.Cool_Num.objects.all()
#获取前十条数据 左闭区间,右开区间
queryset = models.Cool_Num.objects.all()[0:10]
#获取符合条件的前十条数据
queryset = models.Cool_Num.objects.filter()[0:10 ]
那么获取的数据就可以分成页
queryset = models.Cool_Num.objects.all()[0:10] # 第一页
queryset = models.Cool_Num.objects.all()[10:20] # 第二页
queryset = models.Cool_Num.objects.all()[20:30] # 第三页
queryset = models.Cool_Num.objects.all()[30:40] # 第四页
获取数据库中符合条件的数据条数
data = models.Cool_Num.objects.all().count()
data = models.Cool_Num.objects.filter(id=1).count()
python内置函数divmod可以做到获取除法的商和余数
divmod(91, 10)
# >>> (9, 1) #(商, 余数)
1.直接写
views.py
def num_list(request):
"""靓号列表"""
# 随机生成一些电话号码
# for i in range(300):
# number = generate_phone_number()
# models.Cool_Number.objects.create(mobile=number, price=10, level=1, status=0)
#定义分页函数,返回一个包含当前页面显示内容和分页页码的html字符的字典
def DivPage(request, data_dict={}):
# 获取当前的页面,没有默认是1
page = int(request.GET.get('page', 1))
# 获取当前要显示的数据条数 [start: end]
pagesize = 10
start = (page-1)*pagesize
end = page*pagesize
# 获取全部数据
if data_dict == {}:
# select * from 表 order by level desc
num_list = models.Cool_Number.objects.all().order_by("-level")[start: end]
else:
num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level")[start: end]
# 获取数据总条数
total_count = models.Cool_Number.objects.filter(**data_dict).order_by("-level").count()
total_page, div = divmod(total_count, pagesize)
# 获取页码条数
if div:
total_page += 1
# 仅显示当前页的前5页和后5页
# 总页数小于等于11时候无需扩展页面
if total_page <= 11:
start_page = 1
end_page = total_page
else:
if page <= 6:
start_page = 1
end_page = 13
if page > 6 and page < total_page:
start_page = page - 5
end_page = page + 5
if page >= total_page - 5:
start_page = total_page - 12
end_page = total_page
# 生成全部的页码
page_str_list = []
if data_dict == {}:
#添加上一页
if page != 1 :
prev = '<li><a href="?page={}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format(page-1)
page_str_list.append(prev)
else:
prev = '<li class="disabled"><a href="?page=1" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'
page_str_list.append(prev)
# 让第一页常驻
ele1 = '<li><a href="?page=01">01</a></li>'
ele_total = '<li><a href="?page={}">{}</a></li>'.format(total_page, total_page)
ele_omit = '<li><a>...</a></li>'
if page > 6 and total_page > 11:
page_str_list.append(ele1)
page_str_list.append(ele_omit)
# 生成中间部分的页码
for i in range(start_page, end_page+1):
if i == page:
ele = '<li class="active"><a href="?page={}">{}</a></li>'.format(i, str(i).zfill(2))
else:
ele = '<li><a href="?page={}">{}</a></li>'.format(i, str(i).zfill(2))
page_str_list.append(ele)
# 让最后一页常驻
if page < total_page - 5 and total_page > 11:
page_str_list.append(ele_omit)
page_str_list.append(ele_total)
# 添加下一页
if page != total_page:
next = '<li><a href="?page={}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(page+1)
page_str_list.append(next)
else:
next = '<li class="disabled"><a href="?page={}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(total_page)
page_str_list.append(next)
search_string = """
<li>
<form style="float:left;margin-left:-1px" method="get">
<input type="text" name="page" style="position:relative;float:left;display:inline-block;width:80px;border-radius:0;" class="form-control" value={}>
<button style="border-radius:0" class="btn btn-default" type="submit">跳转</button>
</form>
</li>
""".format(page)
page_str_list.append(search_string)
page_string = mark_safe("".join(page_str_list))
return {'num_list':num_list, 'page_string':page_string}
data_dict={}
value = request.GET.get('query_info')
status = request.GET.get('status')
if value:
data_dict['mobile__contains'] = value
if status:
data_dict['status'] = status
reback_form = DivPage(request, data_dict)
num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
return render(request, "num_list.html", {'num_list':num_list, 'search_data':value, 'status':status})
if status and not value:
data_dict['status'] = status
num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
return render(request, "num_list.html", {'num_list':num_list, 'search_data':value, 'status':status})
reback_form = DivPage(request)
return render(request, "num_list.html", reback_form)
num_list.html
{% extends "layout.html" %}
{% block title %}
靓号管理
{% endblock %}
{% block css %}
.unoccupied {
background-color: #0066CC;
color: #FFFFFF;
}
{% endblock %}
{% block 靓号管理 %}class="active"{% endblock %}
{% block content %}
<div class="container">
<div style="margin-bottom:10px;" class="clearfix">
<a class="btn btn-success" href="/num/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建靓号</a>
<div style="float:right;width:400px">
<form method='get'>
<div class="input-group">
<input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
<span class="input-group-btn">
<button type="submit" class="btn btn-default" ><i class="fa fa-search" aria-hidden="true"></i> 搜索</button>
</span>
<select name="status" class="form-control">
<option {% if status == None %}selected{% endif %} disabled>筛选</option>
<option value="0" {% if status == 0 %}selected{% endif %}>未占用</option>
<option value="1" {% if status == 1 %}selected{% endif %}>已占用</option>
<option value="">全部</option>
</select>
</div><!-- /input-group -->
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-list-ul"></i> 靓号列表</h3>
</div>
<table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
<thead>
<tr>
<th>ID</th>
<th>移动号码</th>
<th>价格</th>
<th>等级</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in num_list %}
<tr>
<td style="width:100px;">{{ item.id }}</td>
<td>{{ item.mobile}}</td>
<td>{{ item.price }}</td>
<td>{{ item.get_level_display }}</td>
<td
{% if item.status == 0 %}
class="unoccupied"
{% endif %}
>{{ item.get_status_display }}</td>
<td>
<a class="btn btn-primary btn-xs" href="/num/nid={{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
<span> </span>
<a class="btn btn-danger btn-xs" href="/num/nid={{ item.id }}/del/"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!--panel-->
<ul class="pagination">
{{ page_string }}
</ul>
</div><!--container-->
{% endblock %}
2.将分页写成一个通用的类
在app01中创建utils目录,创建一个pagination.py文件
pagination.py
"""
自定义的分页组件
"""
from django.utils.safestring import mark_safe
import copy
class Pagination(object):
def __init__(
self,
request,
queryset,
page_size=10,
page_param="page", # page_param是页码的名称,默认为page
):
# 获取原本的包含get条件的url
origin_url = copy.deepcopy(request.GET)
origin_url._mutable = True
self.origin_url = origin_url
# 获取当前页
page = request.GET.get(page_param, "1")
if page.isdecimal():
page = int(page)
else:
page = 1
# 页码的名称
self.page_param = page_param
# 当前页码
self.page = page
# 页内容量
self.page_size = page_size
# 开始第start条数据
self.start = (page - 1) * page_size
# 结束第end条数据
self.end = page * page_size
# 获取页面内容
self.page_queryset = queryset[self.start : self.end]
# 计算总页数
total_page, div = divmod(queryset.count(), page_size)
if div:
total_page += 1
self.total_page_count = total_page
def html(self):
# 生成全部的页码
if self.total_page_count <= 11:
start_page = 1
end_page = self.total_page_count
else:
if self.page <= 6:
start_page = 1
end_page = 13
if self.page > 6 and self.page < self.total_page_count:
start_page = self.page - 5
end_page = self.page + 5
if self.page >= self.total_page_count - 5:
start_page = self.total_page_count - 12
end_page = self.total_page_count
# 设置用于添加html元素的列表
page_str_list = []
# 拼接含有页码的url self.origin_url.setlist(self.page_param, [需要跳转的页面])
# 添加上一页
if self.page != 1:
self.origin_url.setlist(self.page_param, [self.page - 1])
prev = '<li><a href="?{}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format(
self.origin_url.urlencode()
)
page_str_list.append(prev)
else:
self.origin_url.setlist(self.page_param, [1])
prev = '<li class="disabled"><a href="?{}" aria-label="Previous"><span aria-hidden="true">«</span></a></li>'.format(
self.origin_url.urlencode()
)
page_str_list.append(prev)
# 让第一页常驻
# 设置第一页标签
self.origin_url.setlist(self.page_param, [1])
ele1 = '<li><a href="?{}">01</a></li>'.format(self.origin_url.urlencode())
ele_omit = "<li><a>...</a></li>"
if self.page > 6 and self.total_page_count > 11:
page_str_list.append(ele1)
page_str_list.append(ele_omit)
# 生成中间部分的页码
for i in range(start_page, end_page + 1):
if i == self.page:
self.origin_url.setlist(self.page_param, [i])
ele = '<li class="active"><a href="?{}">{}</a></li>'.format(
self.origin_url.urlencode(), str(i).zfill(2)
)
else:
self.origin_url.setlist(self.page_param, [i])
ele = '<li><a href="?{}">{}</a></li>'.format(
self.origin_url.urlencode(), str(i).zfill(2)
)
page_str_list.append(ele)
# 让最后一页常驻
# 设置最后一页标签
self.origin_url.setlist(self.page_param, [self.total_page_count])
ele_total = '<li><a href="?{}">{}</a></li>'.format(
self.origin_url.urlencode(), self.total_page_count
)
if self.page < self.total_page_count - 5 and self.total_page_count > 11:
page_str_list.append(ele_omit)
page_str_list.append(ele_total)
# 添加下一页
if self.page != self.total_page_count:
self.origin_url.setlist(self.page_param, [self.page + 1])
next = '<li><a href="?{}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(
self.origin_url.urlencode()
)
else:
self.origin_url.setlist(self.page_param, [self.total_page_count])
next = '<li class="disabled"><a href="?{}" aria-label="Next"><span aria-hidden="true">»</span></a></li>'.format(
self.origin_url.urlencode()
)
page_str_list.append(next)
search_string = """
<li>
<form style="float:left;margin-left:-1px" method="get">
<input type="text" name="page" style="position:relative;float:left;display:inline-block;width:80px;border-radius:0;" class="form-control" value={}>
<button style="border-radius:0" class="btn btn-default" type="submit">跳转</button>
</form>
</li>
""".format(
self.page
)
page_str_list.append(search_string)
page_string = mark_safe("".join(page_str_list))
#返回一个被包装好的HTML标签,被调用时直接将这一部分添加到模板(<ul>{{ page_string }}</ul>)当中即可
return page_string
views.py
def num_list(request):
"""靓号列表"""
"""
# request.GET.urlencode()#原本的搜索条件,但是不可以修改
import copy
# 对其进行深拷贝
query_dict = copy.deepcopy(request.GET)
# 修改属性让其可以修改
query_dict._mutable = True
# 通过.setlist('key', ['value'])修改url
"""
data_dict = {}
value = request.GET.get("query_info")
status = request.GET.get("status")
if value:
data_dict["mobile__contains"] = value
if status:
data_dict["status"] = status
queryset = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
page_object = Pagination(request, queryset)
return render(
request,
"num_list.html",
{
"page": page_object.page,
"num_list": page_object.page_queryset,
"page_string": page_object.html(),
"search_data": value,
"status": status,
},
)
if status and not value:
data_dict["status"] = status
num_list = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
return render(
request,
"num_list.html",
{"num_list": num_list, "search_data": value, "status": status},
)
# 获取全部的数据
queryset = models.Cool_Number.objects.filter(**data_dict).order_by("-level")
page_object = Pagination(request, queryset)
return render(
request,
"num_list.html",
{"num_list": page_object.page_queryset, "page_string": page_object.html()},
num_list.py
{% extends "layout.html" %}
{% block title %}
靓号管理
{% endblock %}
{% block css %}
.unoccupied {
background-color: #0066CC;
color: #FFFFFF;
}
{% endblock %}
{% block 靓号管理 %}class="active"{% endblock %}
{% block content %}
<div class="container">
<div style="margin-bottom:10px;" class="clearfix">
<a class="btn btn-success" href="/num/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建靓号</a>
<div style="float:right;width:400px">
<form method='get'>
<div class="input-group">
<input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
<span class="input-group-btn">
<button type="submit" class="btn btn-default" ><i class="fa fa-search" aria-hidden="true"></i> 搜索</button>
</span>
<select name="status" class="form-control">
<option {% if status == None %}selected{% endif %} disabled>筛选</option>
<option value="0" {% if status == 0 %}selected{% endif %}>未占用</option>
<option value="1" {% if status == 1 %}selected{% endif %}>已占用</option>
<option value="">全部</option>
</select>
</div><!-- /input-group -->
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-list-ul"></i> 靓号列表</h3>
</div>
<table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
<thead>
<tr>
<th>ID</th>
<th>移动号码</th>
<th>价格</th>
<th>等级</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in num_list %}
<tr>
<td style="width:100px;">{{ item.id }}</td>
<td>{{ item.mobile}}</td>
<td>{{ item.price }}</td>
<td>{{ item.get_level_display }}</td>
<td
{% if item.status == 0 %}
class="unoccupied"
{% endif %}
>{{ item.get_status_display }}</td>
<td>
<a class="btn btn-primary btn-xs" href="/num/nid={{ item.id }}/edit/"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
<span> </span>
<a class="btn btn-danger btn-xs" href="/num/nid={{ item.id }}/del/"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div><!--panel-->
<ul class="pagination">
{{ page_string }}
</ul>
</div><!--container-->
{% endblock %}
10.时间插件
user_add.html
{% extends "layout.html" %}
{%load static%}
{% block title %}
添加用户
{% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datetimepicker.min.css' %}">
{% endblock %}
{% block 用户管理 %}class="active"{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">新建用户</h3>
</div>
<div class="panel-body">
<form action="/user/add/" method="post" novalidate>{% comment %}novalidate 关闭浏览器自带的校验{% endcomment %}
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label>{{ field.label }}</label>
{{ field }} {% comment %}此处的label即为定义属性时候的verbose_name值{% endcomment %}
<span style="color:red;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'plugins/bootstrap-datepicker/js/bootstrap-datetimepicker.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/locales/bootstrap-datetimepicker.zh-CN.js' %}"></script>
<script>
$(function () {
$('#id_create_time').datetimepicker({
format:'yyyy-mm-dd',
startDate:'0',
language:"zh-CN",
autoclose:true,
});
})
</script>
{% endblock %}
注意:
- 载入datetimepicker插件
- js代码中包含对应input框的id,如果是用django生成的,默认是id_ + 对应的字段名
11.ModelForm和Bootstrap
-
ModelForm可以帮我们生成HTML标签
class UserModelForm(forms.ModelForm): name = forms.CharField(min_length=3, label='用户名') class Meta: model = models.UserInfo fields = ['name', 'password'] #调用这个类 form = UserModelForm
{{ form.name }} 普通的input框 {{ form.password }} 普通的input框
-
定义插件
class UserModelForm(forms.ModelForm): name = forms.CharField(min_length=3, label='用户名') class Meta: model = models.UserInfo fields = ['name', 'password'] widgets = { "name":forms.TextInput(attrs={"class":"form-control"}) "password": forms.PasswordInput(attrs={"class":"form-control"}), }
class UserModelForm(forms.ModelForm): name = forms.CharField( min_length=3, label='用户名', widget=forms.TextInput(attrs={"class":"form-control"}) ) class Meta: model = models.UserInfo fields = ['name', 'password']
{{ form.name }} Bootstrap的input框 {{ form.password }} Bootstrap的input框
-
在__init__中重新定义
class UserModelForm(forms.ModelForm): name = forms.CharField(min_length=3, label='用户名') class Meta: model = models.UserInfo fields = ['name', 'password'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) #循环ModelForm中的所有字段,给每个字段的插件设置 for name, field in self.fields.items(): field.widget.attrs = { "class":"form-control", "placeholder":field.label }
改进
class UserModelForm(forms.ModelForm): name = forms.CharField(min_length=3, label='用户名') class Meta: model = models.UserInfo fields = ['name', 'password'] 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 }
但是创建UserEditModelForm的时候还要再写一遍,怎么办?
-
自定义类
class BootStrapModelForm(forms.ModelForm): 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 }
继承类
class UserEditModelForm(BootstrapModelForm): class Meta: model = models.UserInfo fields = ["name", "password", "age"]
在UserInfoModelForm中继承
view.py
# 继承BootStrapModelForm class UserModelForm(BootStrapModelForm): name = forms.CharField( min_length=3, label="用户名" ) # 设定一个最小长度用于校验值合法 # password = forms.CharField(validators=正则表达式, label="密码") class Meta: model = models.User_Info fields = [ "name", "password", "age", "account", "create_time", "age", "gender", "depart", ] # 可以通过fields = "__all__" # 通过widgets为某些特定的类型生成指定的输入框模式(不推荐) # widgets = { # "password": forms.PasswordInput(), # "account": forms.TextInput(), # }
-
也可以把所有的modelform类放到utils文件夹中的form.py文件中
modelform.py
from django import forms from app01 import models from django.core.validators import RegexValidator, ValidationError from app01.utils.bootstrap_model_form import BootStrapModelForm # +--------------------------------------------------------------------------------------------------------------------------------------------+# """# class UserModelForm(forms.ModelForm): # name = forms.CharField( # min_length=3, label="用户名" # ) # 设定一个最小长度用于校验值合法 # # password = forms.CharField(validators=正则表达式, label="密码") # class Meta: # model = models.User_Info # fields = [ # "name", # "password", # "age", # "account", # "create_time", # "age", # "gender", # "depart", # ] # 可以通过fields = "__all__" # # 通过widgets为某些特定的类型生成指定的输入框模式(不推荐) # widgets = { # "password": forms.PasswordInput(), # "account": forms.TextInput(), # "create_time": forms.DateInput(), # } # # 快速为所有的输入框添加样式 # def __init__(self, *args, **kwargs): # super().__init__(*args, **kwargs) # # #重新部分input框的type --失效,为何? # # self.fields['password'].widget.attrs.update({'type': 'password'}) # # self.fields['account'].widget.attrs.update({'type': 'text'}) # # self.fields['create_time'].widget.attrs.update({'type': 'date'}) # # 循环找到所有的插件 # # print(self.fields) # for name, field in self.fields.items(): # # if name == 'password': # # field.widget.attrs = {"class":"form-control"} # # continue # field.widget.attrs = {"class": "form-control", "placeholder": field.label} # 继承BootStrapModelForm""" class UserModelForm(BootStrapModelForm): name = forms.CharField( min_length=3, label="用户名" ) # 设定一个最小长度用于校验值合法 # password = forms.CharField(validators=正则表达式, label="密码") class Meta: model = models.User_Info fields = [ "name", "password", "age", "account", "create_time", "age", "gender", "depart", ] # 可以通过fields = "__all__" # 通过widgets为某些特定的类型生成指定的输入框模式(不推荐) widgets = { "password": forms.PasswordInput(), "account": forms.TextInput(), } class NumModelForm(forms.ModelForm): # #验证方式1 # mobile = forms.CharField( # label='移动号码', # validators=[RegexValidator(r'^1\d{10}$', '号码格式错误,必须是以1开头的11位数字'), ], # ) class Meta: model = models.Cool_Number fields = "__all__" # 所有字段 # exclude = ['level'] 去除某些字段 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs = {"class": "form-control", "placeholder": field.label} # 验证:方式2 钩子方法,这里的函数名必须是<clean_字段名>的格式 def clean_mobile(self): input_mobile = self.cleaned_data[ "mobile" ] # cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典 exits = models.Cool_Number.objects.filter(mobile=input_mobile).exists() if exits: raise ValidationError("不允许添加已存在的移动号码") if len(input_mobile) != 11 or input_mobile[0] != "1": # 验证不通过 raise ValidationError("格式错误,移动号码应该是以1开头的11位数字") # 验证通过,将用户的输入的值返回 return input_mobile # 移动号码字段在编辑中应该是不可以修改或者隐藏 class NumEditModelForm(forms.ModelForm): mobile = forms.CharField( disabled=True, label="手机号" ) # 方法一,直接修改对应的属性来调整显示的效果为不可修改 class Meta: model = models.Cool_Number # fields =['price', 'level', 'status']#方法二,去掉对应字段不显示 fields = "__all__" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs = {"class": "form-control", "placeholder": field.label} def clean_mobile(self): # 当前编辑的哪一行ID # print(self.instacne.pk) input_mobile = self.cleaned_data[ "mobile" ] # cleaned_data返回的是用户输入的信息 以 字段名:字段值 组成的字典 exits = ( models.Cool_Number.objects.exclude(id=self.instance.pk) .filter(mobile=input_mobile) .exists() ) if exits: raise ValidationError("不允许添加已存在的移动号码") if len(input_mobile) != 11 or input_mobile[0] != "1": # 验证不通过 raise ValidationError("格式错误,移动号码应该是以1开头的11位数字") # 验证通过,将用户的输入的值返回 return input_mobile
同理,可以将views.py中的函数放到一个新的文件夹views中,然后分别为每种类型新建py文件,但要记住修改urls.py中的引入
from app01.views import depart, user, pretty urlpatterns = { path('depat/list/', depart.depart_list) }
12.管理员
12.1管理员表
view -> admin.py
def admin_list(request):
# 搜索
data_dict = {}
value = request.GET.get("query_info")
if value:
data_dict["username__contains"] = value
queryset = models.Admin.objects.filter(**data_dict)
page_object = Pagination(request, queryset)
return render(
request,
"admin_list.html",
{
"queryset": page_object.page_queryset,
"page_string": page_object.html(),
"search_data": value,
},
)
# 分页
queryset = models.Admin.objects.all()
page_object = Pagination(request, queryset)
return render(
request,
"admin_list.html",
{"queryset": page_object.page_queryset, "page_string": page_object.html()},
)
admin_list.html
{% extends "layout.html" %}
{% block title %}
管理员账户
{% endblock %}
{% block css %}
<style>
</style>
{% endblock %}
{% block 管理员账户 %}class="active"{% endblock %}
{% block content %}
<div class="container">
<div style="margin-bottom:10px;">
<a class="btn btn-success" href="/admin/add/"><i class="fa fa-plus-circle" aria-hidden="true"></i> 新建管理员</a>
<div style="float:right;width:400px">
<form method='get'>
<div class="input-group">
<input type="text" class="form-control" placeholder="请输入搜索内容" name="query_info" value={{ search_data }}>
<span class="input-group-btn">
<button type="submit" class="btn btn-default" ><i class="fa fa-search" aria-hidden="true"></i> 搜索</button>
</span>
</div><!-- /input-group -->
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-list-ul"></i> 管理员列表</h3>
</div>
<table class="table table-hover table-bordered" style="padding:0 20px 0 20px;">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
{% for item in queryset %}
<tr>
<td class="col-xs-3">{{ item.id }}</td>
<td class="col-xs-3">{{ item.username }}</td>
<td class="col-xs-3">********</td>
<td>
<a class="btn btn-primary btn-xs" href="#"><i class="fa fa-edit" aria-hidden="true"></i> 编辑</a>
<span> </span>
<a class="btn btn-danger btn-xs" href="#"><i class="fa fa-trash" aria-hidden="true"></i> 删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<ul class="pagination">
{{ page_string }}
</ul>
</div>
{% endblock %}
{% block js %}
<script>
</script>
{% endblock %}
12.2添加管理员
1.基础代码
view -> admin_add.py
def admin_add(request):
if request.method == "GET":
form = modelform.AdminModelForm()
return render(request, "admin_add.html", {"form": form})
form = modelform.AdminModelForm(data=request.POST)
if form.is_valid():
form.save()
return redirect("/admin/list/")
return render(request, "admin_add.html", {"form": form})
utils -> modelform.py
class AdminModelForm(BootStrapModelForm):
# 限定username的最小字符数
username = forms.CharField(min_length=3, label="用户名")
class Meta:
model = models.Admin
fields = "__all__"
创建一个通用的add.html模板,之后使用含有xxx_add.html的模板只需要使用这个模板即可
templ_add.html
{% extends "layout.html" %}
{%load static%}
{% block title %}
{{ title_templ }}
{% endblock %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ panel_title_templ}}</h3>
</div>
<div class="panel-body">
<form action="/admin/add/" 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"><i class="fa fa-save" aria-hidden="true"></i> 保 存</button>
</form>
</div>
</div>
</div>
{% endblock %}
对应的admin_add.py
def admin_add(request):
templ = {
"titel_templ": "添加管理员",
"panel_title_templ": "添加管理员",
}
if request.method == "GET":
form = modelform.AdminModelForm()
return render(request, "templ_add.html", {**templ, "form": form})
form = modelform.AdminModelForm(data=request.POST)
if form.is_valid():
form.save()
return redirect("/admin/list/")
return render(request, "templ_add.html", {**templ, "form": form})
2.设置确认密码字段
utils -> modelform.py
class AdminModelForm(BootStrapModelForm):
username = forms.CharField(min_length=3, label="用户名")
confirm_password = forms.CharField(
label="确认密码",
widget=forms.PasswordInput(
render_value=True
), # render_value表示在提交raise错误后是否保持这个值
)
class Meta:
model = models.Admin
fields = ["username", "password", "confirm_password"]
widgets = {"password": forms.PasswordInput(render_value=True)}
# 通过钩子函数比对密码和确认密码
def clean_confirm_password(self):
pwd = self.cleaned_data.get("password")
confirm = self.cleaned_data.get("confirm_password")
# 确认密码与密码不一致
if confirm != pwd:
raise ValidationError("确认密码与密码不一致!")
# 确认密码与密码一致,返回一个会保存到confirm_password字段的值
return confirm
3.数据库密码加密
使用MD5加密,先创建一个加密函数
utils -> encrypt.py
from django.conf import settings
import hashlib
def md5(data_string):
salt = settings.SECRET_KEY
obj = hashlib.md5(salt.encode("utf-8"))
obj.update(data_string.encode("utf-8"))
return obj.hexdigest()
在modelform中调用这个加密函数
# 通过钩子函数对密码进行加密
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("确认密码与密码不一致!")
# 确认密码与密码一致,返回一个会保存到confirm_password字段的值
return confirm
13.用户登录 instance&render_value
什么是session和cookie?
- http请求和https请求 - 无状态 & 短链接(一次请求一次响应然后断开链接)
网站可以返回响应体和响应头,响应体即html字符串,响应头 - Cookie(本质上是保存在浏览器的键值对)
基于Cookie,网站可以给用户发放凭证,用户可以使用凭证网站可以实现有记忆性的响应,即用户在发放请求时候浏览器会自动响应
同时网站会存储凭证和用户信息的映射关系,当用户提供凭证时候用于比对(由网站存储的内容叫做Session -> 一个抽象的概念,具体可以存储在数据库文件等等)
render_value和instance
render_value用在input框中发生错误后是否保留原数据
instance用于:
1.在进入页面时候在input框中提前添加
首先写出对应的login.py和login.html文件
views -> login.py
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():
# 验证成功,获取到的用户名和密码
# print(form.cleaned_data)
# 去数据库校验数据是否正确
# admin_object = models.Admin.objects.filter(
# username=form.cleaned_data["username"],
# password=form.cleaned_data["password"],
# ).first() 过于繁琐
admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
"""
clean_data是一个包含验证后的数据的字典,
如果用户名密码正确,admin_object是一个admin对象,
如果不正确,admin_object=None
"""
if admin_object:
# 账号密码正确
return redirect("/admin/list/")
# 添加错误 .add_error("字段名", "错误信息")
form.add_error("password", "用户名或密码错误")
return render(request, "login.html", {"form": form})
return render(request, "login.html", {"form": form})
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.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
<style>
.account{
width:500px;
border: 1px solid #dddddd;
height:350px;
padding:30px;
margin-left:auto;
margin-right:auto;
margin-top:100px;
border-radius: 8px;
box-shadow: 2px 2px 20px #aaa;
}
.account h1{
text-align:center;
}
.navbar{
border-radius:0;
}
.login-form{
display:block-inline;
hieght:500px;
}
</style>
</head>
<body>
<div class="container account">
<h1>用户登录</h1>
<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>
<button type="submit" class="btn btn-primary">登 录</button>
</form>
</div>
<script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}"></script>
</body>
</html>
添加Session
admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
"""
clean_data是一个包含验证后的数据的字典,
如果用户名密码正确,admin_object是一个admin对象,
如果不正确,admin_object=None
"""
if admin_object:
# 账号密码正确
# 网站生成一个随机字符串*Cookie*, 再写入到Session中
# request.session["info"] = "xxx"
# # 在Session中存储"[cookie | info":"xxx" ]
# 在Session中以字典形式存储cookie的对应内容 - info= {id:1, name:admin}
request.session["info"] = {
"id": admin_object.id,
"name": admin_object.username,
}
return redirect("/admin/list/")
查看网页获取的Cookie
查看数据库获取的Cookie
mysql> select * from django_session;
+----------------------------------+------------------------------------------------------------------------------------------------+----------------------------+
| session_key | session_data
| expire_date |
+----------------------------------+------------------------------------------------------------------------------------------------+----------------------------+
| 1bifn4ie9b5jbvr95v0q8yoseukmk0lq | eyJpbmZvIjp7ImlkIjo3LCJuYW1lIjoiYWRtaW4ifX0:1rYQ0Z:puh2U_7K_cS7x2tb9CRNG32_4FTySOsuR8H2dUo94lw | 2024-02-23 12:29:31.603149 |
+----------------------------------+------------------------------------------------------------------------------------------------+----------------------------+
1 row in set (0.00 sec)
mysql>
之后除了登录页面之外的所有页面都应该有对Cookie的验证,如果正确才能正常页面,否则返回/login/
13.1登录
登录成功后:
- cookie,随机字符串
- session,用户信息
在其他需要登录才能访问的页面中,都需要加入
def index(request):
info = request.session.get("info")
if not info:
return redirect("/login/")
...
在django中,可以使用组件帮助快速完成登录跳转
目标:在18个视图函数前面统一加入判断 - 使用django的中间件
13.2中间件
中间件的本质是类,网页的请求和响应都要经过中间件,类中定义了process_request方法和process_response方法用于用户请求信息的传入和页面信息的传出,可以在中间件中直接禁止请求的访问,就可以直接返回而不需要经过最内层的函数
- 定义中间件
创建middleware文件夹 -> 创建auth.py文件
auth.py
from django.utils.deprecation import MiddlewareMixin
class M1(MiddlewareMixin):
"""中间件"""
def process_request(self, request):
# 如果没有返回值(return),默认为None,意味着可以继续往后走
# 如果有返回值(必须是HttpResponse或者render或者redirect)
print("进入M1")
def process_response(self, request, response):
print("退出M1")
return response
class M2(MiddlewareMixin):
"""中间件2"""
def process_request(self, request):
print("进入M2")
def process_response(self, request, response):
print("退出M2")
return response
- 应用中间件
修改settings.py中的MIDDLEWARE = ...
添加app01.middleware.auth.M1和app01.middleware.auth.M2
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",
]
- 运行结果
[10/Feb/2024 17:51:05] "GET /user/list/ HTTP/1.1" 200 11087
进入M1
进入M2
退出M2
退出M1
- 在中间件的process_request方法中
# 如果没有返回值(return),默认为None,意味着可以继续往后走
# 如果有返回值(必须是HttpResponse或者render或者redirect)
13.3中间件实现登录校验
-
编写中间件
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect, render, HttpResponse class AuthMiddleware(MiddlewareMixin): """中间件""" def process_request(self, request): # 1.排除掉不需要登录就能访问的页面 # request.path_info 获取当前用户请求的URL if request.path_info == "/login/": return # 2.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走 info_dict = request.session.get("info") if info_dict: return # 3.没有登录过,回到登陆页面 return redirect("/login/") def process_response(self, request, response): print("退出M1") return response
-
应用中间件settings.py
"app01.middleware.auth.AuthMiddleware",
13.4注销
views -> account.py
def logout(request):
"""注销"""
# 清除当前用户的session
request.session.clear()
return redirect("/login/")
13.5在html中调用session
request.session中存储的字典关键字.key
layout.html
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ request.session.info.name }}<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人资料</a></li>
<li><a href="#">修改密码</a></li>
<li><a href="/logout/">注销</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">关于我们</a></li>
</ul>
</li>
</ul>
14.图片验证码
14.1生成图片
pip install pillow
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, dormat='png')
utils -> code.py
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random
def check_code(
font_file="project6-Django\\System_Manage_User\\app01\\fontfile\\Monaco.ttf",
width=120,
height=30,
char_length=5,
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__":
# 使用手册
# 0.创建font_file=['font地址1', 'font地址2', 'font地址3']
# 1. 直接打开
# img,code = check_code(font_file)
# img.show()
# 2. 写入文件
# img,code = check_code(font_file)
# 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()
pass
urls.py
path("image/code/", account.image_code),
views -> account.py
def image_code(request):
# 调用含有pillow函数生成图片
img, code_string = check_code()
print(code_string)
# 将图片放到内存中
stream = BytesIO()
img.save(stream, "png")
return HttpResponse(stream.getvalue())
login.html
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
<input type="text" name="code" class="form-control" placeholder="请输入图片验证码" required="" id="id_code">
<span style="color:red;"></span>
</div>
<div class="col-xs-5">
<img id="img_code" src="/image/code/">
</div>
</div>
<div>
<a href="/login/"> 看不清?换一张</a>
</div>
</div>
14.2验证验证码 add_error&Validation
LoginForm 添加code字段
# Form - 要自己动手写,且一般用作比对而不修改数据库内容
# ModelForm - 关联数据库
class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名", widget=forms.TextInput, required=True
) # required是表示该字段是否必须填写,默认为True
password = forms.CharField(
label="密码", widget=forms.PasswordInput(render_value=True), required=True
)
code = forms.CharField(label="验证码", widget=forms.TextInput)
# 定义用于校验登录数据的钩子函数,这样调用这个类所返回的form中的密码会自动加密用于比对数据库
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
login
user_input_code = form.cleaned_data.pop("code")
# 校验验证码是否正确
image_code = request.session.get("image_code", "")
if image_code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, "login.html", {"form": form})
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():
# 验证成功,获取到的用户名和密码 还有验证码
# 获取用户输入的验证码 clean_data
# form.cleaned_data['code'] 过于繁琐
# 剔除验证码(验证码并不在数据库中) .pop()去掉并返回这个值
user_input_code = form.cleaned_data.pop("code")
# 校验验证码是否正确
image_code = request.session.get("image_code", "")
if image_code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, "login.html", {"form": form})
# 去数据库校验数据是否正确 使用字典校验的方式
# admin_object = models.Admin.objects.filter(
# username=form.cleaned_data["username"],
# password=form.cleaned_data["password"],
# ).first() 过于繁琐
admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
"""
clean_data是一个包含验证后的数据的字典,
如果用户名密码正确,admin_object是一个admin对象,
如果不正确,admin_object=None
"""
if admin_object:
# 账号密码正确
# 网站生成一个随机字符串*Cookie*, 再写入到Session中
# request.session["info"] = "xxx"
# # 在Session中存储"[cookie | info":"xxx" ]
# 在Session中以字典形式存储cookie的对应内容 - info= {id:1, name:admin}
request.session["info"] = {
"id": admin_object.id,
"name": admin_object.username,
}
# 设置session的保存时间
request.session.set_expiry(60 * 60 * 24 * 7)
return redirect("/admin/list/")
# 添加错误 .add_error("字段名", "错误信息")
form.add_error("password", "用户名或密码错误")
return render(request, "login.html", {"form": form})
return render(request, "login.html", {"form": form})
add_error是将一个错误信息添加到form中,可以通过field.errors.0来调出该错误内容
ValidationError是引起一个错误,用raise ValidationError 的话后续的cleaned_data字典中将没有该错误字段的键值对
如过有复合校验的需求,例如先校验手机号是否注册,再校验手机号与验证码是否匹配的时候,若想要两个局部钩子clean函数分开单独检验,最好使用第二种方法,否则如果手机号码出错,后续的是否匹配的钩子函数是无法在cleaned_data中获取手机号的。