Django笔记
1.安装django
执行命令进行安装
# 安装Django版本3.0
pip install django==3
创建一个Django项目
# 在指定文件夹下创建一个Django项目
django-admin startproject firstdjango
项目文件结构
- firstdjango ()
- manage.py (管理项目的文件, 例如:运行、类自动生成数据库表)
- firstdjango (项目同名目录)
__init__.py
(初始化化文件)settings.py
(项目配置文件,例如:数据库端口号、用户名、密码…)urls.py
(根路由,url和函数的对应关系)asgi.py
(异步形式运行项目,编写socket,处理网络请求)wsgi.py
(同步形式运行项目,编写socket,处理网络请求)
运行django项目
# 在项目的根目录下执行以下命令运行该项目
# 后面可以指定主机ip和端口号,不指定默认为 127.0.0.1:8000
# 可以根据urls.py文件的配置查看各个API对应的访问路径
python manage.py runserver 127.0.0.1:8000
2.创建app
概念
-
可以创建多个app 对应多个不同的应用,如web应用,小程序,手机app等
-
app 就是浏览器对django发送请求时执行的函数一些文件的文件集合(python包)
-
执行命令创建一个app目录
python manage.py startapp web
-
执行命令后会生成一个 app 包
web (包名) __init__.py (初始化文件) admin.py (默认的内部后台管理的配置,一般无需操作) apps.py (App名字,自定义执行的文件,一般无需操作) migrations (迁移记录,无需操作,自动生成) __init__.py (初始化文件) models.py (编写类对数据可进行操作,简化对SQL的操作,称为ORM) tests.py (单元测试,一般无需操作) views.py (视图函数,用于对接 urls.py 路由中的路径中的对应函数)
-
在路由 urls.py 文件导入视图函数
from django.contrib import admin from django.urls import path from web.views import login # 导入视图文件中 login函数 urlpatterns = [ path('admin/', admin.site.urls), path('login/', login) # 配置 uri 对应的函数 , uri后面必须加 / ]
-
app包中的views.py 视图文件中的代码
from django.shortcuts import render from django.shortcuts import HttpResponse # 导入http响应类 def login(request): # 使用HttpResponse类 # 传入响应数据,实例化一个响应对象 # 然后将响应对象返回给接口请求者,如浏览器、前端、ApiPost等 return HttpResponse("登录页面")
-
在配置文件中注册app
# 安装的应用程序列表 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 将应用包下的apps文件中的XXConfig 配置类,注册到应用列表中, # 让模板配置可以找到对应应用的对应模板 'web.apps.WebConfig', ]
Django 配置文件 settings.py
import os
# 获取项目根目录路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 快速开始开发设置 - 不适用于生产环境
# 参考:https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# 密钥用于加密会话数据和其他安全机制
SECRET_KEY = 'y5lo9-3=ax5!nwgmh0tn4*lgr)2zkg^t64m&-2+lvh(xte*$o0'
# 调试模式:True 为开启,False 为关闭 (上线后关闭)
DEBUG = True
# 允许访问的主机列表
ALLOWED_HOSTS = []
# 安装的应用程序列表
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 将应用包下的apps文件中的XXConfig 配置类,注册到应用列表中,
# 让模板配置可以找到对应应用的对应模板
'web.apps.WebConfig',
# 注册序列化模块
'rest_framework',
# 注册跨域模块 ( 需要先安装)
'corsheaders',
# 注册过滤器模块
'django_filters',
# 注册api文档模块
'coreapi',
]
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# 注册Django跨域中间件, 必须写在此处
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
# 前后端分离模式需要注释掉
'django.middleware.csrf.CsrfViewMiddleware',
# token令牌中间件,django会自动进行身份的验证,无需自行编写代码
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# REST 配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication', # Basic认证
'rest_framework.authentication.SessionAuthentication', # session认证
# 'rest_framework.authentication.TokenAuthentication', # token认证
],
# 可以通过配置文件来设置默认的过滤器类。
# 这样可以确保在整个项目中所有的 API 视图都使用相同的默认过滤器。
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
],
# 接口文档配置
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
# 根 URL 配置 (根路由)
ROOT_URLCONF = 'firstdjango.urls'
# 模板配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 配置全局模版路径, 模板文件最好都设置或命名为 templates ,减少意外
'DIRS': [os.path.join(BASE_DIR, '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',
],
},
},
]
# WSGI 同步应用程序
WSGI_APPLICATION = 'firstdjango.wsgi.application'
# 数据库设置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 连接的数据库类型为mysql
'NAME': 'test', # 数据库名
'USER': 'root', # 数据库用户名
'PASSWORD': 'root', # 数据库密码
'HOST': '127.0.0.1', # 主机ip
'PORT': 3306, # 端口号
}
}
# 密码验证器
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# 国际化设置
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans' # 将Form验证提示信息改为中文
# 设置时区为上海
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# 静态文件 URL
STATIC_URL = '/static/'
# 设置默认的主键类型为 BigAutoField
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# 用于在视图文件中可以访问到app 下面的静态文件(验证码字体需要用到)
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'web', 'static'),
]
# ========= 跨域配置 =========
# 是否允许携带cookie
CORS_ALLOW_CREDENTIALS = True
# 设置允许跨域的域名和端口
CORS_ALLOWED_ORIGINS = [
'http://localhost:5173', # 允许的前端地址,根据实际情况修改
'http://127.0.0.1:5173',
]
# 添加跨域白名单
CORS_ORIGIN_WHITELIST = {
'http://localhost:5173',
'http://127.0.0.1:5173',
}
# 允许所有的用户跨域访问
# CORS_ORIGIN_ALLOW_ALL = True
# 允许前端携带认证信息,设置为 True
CORS_ALLOW_CREDENTIALS = True
3.视图函数
概念
- 在app包中的视图文件views.py文件中创建一个视图函数需要传入一个 request 对象
- request 是用户请求相关的所有数据,包括请求头、请求参数、session、cookie等
- 我们可以从request中获得用户给我们提交的所有数据
- 也可以根据请求头判断用户请求的方式等
- 我们主要就是根据用户给我们发送的请求处理相关的业务,并返回对应的数据
- 视图文件的返回值的类型可以是文本,html页面,json数据,跳转到另一个链接
- 注意:多个业务最好在 app 应用下新建一个 views 目录,里面的每个文件编写同一类型的视图函数, 然后 将views.py 文件删除
示例
from django.shortcuts import render, redirect
from django.shortcuts import HttpResponse # 导入http响应类
from django.http import JsonResponse # 导入Json响应类
def login(request):
# 使用HttpResponse类
# 传入响应数据,实例化一个响应对象
# 然后将响应对象返回给接口请求者,如浏览器、前端、ApiPost等
# 返回的直接就是一个文本类型
# return HttpResponse("登录页面")
# 传入请求对象和一个Html页面的路径,表示返回一个Html页面
# return render(request, 'login.html')
# 表示重定向到百度页面
# return redirect("https://www.baidu.com")
data = {'status': 200, 'data': "响应的数据"} # 要返回的 JSON 数据
# 返回一个Json对象
return JsonResponse(data)
配置文件settings.py中需要修改两个位置
# 安装的应用程序列表
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 将应用包下的apps文件中的XXConfig 配置类,注册到应用列表中,
# 让视图函数可以找到对应的模板,寻找模板的顺序是按照应用注册的顺序寻找
# 先找项目根目录下的templates中有没有指定的文件
# 如果没有则根据应用注册的顺序在每一个应用里面寻找对应名称的模板
'web.apps.WebConfig',
]
# 模板配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 配置根模板的路径
'DIRS': [os.path.join(BASE_DIR, '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',
],
},
},
]
3.1 视图类
删除视图类的使用
执行流程
- 当用户发送一个 HTTP 请求到与 DeleteView 关联的 URL 时,Django 的 URL 解析器将会匹配到对应的视图。
- DeleteView 类首先检查是否定义了
queryset
属性或model
属性。如果都未定义,则会抛出ImproperlyConfigured
异常。 - 如果定义了
queryset
属性,则会调用视图类的get_queryset()
方法获取要删除的对象的查询集。默认情况下,该方法返回self.queryset
。 - 如果定义了
model
属性,则会调用视图类的get_queryset()
方法获取要删除的对象的查询集。默认情况下,该方法返回self.model.objects.all()
。 - DeleteView 根据请求中传递的参数(通常是对象的主键值)从查询集中获取要删除的对象。如果找不到对应的对象,会抛出
Http404
异常。 - DeleteView 调用
get()
方法进行 GET 请求处理,在此方法中,它会渲染一个确认删除的页面,并将要删除的对象传递给模板。 - 如果用户确认删除操作并发送一个 POST 请求,DeleteView 将调用
post()
方法进行 POST 请求处理。 - 在
post()
方法中,DeleteView 首先调用delete()
方法删除对象。默认情况下,它会调用对象的delete()
方法来执行删除操作。 - 删除成功后,DeleteView 根据
success_url
属性指定的重定向路径进行重定向。如果未定义success_url
,则会抛出ImproperlyConfigured
异常。 - 如果删除失败(例如,对象不存在),DeleteView 会调用
delete()
方法的handle_no_delete()
方法处理删除失败的情况。 - DeleteView 最终返回一个 HTTP 响应,可以是重定向到其他页面或者是渲染一个模板。
示例
视图文件代码
from django.urls import reverse_lazy
from django.views.generic import DeleteView
from web.models import Admin
# 定义一个删除视图类,必须继承父类 DeleteView
# DeleteView 中包含了各种处理方法,
# 我们只需要指定 要操作的模型和重定向路径以及确认删除页面路径即可
class DeleteAdmin(DeleteView):
# 关联要删除的数据的模型类
model = Admin
# 设置要重定向的页面路径 , 变量名必须使用 success_url
# reverse_lazy() 方法 传入一个路由别名,它会自动解析出 对应的url
success_url = reverse_lazy('admin_list')
# 指定确认删除页面的模版名称,会自动寻找该模版 变量名必须使用 template_name
template_name = 'confirm_del_admin.html'
路由文件 部分关键代码
from web.views.admin import DeleteAdmin
urlpatterns = [
# DeleteAdmin 删除视图类 必须指定一个主键参数 pk <int:pk> 参数名必须使用 pk
path('delete/user/<int:pk>/', DeleteAdmin.as_view(), name="delete_user"), # 删除用户路由
]
列表页面关键代码
<!-- 把需要删除的数据的id传入url中 -->
<a class="btn btn-primary" href="{% url 'delete_user' pk=row.id %}">删除</a>
确认删除页面代码
<!-- 继承母板 -->
{% extends 'base.html' %}
<!-- 主体内容 -->
{% block content %}
<h2>确认删除管理员</h2>
<!-- 必须使用 object对象名获取对应模型的 字段值, -->
<p>您确定要删除管理员 "{{ object.username }}" 吗?</p>
<form method="post">
{% csrf_token %}
<input type="submit" value="确认删除">
</form>
{% endblock %}
获取前端提交过来的 json数据
import json
from django.http import JsonResponse
from django.views import View
from rest_framework.parsers import JSONParser
# 创建视图类,继承父类 View
class LoginView(View):
# 定义POST请求执行的代码
def post(self, request):
""" 登录接口 """
# 方法一 获取提交过来的json数据, 并转为字典类型
# data = json.loads(request.body.decode())
# 方法二 获取提交过来的json数据, 并转为字典类型
data = JSONParser().parse(request)
print(data)
return JsonResponse({'code': 200, 'message': '登录成功'})
4.HTML模板
模板语法
-
变量
- 使用两个双大括号
{{message}}
包含着一个变量, - 用于接收模版文件中 视图函数返回值中的参数值将 数据赋值到html页面中,
- 然后再返回给请求者
- 使用两个双大括号
-
循环
-
使用大括号和百分号将 代码包起来,声明这是模板语法
<ul> <!-- 可以使用模板函数返回值参数中的字典的 key 获取对应的值 --> <!-- 模版语法的循环开头 --> {% for item in data_list %} <li> {{ item }} </li> <!-- 模版语法的循环结尾 --> {% endfor %} </ul>
-
模板文件中的视图函数
def user_list(request): # 1.数据库中获取的数据 data = ["Tom", "Jack", "Mary"] # 2.redner函数的参数 # 参数一 request 请求对象 # 参数二 模板文件路径 # 参数三 字典类的数据,用于将数据传给模板,让模板渲染数据到html页面中 return render(request, './html/user_list.html', {"message": "标题", "data_list": data})
-
-
模版语法-获取数据容器中的指定元素
-
使用 {{ 列表键名.0 }} 获取字典中指定key列表中的第一个元素
<a> {{ data_lsit.0 }} </a>
-
使用 {{ 字典键名.二层字典键名 }} 获取外层字典中指定的 key字典的指定key的值
<a> {{ dict.name }} </a>
# 视图函数返回值应该如下 return render(request, './html/user_list.html', {"message": "标题", "dict": dict_data})
-
-
获取字典中的所有key 和所有value
-
获取字典中的所有key
<ul> <!-- 可以使用模板函数返回值参数中的字典的 key 获取对应的值 --> <!-- 模版语法的循环开头 --> {% for item in dict.keys %} <li> {{ item }} </li> <!-- 模版语法的循环结尾 --> {% endfor %} </ul>
-
获取字典中的所有value
<ul> <!-- 可以使用模板函数返回值参数中的字典的 key 获取对应的值 --> <!-- 模版语法的循环开头 --> {% for item in dict.values %} <li> {{ item }} </li> <!-- 模版语法的循环结尾 --> {% endfor %} </ul>
-
获取字典中的所有 key 和 value
<ul> <!-- 可以使用模板函数返回值参数中的字典的 key 获取对应的值 --> <!-- 模版语法的循环开头 --> {% for k,v in dict.items %} <li> 键 {{ k }} 值 {{ v }}</li> <!-- 模版语法的循环结尾 --> {% endfor %} </ul>
-
4.1 路由
路由文件
from django.contrib import admin
from django.urls import path
from web.views.account import login, host, CaptchaView, index, logout
from web.views.admin import admin_list, add_user, edit_user, DeleteAdmin, get_json_data
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', login, name="login"),
path('', host),
# 调用CaptchaView 类视图的as_view()方法执行请求方式对应的函数
# name属性用于在模版页面使用模版语法 触发指定链接 如 {% url 'captcha' %}
path('captcha/', CaptchaView.as_view(), name='captcha'),
path('index/', index, name="index"),
path('logout/', logout, name="logout"), # 退出登录
path('admin/list/', admin_list, name="admin_list"), # 用户列表路由
path('add/user/', add_user, name="add_user"), # 添加用户路由
# 通过路由设置 获取get参数 当请求url为 edit/user/数字类型/ 时会执行指定的视图函数
path('edit/user/<int:uid>/', edit_user, name="edit_user"), # 编辑用户路由
# DeleteAdmin 删除视图类 必须指定一个主键参数 pk <int:pk>
path('delete/user/<int:pk>/', DeleteAdmin.as_view(), name="delete_user"), # 删除用户路由
# 测试路由
path('getJsonData/', get_json_data, name="get_json_data"), # 获取json数据
]
模板页面
<tbody>
<!-- 循环展示所有的用户数据 -->
{% for row in queryset %}
<tr>
<td>{{ row.username }}</td>
<td>{{ row.age }}</td>
<td>{{ row.get_gender_display }}</td>
<td>{{ row.depart.title }}</td>
<td>
<a class="btn btn-primary" href="{% url 'delete_user' pk=row.id %}">删除</a>
<!-- 访问执行url并传入get参数 {% url 'edit_user' uid=row.id %} -->
<a class="btn btn-primary" href="{% url 'edit_user' uid=row.id %}">编辑</a>
</td>
</tr>
{% endfor %}
</tbody>
视图文件
def edit_user(request, uid):
# 根据 id 获取对应的用户数据对象 (不管是GET请求还是POST请求都能获取到用户 uid)
admin_obj = get_object_or_404(Admin, id=uid)
# 如果是GET请求让他渲染页面
if request.method == 'GET':
# 根据用户对象创建表单对象
form = EditUserModelForm(instance=admin_obj)
# 将表单连通用户数据返回给编辑页面
return render(request, 'admin_form.html', {'form': form})
# POST请求逻辑
# 根据请求数据创建表单,并传入要修改的用户对象
form = EditUserModelForm(data=request.POST, instance=admin_obj)
# 验证数据合法性
if form.is_valid():
# 验证通过, 将ModelForm表单对象的数据保存到数据库
form.save()
# 返回用户列表
return redirect('/admin/list/')
else:
# 校验不通过,携带错误信息返回编辑页面
return render(request, 'admin_form.html', {'form': form})
def delete_user(request, uid):
# 根据id查询数据然后删除
get_object_or_404(Admin, id=uid).delete()
补充知识:拆分每个应用的路由
-
项目根目录下的 路由文件 urls.py
from django.contrib import admin from django.urls import path,include,re_path urlpatterns = [ path('admin/', admin.site.urls), # 将应用目录下的路由文件包含进来 re_path(r'^web/', include('web.urls')), ]
-
应用目录下新建的路由文件 urls.py
from django.urls import path from web.views.account import login urlpatterns = [ # 访问的实际路径为 127.0.0.1:8000/web/login/ path('login/', login), ]
5.模板、视图和路由 练习
html模板代码
<form>
<table>
<thead>
<tr>
<!-- 循环传过来的列表数据获得第一行数据的所有key 作为表头 -->
{% for key in list_data.0.keys %}
<th>{{ key }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
<!-- 循环传过来的列表数据获得每一行的数据 -->
{% for item in list_data %}
<tr>
<!-- 循环得每一行的数据获得每列的单个value值 -->
{% for value in item.values %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</form>
模版文件views.py中的视图函数
def phone_list(request):
# 获取数据库数据
queryset = [
{"id": 1, "phone": 1452345125, "city": "北京"},
{"id": 2, "phone": 1434345125, "city": "深圳"},
{"id": 3, "phone": 1674345125, "city": "上海"}
]
# 将数据传给模版,并返回这个html页面
return render(request, './html/phone_list.html', {"list_data": queryset})
路由的配置
urlpatterns = [
path('phone_list/', phone_list)
]
项目运行后就可以通过 127.0.0.1:8000/phone_list/
发送请求获的渲染后的html模板,并显示在浏览器中
补充知识点:路由使用别名,html链接使用模版语法
html 模板
<a class="btn btn-primary" href="{% url "add_user" %}">添加用户</a>
<!-- 访问执行url并传入get参数 {% url 'edit_user' uid=row.id %} -->
<a class="btn btn-primary" href="{% url 'edit_user' uid=row.id %}">编辑</a>
urls 路由
urlpatterns = [
path('add/user/', add_user, name="add_user"), # 添加用户路由
# 通过路由设置 获取get参数 当请求url为 add/user/数字类型/ 时会执行指定的视图函数
path('add/user/<int:uid>/', edit_user, name="edit_user"), # 编辑用户路由
]
view视图函数
# 通过参数获取路由传过来的用户 uid
def edit_user(request, uid):
print(uid)
return
6.静态文件
可以在app应用包 中新建一个static 目录,用于存放静态文件
在模板文件中调用app下的static目录静态文件
{% load static %}
在html模板第一行添加这行代码,表示将静态文件前缀引入
{% static 'js/bootstrap-3.4.1-dist/js/jquery3-7-1.min.js' %}
使用这种方式导入静态文件
<!-- 导入静态配置前缀 -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 默认会在项目的目录下寻找指定文件,如果找不到再依据app注册顺序进行查找指定文件 -->
<link rel="stylesheet" href="{% static 'js/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"/>
<script src="{% static 'js/bootstrap-3.4.1-dist/js/jquery3-7-1.min.js' %}"></script>
<script src="{% static 'js/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
<!-- <img src="{% static 'img/02Q721YBPDW1_lead-720x1280.png' %}">-->
<table class="table table-striped">
<thead>
<tr>
{% for key in list_data.0.keys %}
<th>{{ key }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for item in list_data %}
<tr>
{% for value in item.values %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
7.登录练习
路由urls.py
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from web import views
urlpatterns = [
path('admin/', admin.site.urls), # djngo默认后台
path('login/', views.login), # 设置登录页面路由
path('index/', views.index), # 设置首页路由
]
视图页面 views.py
from django.shortcuts import render, redirect
from django.shortcuts import HttpResponse # 导入http响应类
def login(request):
# 判断请求的方法类是否为GET ,如果是则返回一个登录页面
if request.method == 'GET':
return render(request, 'html/login.html')
# 判断请求的方法是否为POST ,如果是则验证传过来的用户名和密码是否正确
if request.method == 'POST':
# 通过request的POST属性获得传过来的参数字典
# 再通过get('key') 获取用户名 (这个key是通过前端的name属性值获得)
user_name = request.POST.get('user')
pwd = request.POST.get('pwd')
if user_name == 'admin' and pwd == '123456':
# 跳转到首页,然后自动调用首页对应函数返回对应的数据到首页
# 重定向并携带参数 用户名
return redirect('/index/?user={}'.format(user_name))
else:
# 如果密码错误则 携带错误信息 返回登录页面
return render(request, 'html/login.html', {'error': '用户名或密码错误'})
def index(request):
# 获取数据库数据
queryset = [
{"id": 1, "phone": 1452345125, "city": "北京"},
{"id": 2, "phone": 1434345125, "city": "深圳"},
{"id": 3, "phone": 1674345125, "city": "上海"}
]
# 将请求对象中传过来的参数传给index.html 页面,让他渲染数据,然后在浏览器显示出来
return render(request, 'html/index.html', {'user': request.GET.get('user'), "list_data": queryset})
登录页面模板 login.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入bootstrap样式 -->
<link rel="stylesheet" href="{% static 'js/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"/>
<!-- 引入jquery.js -->
<script src="{% static 'js/bootstrap-3.4.1-dist/js/jquery3-7-1.min.js' %}"></script>
<!-- 引入bootstrap.js -->
<script src="{% static 'js/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<style>
.con{
position: relative;
width: 100vw;
height: 100vh;
background-color: pink;
}
.form-div{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 250px;
background-color: #666666;
padding: 10px;
}
.btn{
margin: 20px auto;
margin-left: 85px;
width: 100px;
}
</style>
<body>
<div class="con">
<div class="form-div container">
<form method="post" action="/login/">
<!-- Django 自动生成令牌 (隐藏的输入框) 不写这句代码会报错 -->
{% csrf_token %}
<div class="form-group">
<label for="exampleInputEmail1">用户名</label>
<!-- 必须给输入框设置一个name属性,这样后端才能通过name的值获得输入框中的数据 -->
<input type="text" class="form-control" id="exampleInputEmail1" placeholder="Username" name="user">
</div>
<div class="form-group">
<label for="exampleInputPassword1">密码</label>
<!-- 必须给输入框设置一个name属性,这样后端才能通过name的值获得输入框中的数据 -->
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" name="pwd">
</div>
<!-- 用户名或密码输入错误时返回的错误信息 -->
<span style="color: red;">{{ error }}</span>
<button type="submit" class="btn btn-default">提交</button>
</form>
</div>
</div>
</body>
</html>
首页模板 index.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 默认会在项目的目录下寻找指定文件,如果找不到再依据app注册顺序进行查找指定文件 -->
<link rel="stylesheet" href="{% static 'js/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"/>
<script src="{% static 'js/bootstrap-3.4.1-dist/js/jquery3-7-1.min.js' %}"></script>
<script src="{% static 'js/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<h1 class="h1" >首页 {{ user }}</h1>
<div class="container">
<table class="table table-striped">
<thead>
<tr>
{% for key in list_data.0.keys %}
<th>{{ key }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for item in list_data %}
<tr>
{% for value in item.values %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
配置文件 settings.py
import os
# 获取项目根目录路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 快速开始开发设置 - 不适用于生产环境
# 参考:https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# 密钥用于加密会话数据和其他安全机制
SECRET_KEY = 'y5lo9-3=ax5!nwgmh0tn4*lgr)2zkg^t64m&-2+lvh(xte*$o0'
# 调试模式:True 为开启,False 为关闭
DEBUG = True
# 允许访问的主机列表
ALLOWED_HOSTS = []
# 安装的应用程序列表
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 将应用包下的apps文件中的XXConfig 配置类,注册到应用列表中,
# 让模板配置可以找到对应应用的对应模板
'web.apps.WebConfig',
]
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# token令牌中间件,django会自动进行身份的验证,无需自行编写代码
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# 根 URL 配置 (根路由)
ROOT_URLCONF = 'firstdjango.urls'
# 模板配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'common')],
'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',
],
},
},
]
# WSGI 应用程序
WSGI_APPLICATION = 'firstdjango.wsgi.application'
# 数据库设置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# 密码验证器
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# 国际化设置
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# 静态文件 URL
STATIC_URL = '/static/'
8.ORM 表结构 (Django链接数据库)
概念
- ORM (object relation mapping) 对象关系映射
- 根据类创建数据库表
- 类名就是数据库表名,属性名就是表的列名
- 实例化一个对象就是新增一条数据
Django 创建表
-
先自己创建一个数据库
-
在models.py中创建一个表对应的类
from django.db import models # 会员表 class Member(models.Model): # 会员等级 member_level = models.CharField(verbose_name="会员等级", max_length=20) # 继承父类 Model # 类名会作为表名 并且加上模块名作为前缀,表名全变为小写 例如: web_userinfo class UserInfo(models.Model): # id = models.AutoField() # 默认会生成一个ID列自增,且为主键,不需要自己写 # 定义一个表字段 name # 设置它的数据类型为 varchar 类型,最大长度为16 # CharField 类型必须设置最大长度否则会报错 name = models.CharField(verbose_name="姓名", max_length=16) # 定义一个表字段 age # 设置它的数据类型为 int 类型,最大长度为3 age = models.IntegerField(verbose_name="年龄", max_length=3) # 定义一个表字段 email # 设置它的数据类型为 varchar 类型,最大长度为32 email = models.CharField(verbose_name="邮箱", max_length=32) # 定义一个表字段 password # 设置它的数据类型为 varchar 类型,最大长度为50 # 可以设置的参数有 # 表示该列的值可以为空 null=True, blank=True 两个参数最好联合使用 # 表示设置该列的默认值 default=“123456” password = models.CharField(verbose_name="密码", max_length=50) # 与会员表主键ID关联的外键字段 # 使用models的ForeignKey类实例化一个外键列 # 这个外键列在数据库中的命名规则为 属性名_id 如 member_id # 参数 to="Member" 表示要关联的表的类为会员表 # 参数 on_delete=models.CASCADE 表示当会员表的数据被删除时,用户表中关联的数据也会随之删除 # on_delete=models.SET_NULL 表示当会员表的数据被删除时,用户表中关联的外键值设置为空 需要额外设置参数(允许本列为空) null=True, blank=True # on_delete=models.SET_DEFAULT 表示当会员表的数据被删除时,用户表中关联的外键值设置为 需要额外设置默认值参数 default=1 member = models.ForeignKey(verbose_name="member表的ID", to="Member", default=1, on_delete=models.SET_NULL, null=True, blank=True)
-
修改 settings.py 配置文件,连接数据库
# 数据库设置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 连接的数据库类型为mysql 'NAME': 'test', # 数据库名 'USER': 'root', # 数据库用户名 'PASSWORD': 'root', # 数据库密码 'HOST': '127.0.0.1', # 主机ip 'PORT': 3306 # 端口号 } }
-
安装数据库模块
pip install pymysql pip install mysqlclient
-
执行命令创建表
# 读取已注册(在配置文件中已添加了app包路径)的所有app中的models.py中的所有类 # 根据models.py中的类生成配置文件,放到当前app的 migrations包下 python manage.py makemigrations # 根据migrations包下配置文件生成响应的SQl语句 并执行 (会生成Django内置的app的表,不必理会) python manage.py migrate
9.ORM 数据库操作
当models 对应的表映射类创建好了之后,就可以使用这个类执行各种数据库操作了
-
新增一条数据
from web.models import UserInfo # 向web_userinfo表中新增一条数据 UserInfo.objects.create(name="admin", age=18, email="1235@qq.com", password="123456") # 也可以传入一个字典类型,新增一条数据 UserInfo.objects.create(**{"name": "admin", "age": 18, "email": "1235@qq.com", "password": "123456"})
-
新增一条数据,并且这条数据包含一个外键列
from web.models import UserInfo from web.models import Member # 方法一 # 先查询到会员表中一行数据 member_obj = Member.objects.filter(id=1).first() # 再将这个对象作为值传入用户表的外键列中,进行用户数据的创建 UserInfo.objects.create(name="admin", age=18, email="1235@qq.com", password="123456", member=member_obj) # 方法二 # 直接给数据库的列表设置一个外键值, 注意:数据库中的外键是属性字段加_id UserInfo.objects.create(name="admin", age=18, email="1235@qq.com", password="123456", member_id=1)
-
一次性新增多条数据到数据库中
users = [ UserInfo(name='Tom', age=22, email="123422@qq.com", password="123456"), UserInfo(name='Jack', age=19, email="352643@qq.com", password="123456"), UserInfo(name='Mary', age=20, email="325678@qq.com", password="123456"), ] # 使用 bulk_create() 方法 传入一个列表,列表中的每一个元素就是一个UserInfo对象 # UserInfo对象中封装有需要新增的数据 # 将所有的数据新增到数据库表中 UserInfo.objects.bulk_create(users)
-
查询符合条件的所有数据
# 使用UserInfo类的 objects 对象的 filter() 方法 # 查找符合条件的数据 # 返回一个queryset列表,列表中含有一个或多个对象,每一个对象就是一条数据 # 如果未查询到数据返回值为空列表 [] # 知识点:id__gt=0 表示 id>0 这个条件, id__lt=5 表示 id<5 row_list = UserInfo.objects.filter(name="admin", age=18) # 查找姓名为admin且年龄为18的所有数据 # 获取第一行的所有数据 name = row_list[0].name # 获得第一行数据的姓名 age = row_list[0].age # 获得第一行数据的年龄 password = row_list[0].password # 获得第一行数据的密码 email = row_list[0].email # 获得第一行数据的邮箱
-
查询表中的所有数据
# 如果未查询到数据返回值为空列表 [] row_list = UserInfo.objects.all()
-
查询符合条件的第一条数据
# 使用get方法查询一条数据 (推荐使用) 注意这个方法只能传入一个参数 obj = UserInfo.objects.get(name="admin") # 查询一个用户对象,如果没查到则返回 404 obj = get_object_or_404(UserInfo, id=uid) # 返回一个obj对象,对象中包含符合条件的第一条数据 # 可以通过 obj.列名 获得该条数据对应的值 # 如果未查询到数据返回值为 None obj = UserInfo.objects.filter(name="admin", age=18).first() # 查找姓名为admin且年龄为18的所有数据
-
查询符合条件的数据并根据指定的指定排序
row_list = UserInfo.objects.filter(id__gt=0).order_by("id") # 根据id从小到大对查询到的数据进行排序, ASC row_list = UserInfo.objects.filter(id__gt=0).order_by("-id") # 根据id从大到小对查询到的数据进行排序,DESC
-
连表查询方式
# 查询用户表与会员表中关联的会员等级为 等级一 的所有用户数据 # 用户表映射类 member字段加两个下划线 加会员表映射类的字段名 member__member_level # 表示连表查询 会员表中的指定字段对应的 用户数据 queryset = UserInfo.objects.filter(member__member_level="等级一") # 获取数据时也可以通过 外键字段.主表(Member)的字段获得该条数据对应的列值 for row in queryset: # 通过用户表映射类的member字段.主表(Member)的字段获得该条数据对应的列值 print(row.member.member_level) # 打印本条用户数据对应的会员等级 # 无需导入Q 类的查询方法 # 字段名__startswith="ad" 查询某个字段以 ad开头的数据 obj = UserInfo.objects.filter(name__startswith="ad").first() # 字段名__startswith="ad" 查询某个字段以 ad开头的数据 obj = UserInfo.objects.filter(name__startswith="ad").first() # 字段名__contains="dm" 查询某个字段包含 dm 指定字符的数据 obj = UserInfo.objects.filter(name__contains="dm").first() # 字段名__endswith="in" 查询某个字段以 in 结尾的数据 obj = UserInfo.objects.filter(name__startswith="ad").first() # 字段名__gt=5 查询某个字段的值大于 5 的所有数据 # 字段名__gte=5 查询某个字段的值大于等于 5 的所有数据 # 字段名__lt=5 查询某个字段的值小于 5 的所有数据 obj = UserInfo.objects.filter(id__gt=5).first()
-
模糊查询方法
from django.db.models import Q # 导入模糊查询专用类 keyword = request.POST.get('search') # Q(要模糊查询的字段名__icontains= 模糊查询的关键字) 表示查询模糊查询这个表中的符合条件的数据 result = Employee.objects.filter(Q(emp_name__icontains=keyword)).first()
-
删除
# 删除该表中的所有数据 UserInfo.objects.all().delete() # 删除符合条件的所有数据 UserInfo.objects.filter(name="admin", age=18).delete() # 删除符合条件的第一条数据 UserInfo.objects.filter(id=5).first().delete()
-
修改
# 修改表中所有数据为指定的值 UserInfo.objects.all().update(age=20, name="null") # 修改符合条件的第一条数据的指定字段值 UserInfo.objects.filter(name="admin", age=18).first().update(age=20) # 修改指定id的指定字段值 UserInfo.objects.filter(id=6).update(age=20)
10.Cookie和Session
概念
- Cookie就是 Session ID 由服务器创建并返回给浏览器存储起来
- Session 中包含有 Session ID 和 存放用于识别用户的数据,如ID ,用户名
- 后端可以根据Cookie 去查找Session 中的数据,用于识别用户的状态
- 设置session过期时间60秒
request.session.set_expiry(60)
登录实例 视图 views.py 中的代码
# 登录函数
def login(request):
# 判断请求的方法类是否为GET ,如果是则返回一个登录页面
if request.method == 'GET':
return render(request, 'html/login.html')
# 判断请求的方法是否为POST ,如果是则验证传过来的用户名和密码是否正确
if request.method == 'POST':
# 通过request的POST属性获得传过来的参数字典
# 再通过get('key') 获取用户名 (这个key是通过前端的name属性值获得)
user_name = request.POST.get('user')
pwd = request.POST.get('pwd')
if user_name == 'admin' and pwd == '123456':
# Django会自动生成一个Session ID,作为Cookie传给浏览器
# Session中包含 session ID 和用户数据,用于区分不同的用户
# 使用请求对象的session属性给 session数据赋值
# 里面会将session ID作为Cookie返回给浏览器,让浏览器存储起来
request.session['username'] = {'user': user_name}
# 跳转到首页,然后自动调用首页对应函数返回对应的数据到首页
# 重定向并携带参数 用户名
# return redirect('/index/?user={}'.format(user_name))
return redirect('/index/')
else:
# 如果密码错误则 携带错误信息 返回登录页面
return render(request, 'html/login.html', {'error': '用户名或密码错误'})
# 首页视图函数
def index(request):
# 获得UserInfo 表中的所有数据,并排序
queryset = UserInfo.objects.all().order_by("id")
# 获得session中的登录的用户名
dict_session = request.session.get('username')
# 判断用户是否登录,如果未登录则返回登录页面
if dict_session is None: # session中没有数据,则跳转到登录页面
# print("信息", dict_session)
return redirect('/login/')
# 将请求对象中传过来的参数传给index.html 页面,让他渲染数据,然后在浏览器显示出来
return render(request, 'html/index.html', {'user': dict_session['user'], "list_data": queryset})
11.中间件
概念
-
中间件就是用于处理请求对象和响应对象
-
在 到达视图函数之前 和 到达浏览器之前 对这些对象进行处理
-
注意事项:在中间件中拦截的请求对象,一般需要直接放行登录页面和静态文件
-
步骤
-
第一步先在项目根目录下创建一个中间件的包
-
在包中新建一个中间件
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render, redirect import re # 继承Django的 父类中间件 MiddlewareMixin class LoginMiddleware(MiddlewareMixin): # 重写父类的 process_request() 方法 # 用于处理浏览器发送的请求数据 def process_request(self, request): pattern = r'/vue/.*' # 路径正则表单式,用于方向指定的一部分路径 # 如果是 登录页面 和 验证码请求,直接将请求对象传给view视图进行处理 # re.match(pattern, request.path_info) re.match() 判断请求的路径是否符合规则,返回一个布尔值 if request.path_info == "/login/" or request.path_info == '/captcha/' or re.match(pattern, request.path_info): return None # 获得session中的登录的用户名 dict_session = request.session.get('username') # 判断用户是否登录,如果未登录则返回登录页面,除了login以外的页面都需要进行判断 if dict_session is None: # session中没有数据,则跳转到登录页面 print("信息", dict_session) return redirect('/login/') # 给request新增一个自定义属性,并赋值,方便在view视图中获得需要的数据 request.dict_session = dict_session # 返回值为空时,会将请求对象传给视图的对应方法 # 返回值为其他类型时,会直接调用自身的process_response() 方法,将数据返回给浏览器 return None # 重写父类的 process_response() 方法 # 用于处理服务端的响应数据对象 def process_response(self, request, response): return response
-
在settings.py 配置文件中注册中间件
# 中间件 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # token令牌中间件,django会自动进行身份的验证,无需自行编写代码 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', # 注册 自定义的登录中间件类 'middleware.check_login_middleware.LoginMiddleware', ]
-
views.py 视图中的代码
from django.shortcuts import render, redirect from django.shortcuts import HttpResponse # 导入http响应类 from web.models import UserInfo # 登录函数 def login(request): # 使用HttpResponse类 # 传入响应数据,实例化一个响应对象 # 然后将响应对象返回给接口请求者,如浏览器、前端、ApiPost等 # 返回的直接就是一个文本类型 # return HttpResponse("登录页面") # 传入请求对象和一个Html页面的路径,表示返回一个Html页面 # 默认的根目录需要现在配置文件中配置 TEMPLATES{{ 'DIRS': [os.path.join(BASE_DIR, 'templates')] },} # 配置了之后 templates文件夹就是模板的根目录 将当前目录下的html文件中的login.html文件返回给请求者 # return render(request, './html/login.html') # 表示重定向到百度页面 # return redirect("https://www.baidu.com") # 判断请求的方法类是否为GET ,如果是则返回一个登录页面 if request.method == 'GET': return render(request, 'html/login.html') # 判断请求的方法是否为POST ,如果是则验证传过来的用户名和密码是否正确 if request.method == 'POST': # 通过request的POST属性获得传过来的参数字典 # 再通过get('key') 获取用户名 (这个key是通过前端的name属性值获得) user_name = request.POST.get('user') pwd = request.POST.get('pwd') if user_name == 'admin' and pwd == '123456': # Django会自动生成一个Session ID,作为Cookie传给浏览器 # Session中包含 session ID 和用户数据,用于区分不同的用户 # 使用请求对象的session属性给 session数据赋值 # 里面会将session ID作为Cookie返回给浏览器,让浏览器存储起来 request.session['username'] = {'user': user_name} # 跳转到首页,然后自动调用首页对应函数返回对应的数据到首页 # 重定向并携带参数 用户名 # return redirect('/index/?user={}'.format(user_name)) return redirect('/index/') else: # 如果密码错误则 携带错误信息 返回登录页面 return render(request, 'html/login.html', {'error': '用户名或密码错误'}) # 退出登录 def exit(request): # 清空Session状态 request.session.clear() # 重定向到登录页面 return redirect('/login/') # 首页视图函数 def index(request): # 获得UserInfo 表中的所有数据,并排序 queryset = UserInfo.objects.all().order_by("id") # 获得session中的登录的用户名, 获得中间件中自定义的属性 dict_session = request.dict_session # 判断用户是否登录,如果未登录则返回登录页面 # if dict_session is None: # session中没有数据,则跳转到登录页面 # # print("信息", dict_session) # return redirect('/login/') # 将请求对象中传过来的参数传给index.html 页面,让他渲染数据,然后在浏览器显示出来 return render(request, 'html/index.html', {'user': dict_session['user'], "list_data": queryset})
-
12. HTML母板继承
步骤
-
第一步在templates 目录下新建一个母板html文件
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <!-- 默认会在项目的目录下寻找指定文件,如果找不到再依据app注册顺序进行查找指定文件 --> <!-- 引入bootstrap样式 --> <link rel="stylesheet" href="{% static 'js/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"/> <!-- 引入jquery.js --> <script src="{% static 'js/bootstrap-3.4.1-dist/js/jquery3-7-1.min.js' %}"></script> <!-- 引入bootstrap.js --> <script src="{% static 'js/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script> {% block css %} {% endblock %} </head> <body> {% block content %} {% endblock %} </body> {% block js %} {% endblock %} </html>
-
第二步,在其他html页面继承母板,即可
<!-- 继承母板 --> {% extends 'html/base.html' %} <!-- 本页面的css代码和标题信息 --> {% block css %} <title>修改用户信息</title> {% endblock %} <!-- 主体内容 --> {% block content %} <form method="POST" action="/edit/userinfo/"> {% csrf_token %} <input type="hidden" value="{{ user_obj.id }}" name="id" placeholder="" /> <input type="text" value="{{ user_obj.name }}" name="name" placeholder="请输入用户名" /> <input type="number" value="{{ user_obj.age }}" name="age" placeholder="请输入年龄" /> <input type="text" value="{{ user_obj.email }}" name="email" placeholder="请输入邮箱" /> <button type="submit"> 提交 </button> </form> {% endblock %} <!-- 本页面的js代码 --> {% block js %} {% endblock %}
13. Form组件生成输入框和验证提交的数据
Django中的Form组件的作用
- 生成HTML表单标签
- 数据校验
使用步骤
-
在应用包下新建一个forms包,定义页面对应的Form组件类
from django import forms from django.core.validators import RegexValidator # 导入正则验证类 # 继承From组件父类 class AddForm(forms.Form): # 给属性赋值,将input输入框赋值给这个变量,变量名会作为 input输入框的 name属性 # 使用forms文件的 CharField() 类创建一个html文本对象,用于在html页面显示输入框 name = forms.CharField( label="用户名", # 使用forms文件的 TextInput() 类创建一个文本类型的输入框,并且传入参数 attrs设置输入框的默认属性 widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入用户名"}), ) age = forms.CharField( label="年龄", widget=forms.NumberInput(attrs={"class": "form-control", "placeholder": "请输入年龄"}), validators=[RegexValidator(r'^[0-9]{1,3}$', '请输入正确的年龄')], # 修改默认非空验证的提示文字 error_messages={'required': '验证码不能为空'} ) # 使用forms文件的 EmailField() 类创建一个html邮箱输入框文本对象,用于在html页面显示输入框 email = forms.EmailField( label="邮箱", widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入邮箱"}), # required=False # 表示该字段允许为空 ) password = forms.CharField( label="密码", # 使用forms文件的 PasswordInput() 类创建一个密码类型的输入框,并且传入参数 attrs设置输入框的默认属性 # render_value=True 参数表示让密码的默认值可以在前端显示 '......' widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "请输入密码"}, render_value=True) ) # 使用forms文件的 ChoiceField() 类创建一个选择类型html表单文本对象,用于在html页面显示输入框 member_id = forms.ChoiceField( # choices=[(1, '等级一'), (2, '等级二'), (3, '等级三')] # 设置选项初始值 choices=[], # 使用forms文件的 Select() 类创建一个下拉框,并且传入参数 attrs设置输入框的默认属性 widget=forms.Select(attrs={"class": "form-control"}), required=False # 表示该字段允许为空 ) # 构造方法 def __init__(self, *args, **kwargs): # 在关键字参数字典中提取 choices变量的值,并在字典中删除,如果这个没有这个参数则返回一个 None choices = kwargs.pop('choices', None) # 调用父类的初始化方法,初始化表单的各个字段,前提:必须先将自定义的参数获得 super().__init__(*args, **kwargs) # 判断如果choices有值则将这些选项赋值给下拉框选项渲染 if choices: # 给自身的 member_id里的 choices参数赋值 self.fields['member_id'].choices = choices
-
views视图中的登录函数的代码
# 新增用户 def add_user(request): # 将会员表所有数据查询出来并传给添加页面渲染 members = Member.objects.all() # 定义一个下拉选项的列表,用于初始化下拉框 choices = [] for row in members: # 将会员表中的每一行组合和一个元组元素添加到 choices列表中 choices.append((row.id, row.member_level)) # 判断请求的方式,如果为GET返回一个新增页面 if request.method == "GET": # 将所有输入框封装到对象中并传给新增页面 并给输入框 赋默认值 # initial 参数表示给输入框赋默认值,传入一个字典 name属性的值作为 key ,要设置的默认值为value # 例如:initial={'name': '用户名', 'password': '123456', 'email': '214432@qq.com'} # 实例化一个form对象,用于将添加数据的输入框传给前端 form = AddForm(choices=choices) # 给自定义的初始化参数 choices 赋值,表示设置下拉选项的值 return render(request, 'html/add_user.html', {'members': members, 'form': form}) # 否则就是POST请求,将数据添加到数据库,并回到首页 else: # 将POST请求的数据交给Form组件进行验证 form = AddForm(choices=choices, data=request.POST) # 使用 form对象的 is_valid() 方法对数据进行验证,Django会对不同类型的输入框进行不同的验证 # 也可以在对应 From类中 自定义验证规则 if form.is_valid(): # 如果验证为True 就将数据存入数据库 name = request.POST.get("name") age = request.POST.get("age") email = request.POST.get("email") password = request.POST.get("password") member_id = request.POST.get("member_id") UserInfo.objects.create(name=name, age=age, email=email, password=password, member_id=member_id) # 返回首页 return redirect('/index/') else: print(form.errors) # 否则将错误验证提交回添加页面进行渲染告知用户 # 将携带错误信息的form对象和里面的数据一并提交给前端 return render(request, 'html/add_user.html', {'form': form})
-
添加数据页面的代码
<!-- 继承母板 --> {% extends 'html/base.html' %} <!-- 本页面的css代码和标题信息 --> {% block css %} <title>新增用户</title> {% endblock %} <!-- 主体内容 --> {% block content %} <div class="container" style="margin-top: 30px"> <form class="form-inline" method="post" action="/add/userinfo/"> {% csrf_token %} <!-- 获取传过来的form对象,并从里面获得每一个输入框,如果有错误信息回携带错误信息显示第一条错误信息 --> <p>{{ form.name }}<span>{{ form.name.errors.0 }}</span></p> <p>{{ form.age }}<span>{{ form.age.errors.0 }}</span></p> <p>{{ form.email }}<span>{{ form.email.errors.0 }}</span></p> <p>{{ form.password }}<span>{{ form.password.errors.0 }}</span></p> <p>{{ form.member_id }}<span>{{ form.member_id.errors.0 }}</span></p> <button type="submit"> 提交 </button> </form> </div> {% endblock %} <!-- 本页面的js代码 --> {% block js %} {% endblock %}
-
配置文件将验证提示设置为中文
LANGUAGE_CODE = 'zh-hans' # 将Form验证提示信息改为中文
**补充知识点: ** 获取数字值关联的文本数据
def admin_list(request):
print("用户列表")
queryset = Admin.objects.all()
# 可用通过 每一条数据的对象的 get_(ModelForm中的字段名)_display() 获取它的 choice对应的文本数据
# 在html页面中调用 get_gender_display() 时不需要加 ()括号 他会自动加
gender_text = queryset[0].get_gender_display()
# 获取用户表关联的 部门表的文本标题, (会自动连表查询)
title_text = queryset[0].depart.title
print(gender_text, title_text)
# 返回用户列表页面并将Session中的用户名返回,让他渲染
return render(request, 'admin_list.html', {
'username': request.session['info']['username'],
"queryset": queryset
})
14.ModelForm关联Model的Form组件
使用步骤
-
定义一个ModelForm类继承 forms.ModelForm , 并关联对应的模型表映射类
from django import forms from django.core.validators import RegexValidator # 导入正则验证类 from web.models import UserInfo import re class LoginFrom(forms.ModelForm): # 重写嵌套类 元数据类 class Meta: # 关联模型类 model = UserInfo # 让表单关联模型的指定字段 # 定义一个数组变量, 每个元素就是模型中对应的字段名 # 这里会自动生成对应 输入框name属性值为模型的字段名 # 模型类中的verbose_name参数的值会赋值给 输入框的label属性 fields = ['name', 'password'] # 让表单关联模型的所有字段 # fields = "__all__" # 设置对应输入框的属性和值 widgets = { # 设置name自动输入框对应的值 'name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入用户名', 'id': 'exampleInputEmail1'}), # render_value=True 验证失败时不清空密码 'password': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入密码', 'id': 'exampleInputPassword1'}, render_value=True) } # 修改Django默认的非空验证提示 error_messages = { 'name': {'required': '用户名不能为空'}, 'password': {'required': '密码不能为空'}, } # 重写验证密码的规则 clean_加模型字段名 def clean_password(self): password = self.cleaned_data.get('password') if not re.match(r'^.{6,18}$', password): # 引发一个forms.ValidationError 异常将错误信息返回给页面 raise forms.ValidationError('请输入6-18位密码') # 验证通过正常返回密码 return password # 给ModelForm 重写初始化构造方法 def __init__(self, *args, **kwargs): # 执行父类的构造方法 super().__init__(*args, **kwargs) # 循环 自身的所有字段(fields是一个字典类型)的items方法的返回值 获得 每一个字段的名称和封装的对象 # 也可以只拿对象 for field_obj in self.fields.values(): for name, field_obj in self.fields.items(): # 给自身定义的所有字段设置 输入框属性, 如果前面已经给某个自身设置了widget 则这里不会生效 field_obj.widget.attr = {'class': 'form-control'}
-
示例
ModelForm新增数据
# 实例化自定义的ModelForm类,传入请求的数据,获得一个ModelForm表单对象 form = AddForm(request.POST) # 对数据进行操作前需要验证数据完整性,会去数据库查找对应的表的数据是否存在等操作 if form.is_valid(): # 直接调用表单对象的save方法,将提交的数据保存到ModelForm关联的数据库表中 form.save()
ModelForm更新数据库数据
from web.forms.user_form import UserModelForm # GET传过来的要编辑的数据的ID id = request.GET.get('id') # 从数据库查询到该条数据的对象格式 user_obj = UserInfo.objects.filter(id=id).first() if request.method == 'GET': # 将这个对象通过ModelForm的 instance参数交给表单对象渲染展示给用户 form = UserModelForm(instance=user_obj) return render(request, '/html/edit_user.html', {'form': form}) else: # 传入两个参数,data 请求体的数据, instance 确认要修改的是哪条数据(这条数据的对象格式) form = UserModelForm(data=request.POST, instance=user_obj) # 对提交过来的ModelForm表单对象进行合法性校验 if form.is_valid(): # 验证通过保存修改后的数据到数据库中 form.save() return redirect('/index/') else: # 否则携带错误信息返回编辑页面 return render(request, '/html/edit_user.html', {'form': form})
完整代码示例
-
在应用包下的新建一个 forms包,在包里新建一个 ModelForm 文件
edit_form.py 文件代码
from django import forms from web.models import Member, UserInfo # 继承From组件父类 class AddForm(forms.ModelForm): # 在Meta类外部自定义下拉框,并将数据库的数据查询出来赋值到下拉框 # ModelChoiceField 关联模型类的字段 member = forms.ModelChoiceField( queryset=Member.objects.all(), # 将会员数据查询出 Django 会自动将查询的数据赋值给每一个Option widget=forms.Select(attrs={"class": "form-control", 'blank_label': '请选择'}), # 设置输入框的类型为下拉框并设置属性 empty_label='请选择', # 设置空白选项的文本 ) class Meta: # 关联用户模型类 model = UserInfo # 选择要生成表单的字段 fields = ['name', 'password', 'age', 'email', 'member'] # 设置每个字段的输入框类型,并设置输入框的属性 widgets = { 'name': forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入用户名"}), 'password': forms.PasswordInput(attrs={"class": "form-control", "placeholder": "请输入密码"}), 'age': forms.NumberInput(attrs={"class": "form-control", "placeholder": "请输入年龄"}), 'email': forms.EmailInput(attrs={"class": "form-control", "placeholder": "请输入邮箱"}), } def clean_age(self): # 获取请求时传过来的age输入框的值, 并将年龄转为字符串也便正则表达式能够匹配 age = str(self.cleaned_data.get('age')) # 对年龄进行正则验证 if not re.match(r'^[0-9]{1,3}$', age): # 如果为False表示验证失败,引发自定异常 # 引发一个forms.ValidationError 异常将错误信息返回给页面 raise forms.ValidationError('请输入正确年龄') # 否则验证正确返回 age 的值给views视图进行逻辑处理 return age
-
models.py 中要重写下拉选项的打印方法
# 会员表 class Member(models.Model): # 会员等级 member_level = models.CharField(verbose_name="会员等级", max_length=20) # 重写打印方法输入会员等级,让其可以在下拉列表显示 def __str__(self): return self.member_level
-
视图文件代码
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import HttpResponse # 导入http响应类 from web.models import UserInfo from web.models import Member from web.forms.add_form import AddForm # 导入自定义的 Form 组件类 from web.forms.login_form import LoginFrom # 导入自定义的 Form 组件类 from web.forms.edit_form import EditModelForm # 导入自定义的 Form 组件类 # 新增用户 def add_user(request): # 将会员表所有数据查询出来并传给添加页面渲染 members = Member.objects.all() # 判断请求的方式,如果为GET返回一个新增页面 if request.method == "GET": # 实例化一个form对象,用于将添加数据的输入框传给前端 form = AddForm() # 给自定义的初始化参数 choices 赋值,表示设置下拉选项的值 # print(form) return render(request, 'html/user_form.html', {'members': members, 'form': form}) # 否则就是POST请求,将数据添加到数据库,并回到首页 else: # 将POST请求的数据交给Form组件进行验证 form = AddForm(data=request.POST) # 使用 form对象的 is_valid() 方法对数据进行验证,Django会对不同类型的输入框进行不同的验证 # 也可以在对应 From类中 自定义验证规则 if form.is_valid(): # 如果验证为True 就将数据存入数据库 # 将提交过来的数据可保存到ModelForm关联的对应表中 form.save() # 返回首页 return redirect('/index/') else: print(form.errors) # 否则将错误验证提交会添加页面进行渲染告知用户 # 将携带错误信息的form对象和里面的数据一并提交给前端 return render(request, 'html/user_form.html', {'form': form}) # 根据id删除用户数据函数 def delete_user(request): # 获得传过来的id user_id = request.GET.get("id") # print(user_id) u = UserInfo.objects.filter(id=user_id).delete() # print(u) return redirect('/index/') # 根据id查找用户数据然后修改数据 def edit_user(request): # 获取GET请求传过来的用户ID user_id = request.GET.get("id") print('get获取的ID', user_id) if user_id is None: print("未查询到数据") return redirect('/index/') # print(user_id) # 根据ID去用户表查询这条数据,如果有则返回这条数据对应的对象,否则返回404页面 instance = get_object_or_404(UserInfo, id=user_id) # 判断请求方式 如果是GET方法,返回一个页面 if request.method == "GET": # 将对应的用户数据封装到ModelForm表单中让页面渲染 form = EditModelForm(instance=instance) return render(request, 'html/user_form.html', {'form': form}) # POST 请会执行以下代码 # 给ModelForm类传入两个参数 data 表示请求的数据体,instance表示要修改哪个用户的数据 form = EditModelForm(data=request.POST, instance=instance) # 进行完整性验证 if form.is_valid(): print(user_id) # 将提交过来的数据保存到对应的用户数据上 form.save() # 修改完成后跳转回首页 return redirect("/index/") else: # 否则携带错误信息返回编辑页面 return render(request, 'html/user_form.html', {'form': form})
user_form.html页面代码
<!-- 继承母板 --> {% extends 'html/base.html' %} <!-- 本页面的css代码和标题信息 --> {% block css %} <title>新增用户</title> {% endblock %} <!-- 主体内容 新增用户和修改用于共用一个页面模板 --> {% block content %} <div class="container" style="margin-top: 30px"> <!-- 不要写action让它提交到当前页面,当前页面会有ID存在并传回后端 --> <form class="form-inline" method="post"> {% csrf_token %} <!-- 循环传过来的表单对象 --> {% for obj in form %} <p>{{ obj }}<span>{{ obj.errors.0 }}</span></p> {% endfor %} <button class="btn btn-success" type="submit"> 提交 </button> </form> </div> {% endblock %} <!-- 本页面的js代码 --> {% block js %} {% endblock %}
15.生成图片验证码
Django中生成验证码图片步骤
-
安装 Pillow 模块
pip install Pillow
-
配置 settings.py 静态文件根路径
# 静态文件路径设置 STATIC_URL = '/static/' # 用于在视图文件中可用访问到app 下面的静态文件 STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'web', 'static'), ]
-
在 views.py 视图文件中 创建验证码视图类
from django.views.generic import View from django.http import HttpResponse from PIL import Image, ImageDraw, ImageFont import random # 导入静态根路径类 from django.contrib.staticfiles import finders # 生成验证码图片类视图 # Django 提供了一个内置的视图函数 django.views.generic.base.View,可以用于生成验证码图片 # 继承View父类用于生成验证码图片 class CaptchaView(View): # 当路由配置中调用了as_view()方法是会自动执行与请求方式向对应的函数 # CaptchaView 类中函数的命名 只能使用 get、post、put def get(self, request, *args, **kwargs): # 生成随机验证码 code = ' '.join(random.sample('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 4)) print(code) # 将验证码存储在session中 request.session['captcha'] = code # 创建一个画布 img = Image.new(mode='RGB', size=(130, 50), color=(255, 255, 255)) draw = ImageDraw.Draw(img) # 设置字体和字体大小 font_path = finders.find('fonts/Kratos-TrueType-1.ttf') font_size = 30 font = ImageFont.truetype(font_path, font_size) # 在画布上绘制验证码 text_color = (0, 0, 0) draw.text((10, 10), code, fill=text_color, font=font) # 添加干扰线 line_color = (100, 100, 100) for _ in range(5): x1 = random.randint(0, 150) y1 = random.randint(0, 50) x2 = random.randint(0, 150) y2 = random.randint(0, 50) draw.line([(x1, y1), (x2, y2)], fill=line_color, width=2) # 删除画布对象 del draw # 将图片转换为二进制流并返回HttpResponse对象,用于在img标签显示图片 response = HttpResponse(content_type="image/png") img.save(response, "PNG") return response
-
在 urls.py 中添加路由
urlpatterns = [ path('login/', login, name="login"), path('', host), # 调用CaptchaView 类视图的as_view()方法执行请求方式对应的函数 # name属性用于在模版页面使用模版语法 触发指定链接 如 {% url 'captcha' %} path('captcha/', CaptchaView.as_view(), name='captcha'), ]
-
html页面中使用模版语法调用路由
<!-- 主体内容 --> {% block content %} <img src="{% url 'captcha' %}" id="captcha" style="width:120px"/> {% endblock %} <!-- 本页面的js代码 --> {% block js %} <script> // 入口函数,当页面加载完毕后执行里面的代码 $(function(){ // 设置id为 captcha 标签 单击时触发的事件 $("#captcha").click(function(){ // 获取这个标签的 src 属性的值,存入变量中 let url = $(this).attr('src'); // 修改这个标签的src属性的值,最后才向后端发送请求,实现点击图片刷新验证码的功能 $(this).attr('src', url + '?') }) }) </script> {% endblock %}
16.Django的哈希密码验证
使用步骤
-
在视图views.py 中自定义一个验证密码的方法
from django.contrib.auth.hashers import make_password, check_password # 导入密码封装和比较的两个方法 # 验证密码的方法 def check_user(username, password): try: print("用户名和密码", username, password) user = Admin.objects.get(username=username) # 使用check_password方法验证输入框中未加密的密码和数据库中已加密的密码是否相等 if check_password(password, user.password): # 如果正确返回这个用户对象 return user # 密码不匹配返回一个None return None except Admin.DoesNotExist: # get方法查询不到数据会发生异常,返回一个空对象 return None # 登录方法的代码 def login(request): ... # 判断验证码是否正确 if input_captcha == session_captcha: print("验证通过", input_captcha, session_captcha) # 调用自定义的方法根据用户查询数据库数据并比较密码是否正确,正确则返回该用户对象,否则返回None user_obj = check_user(form.cleaned_data['username'], form.cleaned_data['password']) # 如果有数据就表示登录成功,否则登录失败 if user_obj: print("登录成功") # 将用户信息存入Session中 request.session['info'] = {"id": user_obj.id, "username": user_obj.username} # 重新设置Session过期时间为 7 天 request.session.set_expiry(60 * 60 * 12 * 7) return render(request, 'login.html', {"form": form}) else: print("登录失败") return render(request, 'login.html', {"form": form, "error": "用户名或密码错误"}) else: # 验证码错误返回错误信息 print("验证码错误", input_captcha, session_captcha) # 给表单对象的指定字段添加错误信息,然携带所有错误信息返回页面 form.add_error('code', '验证码错误') return render(request, 'login.html', {"form": form})
17.修改ModelForm的下拉框首选项文本
from django import forms
from web.models import Employee
class AddEmpModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AddEmpModelForm, self).__init__(*args, **kwargs)
# 将自身的 depart 字段的空选项修改默认值
self.fields['depart'].empty_label = '请选择'
class Meta:
model = Employee
fields = ['emp_name', 'gender', 'age', 'id_card_number', 'phone_number', 'depart']
# 设置对应输入框的属性和值
widgets = {
'emp_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入用户名'}),
'gender': forms.Select(attrs={'class': 'form-control'}),
'age': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': '请输入年龄'}),
'id_card_number': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入身份证号'}),
'phone_number': forms.TextInput(attrs={'class': 'form-control', 'placeholder': '请输入手机号'}),
'depart': forms.Select(attrs={'class': 'form-control'}),
}
# 修改Django默认的非空验证提示
error_messages = {
'emp_name': {'required': '用户名不能为空'},
'age': {'required': '年龄不能为空'},
'id_card_number': {'required': '身份证号不能为空'},
'phone_number': {'required': '手机号码不能为空'},
}
18. 对查询的数据进行分页
视图函数
from django.core.paginator import Paginator # 导入Django的内置分页器类
# 首页视图函数
def index(request):
queryset = Employee.objects.all()
# 获取数字对应的文本内容
# print(queryset[0].get_gender_display())
# 跨表联查数据
# print(queryset[0].depart.superior_number)
# 获取传过来的 page参数 作为当前显示的页码,如果没有传参则设置为 默认第一页
page_number = request.GET.get('page', 1)
# 实例化一个分页器对象,传入所有要显示的数据,并指定每页条数
paginator = Paginator(queryset, 5)
# 根据当前的页码获取,该页面对应的分页数据
# paginator 分页对象的 get_page() 方法传入当前页码获取本页对应的数据 和分页数据
page = paginator.get_page(page_number)
# 使用 paginator.num_pages 获取数据的总页数据
max_index = paginator.num_pages
# 计算当前页的起始页(显示在第一个页码按钮的位置)当前页减3 或者第一页
start_index = max(1, page.number - 3)
# 获得前端分页组件的结束页 (显示在第七个页码按钮的位置)起始页加 6 或 最后一页
end_index = min(start_index + 6, paginator.num_pages)
# 当数据不足7页时,显示所有的页码 (并且末页时显示7个页码)
if end_index - start_index < 6:
start_index = max(1, end_index - 6)
# 显示页码数据
pages = range(start_index, end_index + 1)
return render(request, 'index.html', {
'page': page, # 返回页面数据,和分页数据
'max_index': max_index, # 返回总页码数量
'pages': pages
})
html模板代码
<div class="panel-body">
<form class="form-group" method="post" novalidate>
{% csrf_token %}
<table class="table table-striped">
<thead>
<tr>
<td>员工姓名</td>
<td>性别</td>
<td>手机号码</td>
<td>身份证号</td>
<td>年龄</td>
<td>入职时间</td>
<td>所在部门</td>
<td>操作</td>
</tr>
</thead>
<tbody>
{% for emp_obj in page %}
<tr>
<td>{{ emp_obj.emp_name }}</td>
<td>{{ emp_obj.get_gender_display }}</td>
<td>{{ emp_obj.phone_number }}</td>
<td>{{ emp_obj.id_card_number }}</td>
<td>{{ emp_obj.age }}</td>
<td>{{ emp_obj.hire_data }}</td>
<td>{{ emp_obj.depart }}</td>
<td>
<a href="#" class="btn btn-danger btn-sm delete-btn" data-toggle="modal" data-target="#myModal"
data-id="{{ emp_obj.id }}" data-name="{{ emp_obj.emp_name }}">
<span class="glyphicon glyphicon-trash"></span>
</a>
<a href="{% url 'edit_emp' eid=emp_obj.id %}" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-edit"></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
<ul class="pagination">
<!-- page.has_previous (Django提供的分页对象的属性) 判断是否有上一页,从而改变上一页和首页页按钮的样式 -->
{% if page.has_previous %}
<li><a href="?page=1">首页</a></li>
<li><a href="?page={{ page.previous_page_number }}">上一页</a></li>
{% else %}
<!-- 没有上一页数据,按钮改为禁用 -->
<li class="disabled"><span>首页</span></li>
<li class="disabled"><span>上一页</span></li>
{% endif %}
<!-- 循环需要显示的分页按钮对象 -->
{% for i in pages %}
<!-- 如果当前页码等于 i 则让页码按钮选中 -->
{% if i == page.number %}
<!-- 循环将每一页的页码添加到链接中 -->
<li class="active"><a href="?page={{ i }}">{{ i }}</a></li>
{% else %}
<!-- 否则页码按钮无样式 -->
<li><a href="?page={{ i }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
<!-- page.has_next (Django提供的分页对象的属性) 判断是否有下一页,从而改变下一页和末页按钮的样式 -->
{% if page.has_next %}
<li><a href="?page={{ page.next_page_number }}">下一页</a></li>
<li><a href="?page={{ max_index }}">末页</a></li>
{% else %}
<!-- 没有下一页数据,按钮改为禁用 -->
<li class="disabled"><span>下一页</span></li>
<li class="disabled"><span>末页</span></li>
{% endif %}
</ul>
<p class="">总数量:{{ page.paginator.count }} 条</p>
</div>
方式二:对分页器进行二次封装的分页类
在应用目录下新建一个utils 包,在包里新建一个分页工具类 pagination.py
from django.core.paginator import Paginator # Django分页类
class Pagination:
def __init__(self, request, queryset, page_total_num=5, ba_number=3):
"""
分页工具类
:param request: 请求对象
:param queryset: 要进行分页的所有数据
:param page_total_num: 每页显示的数据条数 默认为显示5条数据
:param ba_number: 当前页前后显示的页码数量 默认当前页前后都显示三个页码
"""
self.request = request
self.queryset = queryset
self.page_total_num = page_total_num
self.ba_number = ba_number
def get_page_data(self):
# 创建一个 Paginator 对象,并指定每页显示的数据条数
paginator = Paginator(self.queryset, self.page_total_num)
# 获取当前页数,如果没有指定则默认为第一页
page_number = self.request.GET.get('page', 1)
# 获取当前页的数据
page = paginator.get_page(page_number)
# paginator 的 page_range 属性是一个列表,列表里面包含多个有效的页码元素
# 使用 len函数 获取数据的总页数
# max_index = len(paginator.page_range)
# 使用 paginator.num_pages 获取数据的总页数据
max_index = paginator.num_pages
# 计算当前页的起始页(显示在第一个页码按钮的位置)当前页减3 或者第一页
start_index = max(1, page.number - self.ba_number)
# 获得前端分页组件的结束页 (显示在第七个页码按钮的位置)起始页加 6 或 最后一页
end_index = min(start_index + (self.ba_number * 2), paginator.num_pages)
# 当数据不足7页时,显示所有的页码 (并且末页时显示7个页码)
if end_index - start_index < (self.ba_number * 2):
start_index = max(1, end_index - (self.ba_number * 2))
# 组件显示的页码数据
pages = range(start_index, end_index + 1)
# 将所需的页面数据page、 总页码数量max_index 和 前端分页组件的显示对象pages 返回
page_dict = {"page": page, "max_index": max_index, "pages": pages}
return page_dict
视图函数
from web.utils.pagination import Pagination # 导入自定义的分页工具类
def position_list(request):
# 查询职位数据
queryset = Position.objects.all()
# 传入 request 对象和需要分页的数据集,以及每页显示的数据条数据和当前页前后显示的页码按钮个数
pagination = Pagination(request, queryset, 10, 2)
# 获得所需的页面数据和总页码以及 分页对象
page_dict = pagination.get_page_data()
# 将数据返回给模板渲染
return render(request, 'position_list.html', {
'page': page_dict["page"], # 返回页面数据,和分页数据
'max_index': page_dict["max_index"], # 返回总页码数量
'pages': page_dict["pages"]
})
html模板代码
<div class="panel-body">
<form class="form-group" method="post" novalidate>
{% csrf_token %}
<table class="table table-striped">
<thead>
<tr>
<td>职位名称</td>
<td>职位描述</td>
<td>操作</td>
</tr>
</thead>
<tbody>
{% for position_obj in page %}
<tr>
<td>{{ position_obj.position_name }}</td>
<td>{{ position_obj.position_description }}</td>
<td>
<a href="#" class="btn btn-danger btn-sm delete-btn" data-toggle="modal" data-target="#myModal"
data-id="{{ position_obj.id }}" data-name="{{ position_obj.position_name }}">
<span class="glyphicon glyphicon-trash"></span>
</a>
<a href="{% url 'edit_position' pid=position_obj.id %}" class="btn btn-primary btn-sm">
<span class="glyphicon glyphicon-edit"></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
<ul class="pagination">
<!-- page.has_previous (Django提供的分页对象的属性) 判断是否有上一页,从而改变上一页和首页页按钮的样式 -->
{% if page.has_previous %}
<li><a href="?page=1">首页</a></li>
<li><a href="?page={{ page.previous_page_number }}">上一页</a></li>
{% else %}
<!-- 没有上一页数据,按钮改为禁用 -->
<li class="disabled"><span>首页</span></li>
<li class="disabled"><span>上一页</span></li>
{% endif %}
<!-- 循环需要显示的分页按钮对象 -->
{% for i in pages %}
<!-- 如果当前页码等于 i 则让页码按钮选中 -->
{% if i == page.number %}
<!-- 循环将每一页的页码添加到链接中 -->
<li class="active"><a href="?page={{ i }}">{{ i }}</a></li>
{% else %}
<!-- 否则页码按钮无样式 -->
<li><a href="?page={{ i }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
<!-- page.has_next (Django提供的分页对象的属性) 判断是否有下一页,从而改变下一页和末页按钮的样式 -->
{% if page.has_next %}
<li><a href="?page={{ page.next_page_number }}">下一页</a></li>
<li><a href="?page={{ max_index }}">末页</a></li>
{% else %}
<!-- 没有下一页数据,按钮改为禁用 -->
<li class="disabled"><span>下一页</span></li>
<li class="disabled"><span>末页</span></li>
{% endif %}
</ul>
<p class="">总数量:{{ page.paginator.count }} 条</p>
</div>
19.跨域解决
方法一
-
编写中间件
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render, redirect from django.http import JsonResponse class CorsMiddleware(MiddlewareMixin): # 构造函数 def __init__(self, get_response): self.get_response = get_response # 用于对请求和响应进行预处理或后处理。__call__ 方法是中间件的核心方法,在每次请求到达时被调用 def __call__(self, request): # 允许跨域设置 response = self.get_response(request) # 运行指定的 ip和端口 访问后端接口 * 表示运行所有的ip访问后端数据 response['Access-Control-Allow-Origin'] = '*' response['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' response['Access-Control-Allow-Headers'] = 'Content-Type' response['Access-Control-Allow-Credentials'] = 'true' return response
-
配置文件中添加这个中间件
# 中间件 MIDDLEWARE = [ # 跨域中间件需要方在开头 'middlewares.cors_middleware.CorsMiddleware', ]
方法二
-
安装跨域模块
pip install django-cors-headers
-
配置文件 settings.py 中
# 安装的应用程序列表 INSTALLED_APPS = [ ... # 注册跨域模块 'corsheaders', ] # 中间件注册 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', # 注册Django跨域中间件, 必须写在此处 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', ] # 是否允许携带cookie CORS_ALLOW_CREDENTIALS = True # 设置允许跨域的域名和端口 CORS_ALLOWED_ORIGINS = [ 'http://localhost:5173', # 允许的前端地址,根据实际情况修改 'http://127.0.0.1:5173', ] # 添加跨域白名单 CORS_ORIGIN_WHITELIST = { 'http://localhost:5173', 'http://127.0.0.1:5173', } # 允许所有的用户跨域访问 # CORS_ORIGIN_ALLOW_ALL = True # 允许前端携带认证信息,设置为 True CORS_ALLOW_CREDENTIALS = True
-
**前端 vite.config.js 文件 修改运行的IP地址为 127.0.0.1 ** (localhost不会携带Cookie)
import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { // 设置运行前端页面运行的ip地址 host: '127.0.0.1', }, })
21. 应用目录下的admin.py
创建自带的管理员用户
# 命令行执行命令
# 然后输入要添加的管理员名称、邮箱和密码
python manage.py createsuperuser
admin.py
from django.contrib import admin
from .models import Books, Record
# 定义Django自带的Model关联
class BooksAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'status']
class RecordAdmin(admin.ModelAdmin):
list_display = ['id', 'book', 'name', 's_time', 'e_time', 'state']
# 将模型和自定义的类关联
admin.site.register(Books, BooksAdmin)
admin.site.register(Record, RecordAdmin)
22. API开发
步骤
-
安装模块
pip install djangorestframework
-
配置文件中引入
# 安装的应用列表 INSTALLED_APPS = [ ... # 注册应用,不然模版路径会找不到 'api.apps.WebConfig', # 注册序列化模块 'rest_framework', ]
-
创建序列化器
为每个数据模型创建一个序列化器,
序列化器用于将模型实例转换为 JSON 格式的数据
在 API 应用目录下新建一个包 serializers
-
为每一个 models 模型表创建一个文件
from rest_framework import serializers from web.models import Employee # 继承序列化父类 class EmployeeSerializer(serializers.ModelSerializer): # 将数字转换为对应的中文值,让前端渲染 gender = serializers.CharField(source='get_gender_display') class Meta: # 关联对应的模型 model = Employee # 设置需要序列化成json数据的属性名 fields = ['emp_name', 'gender', 'age', 'id_card_number', 'phone_number', 'hire_data', 'depart']
-
在对应的视图文件中编写API视图
from web.models import Employee, Admin # 使用 generics 模块,你可以避免编写重复的代码,提高开发效率 # 这个模块包含了一些常用的 API 视图类, # 比如 ListCreateAPIView、RetrieveUpdateDestroyAPIView 等, # 用于处理常见的 CRUD 操作 from rest_framework import generics from web.serializers.employee_serializers import EmployeeSerializer # 导入自定义的序列化器 # 使用 ListCreateAPIView 创建一个支持 GET(列表)和 POST(创建)操作的 API 视图 class EmpListCreateView(generics.ListCreateAPIView): # 定义 API 视图所处理的数据集 # 当你在 API 视图中执行一些操作时(比如列表展示、创建新对象等)会被使用 queryset = Employee.objects.all() # 告诉 API 视图使用 EmployeeSerializer # 来处理与 Employee 模型相关的数据序列化和反序列化操作 serializer_class = EmployeeSerializer
-
urls.py路由中的代码
from web.views.employee import EmpListCreateView # 导入API视图 urlpatterns = [ # 将视图类与指定路径相关联 path('vue/emp_list/', EmpListCreateView.as_view(), name='emp_list'), ]
23. DjangoRESTful 风格的接口
23.1 安装
-
安装模块
pip install djangorestframework
-
配置文件中引入
# 安装的应用列表 INSTALLED_APPS = [ ... # 注册应用,不然模版路径会找不到 'api.apps.WebConfig', # 注册序列化模块 'rest_framework', ]
23.2 序列化器 Serializer
-
json序列化:将一个Python对象,转为一个json字符串
-
json反序列化: 将一个json对象,转为一个Python ORM对象, 用于保存到数据库
-
序列化器会自动对前端提交的数据进行对应的字段校验
-
使用步骤:
-
在应用目录下新建一个 序列化器文件 serializers.py
from rest_framework import serializers # 继承序列化父类 class BooksSerializer(serializers.Serializer): # 指定序列化的字段和反序列化的字段类型,变量名要和 model 中的一致 # 并且设置每个字段的验证规则 # CharField() 设置参数 read_only=True 表示只读不需要校验 id = serializers.CharField(max_length=20) name = serializers.CharField(max_length=20) status = serializers.BooleanField() # validete_字段名(self, value) 自定义指定字段的校验规则 def validete_name(self, value): if len(value) > 20: # 数据不符合规则,引发一个异常返回给前端 raise serializers.ValidationError('书籍名称长度不能大于20')
-
在视图文件中使用序列化器将表中的所有数据序列化为json数据返回给前端
# ListAPIView 对应get请求,前端向指定url发送get请求时执行 (查询) # CreateAPIView 对应post请求,前端向指定url发送post请求时执行 (新增) # UpdateAPIView 对应put请求,前端向指定url发送put请求时执行 (修改) # DestroyAPIView 对应delete请求,前端向指定url发送delete请求时执行 (删除) # RetrieveAPIView 对应get请求,获取一条数据 # RetrieveUpdateDestroyAPIView 用于对单个对象的获取更新和删除操作 (支持get、post、delete) # ListCreateAPIView 支持处理 get和post 请求 from rest_framework.generics import ListAPIView from webapi.models import Books from webapi.serializers.BooksSerializer import BooksSerializer # 继承父类 ListAPIView 前端发送get请求时触发 class BookView(ListAPIView): queryset = Books.objects.all() # 查询所有的 Books 数据 serializer_class = BooksSerializer # 使用定义的 BooksSerializer 进行序列化 # 手动将所有的数据序列化为json数据, many=True 表示支持多条数据序列化 # json_data = BooksSerializer(queryset, many=True)
-
urls.py 路由配置 (应用路由)
from django.urls import path, re_path from webapi import views urlpatterns = [ re_path(r'books/$', views.BookView.as_view()), ]
-
在一个视图类中完成不同的请求操作
from rest_framework.generics import ListAPIView, CreateAPIView, RetrieveUpdateDestroyAPIView from webapi.models import Books from webapi.serializers import BooksSerializer class ABookView(ListAPIView, CreateAPIView, RetrieveUpdateDestroyAPIView): queryset = Books.objects.all() serializer_class = BooksSerializer def get(self, request, *args, **kwargs): # 处理 GET 请求的逻辑 return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): # 处理 POST 请求的逻辑 return self.create(request, *args, **kwargs) def put(self, request, *args, **kwargs): # 处理 PUT 请求的逻辑 return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): # 处理 DELETE 请求的逻辑 return self.destroy(request, *args, **kwargs)
-
23.3 模型序列化器 ModelSerializer
使用步骤
-
新件模型序列化器文件
from rest_framework import serializers from webapi.models import Employee # 继承模型序列化器父类 class EmployeeSerializer(serializers.ModelSerializer): # 将数字转换为对应的中文值,让前端渲染 gender = serializers.CharField(source='get_gender_display') class Meta: # 关联对应的模型 model = Employee # 序列化时进行序列化,反序列化时不参与(不校验) read_only_fields = ['id'] # 设置需要序列化成json数据的属性名 fields = ['emp_name', 'gender', 'age', 'id_card_number', 'phone_number', 'hire_data', 'depart'] # 对所有的模型字段信息序列化和反序列化操作 # fields = '__all__' # 序列化和反序列化排除指定的字段 # exvlude = ['id'] # 给指定的字段添加额外的校验规则 # extra_kwargs = { # 'age':{'min_value': 0, 'requires': True}, # }
23.4 继承APIView 的类视图
使用步骤
-
里面的 request 对象是 REST 封装的request对象
-
返回数据时需要使用 REST 的 Response 进行返回
-
任何的 异常都会被捕获到,并且处理成 对应的 json 响应数据
-
拥有 身份认证、权限校验、和流量控制 这三个功能
-
导入所需的模块,并创建类视图
from rest_framework.response import Response # 导入响应对象的封装器 from rest_framework import status # 导入状态码的封装器 from rest_framework.views import APIView # 导入 APIView # 继承 REST 的APIView 视图 class BookView(APIView): def get(self,request): # 获得请求的数据 data = request.data # 返回响应数据,并设置对应的响应码 return Response({'code':'200','message': '请求成功'}, status=status.HTTP_200_OK) def post(self,request): pass def put(self,request): pass def delete(self,request): pass
-
路由配置
from django.contrib import admin from django.urls import path, re_path from webapi import views urlpatterns = [ re_path(r'books/$', views.BookView.as_view()), ]
23.5 视图集 ModelViewSet
-
views.py 视图中的代码
from rest_framework.viewsets import ModelViewSet from myapp.models import Book from myapp.serializers import BookSerializer # 完成数据的增删改查 class BookViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
-
urls.py 中注册视图集
# SimpleRouter 简单的视图路由 # DefaultRouter from rest_framework.routers import SimpleRouter from webapi.views import BookViewSet urlpatterns = [ ] # 实例化路由对象 router = SimpleRouter() # 将视图类注册到路由配置中 # 参数一 为访问的uri # basename 参数用于为视图集生成 URL 名称的前缀 # 这意味着生成的 URL 名称将以 'book-' 作为前缀,如 'book-list' 和 'book-detail' router.register('books', BookViewSet, basename='book') # 将路由添加的url配置列表中 urlpatterns = router.urls
23.6 REST 认证和权限
步骤
-
在settings.py 进行全局配置
# REST 配置 REST_FRAMEWORK = { # 设置身份认证的方式 'DEFAULT_AUTHENTICATION_CLASSES': [ # 'rest_framework.authentication.BasicAuthentication', # Basic认证(接口页面认证) 'rest_framework.authentication.SessionAuthentication', # session认证 # 'rest_framework.authentication.TokenAuthentication', # token认证 ], # 配置默认的权限校验 'DEFAULT_PERMISSION_CLASSES': [ # 表示所有的资源都需要登录才能访问 # 'rest_framework.permissions.IsAuthenticated', ], }
-
views.py 视图中的代码
from rest_framework.viewsets import ModelViewSet from myapp.models import Book from myapp.serializers import BookSerializer # 完成数据的增删改查 class BookViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer # 需要携带session数据才能访问 (登录才能访问) # 使用 permission_classes 属性来针对特定视图进行自定义权限认证 # IsAuthenticatedOrReafOnly 登录的用于可以进行所有操作,没有登录的用户只能访问get方法 permission_classes = [IsAuthenticatedOrReafOnly]
23.7 REST 限流
概念
-
限流类型
- AnonRateThrottle 限制匿名未认证用户,使用IP区分用户 ,使用 DEFAULT_THROTTLE_RATES[‘anon’] 设置频次
- UserRateThrottle 限制认证用户,使用User id 区分,使用 DEFAULT_THROTTLE_RATES[‘user’] 设置频次
- ScopedRateThrottle 限制用户对于具体视图的访问频次,通过ip 或 user id 区分,视图中使用throttle_scope 指定频次
-
settings.py 全局配置
# REST 配置 REST_FRAMEWORK = { ... # 全局配置限流类型 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle', ], # 全局配置访问频次 # 频率周期 second 每秒 ,minute 每分,hour 每时, day 每天 'DEFAULT_THROTTLE_RATES': { 'anon': '10/minute', # 未认证用户,每分钟访问10次 'user': '10000/day', # 认证用户,每天访问 10000次 }, }
-
此外还可以在视图级别使用
throttle_classes
和throttle_rates
属性来自定义视图的限流设置from rest_framework.throttling import UserRateThrottle from rest_framework.views import APIView class MyView(APIView): throttle_classes = [UserRateThrottle] throttle_rates = {'user': '10000/day'} # ...
23.8 REST 过滤器
概念
- 过滤器可以帮助您实现搜索功能。
- 过滤器用于对查询结果进行筛选和过滤,
- 以便根据特定条件从数据库中获取所需的数据
使用步骤
-
安装模块 (需要指定清华镜像源,其他源可能无法下载)
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django-filter
-
在 settings.py 配置文件中注册
# 安装的应用程序列表 INSTALLED_APPS = [ ... # 注册应用 'webapi', # 注册序列化模块 'rest_framework', # 注册跨域模块 'corsheaders', # 注册过滤器模块 'django_filters', ] # REST 配置 REST_FRAMEWORK = { # 可以通过配置文件来设置默认的过滤器类。 # 这样可以确保在整个项目中所有的 API 视图都使用相同的默认过滤器。 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend', ], # 其他 REST framework 配置... }
-
在视图中就可以指定过滤的字段,当get传参时,会根据参数去过滤符合的数据返回给前端
from rest_framework.filters import SearchFilter class BookListView(ListAPIView): queryset = Book.objects.all() serializer_class = BookSerializer # 单个视图指定要使用的过滤器,不指定则使用全局的过滤器, # 如果全局没有配置则需要自己指定 # filter_backends = [SearchFilter] # 指定过滤的字段,get传参时会进行过滤返回对应数据 search_fields = ['id', 'name']
-
**使用自定义的过滤器类:的应用目录下新建一个 filters.py 文件,
-
在里面定义过滤器类,一个类对应一个模型
from django_filters import rest_framework from .models import Books # 书籍表过滤器类 class BookFilter(rest_framework.FilterSet): # 自定义过滤字段的规则 id = rest_framework.CharFilter(lookup_expr='icontains') class Meta: # 过滤器绑定指定的模型 model = Books # 指定需要过滤的表字段 fields = ['id', 'name']
-
视图中使用自定义的过滤器类
from .filters import BookFilter from .models import Books from django_filters.views import FilterView class BookListView(ListAPIView): model = Books # 使用定义的 BooksSerializer 进行序列化 serializer_class = BooksSerializer # 使用自定义的过滤器进行过滤 filterset_class = BookFilter
排序过滤器
-
在视图中指定排序的字段和使用的过滤器
from .filters import BookFilter from .models import Books from django_filters.views import FilterView class BookListView(ListAPIView): model = Books # 使用定义的 BooksSerializer 进行序列化 serializer_class = BooksSerializer # 设置排序的过滤器类型为排序过滤器 filter_backends = [OrderingFilter] # 指定排序的字段 ordering_fields = ['age', 'id']
23.9 REST 接口文档
概念
- 用于生成接口文档的模块
- 继承了APIView的视图都会自动生成接口文档
使用步骤
-
安装模块
pip install drf-yasg
-
在 settings.py 配置文件中配置 应用
# 安装的应用程序列表 INSTALLED_APPS = [ ... # 注册api文档模块 'drf_yasg', ] # 文档配置 SWAGGER_SETTINGS = { 'DEFAULT_INFO': 'your_project.urls.openapi_info', }
-
配置接口文档路由
from rest_framework import permissions from drf_yasg.views import get_schema_view from drf_yasg import openapi schema_view = get_schema_view( openapi.Info( title="Your Project API", default_version='v1', description="API for your project", ), public=True, permission_classes=(permissions.AllowAny,), ) urlpatterns = [ ... path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ]
23.10 REST 分页器
-
配置文件中编写配置
# REST 配置 REST_FRAMEWORK = { ... # 分页配置 (一般不设置全局,而是自定义分页器类) # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', # 每页显示的条数设置为 10条 (全局) # 'PAGE_SIZE': 10 }
-
在应用目录下新建分页器文件 pagination.py
from rest_framework.pagination import PageNumberPagination # 自定义对应模型的分页器类 class BooksPagination(PageNumberPagination): page_size = 10 # 设置默认每页显示10条数据 page_query_param = "page" # 设置get请求当前页关键字的参数名 page_size_query_param = 'page_size' # 设置get请求每页条数的关键字参数名 max_page_size = 20 # 设置每页显示的最大条数
-
视图中使用自定义的分页器类
from .filters import BookFilter from .models import Books from django_filters.views import FilterView from .pagination import BooksPagination class BookListView(ListAPIView): model = Books # 使用定义的 BooksSerializer 进行序列化 serializer_class = BooksSerializer # 设置排序的过滤器类型为排序过滤器 filter_backends = [OrderingFilter] # 指定排序的字段 ordering_fields = ['age', 'id'] # 使用自定义的分页器类 pagination_class = BooksPagination
23.11 REST 异常处理
使用步骤
-
在项目目录下新建一个 common 文件夹,在目录下新建一个异常处理文件 exception.py
from rest_framework.views import exception_handler # 自定义异常处理方法 def my_exception_handler(exc, context): response = exception_handler(exc, context) if response is not None: # 响应数据不为空则进入 # 获得响应码 response.data['status_code'] = response.status_code return response
-
在配置文件中,配置使用自定的异常处理文件
# REST 配置 REST_FRAMEWORK = { # 使用自定义的异常处理函数 'EXCEPTION_HANDLER': 'common.exception.my_exception_handler' }
23.12 REST上传文件
步骤
-
新建图片存储的模型字段 models.py
from django.db import models class ImageModel(models.Model): # FileField() 用于保存文件的字段 file = models.FileField() # ImageField() 用于保存文件的字段 img = models.ImageField() class Meta: db_table = 'images' verbose_name = '图片'
-
序列化器
from rest_framework import serializers from api.models import ImageModel class ImageSerializer(serializers.ModelSerializer): class Meta: model = ImageModel fields = '__all__'
-
视图
from rest_framework.viewsets import ModelViewSet from api.models import ImageModel from api.serializers import ImageSerializer class ImageView(ModelViewSet): queryset = ImageModel.objects.all() serializer_class = ImageSerializer
-
配置文件 settings.py 中添加配置
# 指定文件上传时存放的路径 # 将上传的文件存放在:项目根目录下的 file 目录下的 image 目录中 MEDIA_ROOT = BASE_DIR / 'file/image' # 指定文件获取的路径 MEDIA_URL = 'file/image'
-
路由配置 urls.py
from django.contrib import admin from django.urls import path, re_path, include from rest_framework import routers from rest_framework.documentation import include_docs_urls from rest_framework.schemas import get_schema_view from api import views urlpatterns = [ path('admin/', admin.site.urls), ] route = routers.SimpleRouter() route.register('file/upload/', views.ImageView) # 将视图集的 URL 添加到 urlpatterns 中 urlpatterns += route.urls
23.13 JWT token认证登录
使用步骤
-
安装模块
pip install djangorestframework-simplejwt
-
配置 settings.py
# 安装的应用程序列表 INSTALLED_APPS = [ ... # JWT权限校验注册 'rest_framework_simplejwt', ] # REST 配置 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.BasicAuthentication', # Basic认证 'rest_framework.authentication.SessionAuthentication', # session认证 'rest_framework_simplejwt.authentication.JWTAuthentication', # 配置JWT认证 ], } # 指定自定义的用户模型类 (重写了自带的用户模型需要进行配置) AUTH_USER_MODEL = 'users.User' # 使用自定义的认证类进行身份认证 AUTHENTICATION_BACKENDS = ['common.authenticate.MyBackend'] # ========== JWT token 配置 ============= SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30), # 设置访问 token 的有效时间 "REFRESH_TOKEN_LIFETIME": timedelta(days=7), # 设置刷新令牌的有效时间 "ROTATE_REFRESH_TOKENS": False, "BLACKLIST_AFTER_ROTATION": False, "UPDATE_LAST_LOGIN": False, "ALGORITHM": "HS256", # 加密算法 "SIGNING_KEY": settings.SECRET_KEY, "VERIFYING_KEY": "", "AUDIENCE": None, "ISSUER": None, "JSON_ENCODER": None, "JWK_URL": None, "LEEWAY": 0, "AUTH_HEADER_TYPES": ("Bearer",), "AUTH_HEADER_NAME": "HTTP_AUTHORIZATION", "USER_ID_FIELD": "id", "USER_ID_CLAIM": "user_id", # "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", # # "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",), # "TOKEN_TYPE_CLAIM": "token_type", # "TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser", # # "JTI_CLAIM": "jti", # # "SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp", # "SLIDING_TOKEN_LIFETIME": timedelta(minutes=5), # "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=1), # # "TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer", # "TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer", # "TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer", # "TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer", # "SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer", # "SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer", }
-
模型表中的代码
from django.db import models from common.base_models import BaseModel # 导入自定义的模型抽象类 from django.contrib.auth.models import AbstractUser # 导入Django自带的用于认证模型 # 用户表模型,继承自定义的抽象模型类 和 Django自带的用户模型类 class User(BaseModel, AbstractUser): """ 用户表模型 继承父类的字段有 id、username、email、password、is_active、created_time、update_time 表示 用户id、用户名、邮箱、密码、账户是否可用、创建时间、修改时间 """ # 用户手机号 mobile = models.CharField(verbose_name='手机号', max_length=11, default='') # 用户头像 avatar = models.ImageField(verbose_name='用户头像', null=True, blank=True) class Meta: # 自定义创建的数据库表名 db_table = 'users' # 表的描述 verbose_name = '用户表'
-
登录视图中的代码
from rest_framework import status from rest_framework.response import Response from rest_framework_simplejwt.exceptions import TokenError, InvalidToken from rest_framework_simplejwt.views import TokenObtainPairView # 继承Django JWT 的登录视图,进行重写 class LoginView(TokenObtainPairView): # 重新post方法 def post(self, request, *args, **kwargs): # 将JWT post方法 里面的代码复制过来 serializer = self.get_serializer(data=request.data) try: serializer.is_valid(raise_exception=True) except TokenError as e: raise InvalidToken(e.args[0]) # 自定义登录成功之后返回的结果 result = serializer.validated_data # serializer.user 获得登录用户的对象 result['id'] = serializer.user.id result['mobile'] = serializer.user.mobile result['email'] = serializer.user.email result['username'] = serializer.user.username # 将字典中的 token 提取到自定义的键值对中 result['token'] = result.pop('access') return Response(result, status=status.HTTP_200_OK)
-
路由配置
# 根路由 from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/users/', include('users.urls')) ] # ============================================ # users 应用路由 from django.urls import path, include from users import views urlpatterns = [ # 使用自定义的登录视图,它继承的JWT登录视图 path('login/', views.LoginView.as_view()) ]
23.14 支付功能
使用步骤
-
下载沙箱版支付宝
-
登录网页进入支付宝开放平台,点击进入沙箱应用页面 https://open.alipay.com/develop/sandbox/account
-
沙箱应用–开发信息–公钥模式–点击查看–将应用公钥和应用私钥保存到项目公共目录下
-
应用公钥 文件名 app_public_key.pem
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArMPkFdqMFbDHu/AhSy9mMEIfDk15h1lGrckUHd5OuR+xTWrMmPzJkYAmHYYsNIqEVq89HFL2VwCjYV+ExVoMBADRWKADE5HyKvPvj3KXSrR6R+KOxKWuohosLNcbCNrOxDkZHsHBamoN9RSIy18rP1bql2gL3pC2V2ISSM51Au5bbCcTepcq1+PCbLtLVcQFYor8L1YD0KujTjHCE3fzPSU5m04uaiC4IHMKAgpSVAsOLJUOBQVn0Mb2bQGpjdhZoERIKAVRHbLNxJ5OunPdSDMLWbrkisavmUT+8dNFW5a0HVvfLVULIXFw8HgEKBE7ILnsn2ek4gFUZC3ChY92JQIDAQAB -----END PUBLIC KEY-----
-
应用私钥 文件名 app_private_key.pem
-----BEGIN PRIVATE KEY----- MIIEowIBAAKCAQEArMPkFdqMFbDHu/AhSy9mMEIfDk15h1lGrckUHd5OuR+xTWrMmPzJkYAmHYYsNIqEVq89HFL2VwCjYV+ExVoMBADRWKADE5HyKvPvj3KXSrR6R+KOxKWuohosLNcbCNrOxDkZHsHBamoN9RSIy18rP1bql2gL3pC2V2ISSM51Au5bbCcTepcq1+PCbLtLVcQFYor8L1YD0KujTjHCE3fzPSU5m04uaiC4IHMKAgpSVAsOLJUOBQVn0Mb2bQGpjdhZoERIKAVRHbLNxJ5OunPdSDMLWbrkisavmUT+8dNFW5a0HVvfLVULIXFw8HgEKBE7ILnsn2ek4gFUZC3ChY92JQIDAQABAoIBAEeZs/iep7oBlvW70oCPd2F+tdI+p6RBirpRgKXUUXoFuXylS4AIHB3SJ3nK9p0b3zbxz3jipTdvi0a6yJLl+97Y2+pH/p5ouR22FU21JyCN5bPPIG49YD/Mawx2ZbzouhK2u0N094z7wScWSI9ItOq+QeFX/LtT7E4Q1hUYg4QfxfXyDDF8+S5PWFdwRnosB1yDS6NyHSr07pPhlbAMtZhReQaDelnK/2/3iL2gR3nCGlSjNpgC7EpUVUZ9aVz1HLEE3u7ULs7hKaXTIPSZTc54QUDHdT4umZgq5Tk8/gGR8IZyRcxNPMDZxNBIkddOvREXaLlCbamjqv0zONn+4kECgYEA+vKxz2uYL76iX7m9A7YJhRDkT5AE3nkh/KzYvbauEiXy+EDmy07gfbBFA8qPDhz+VfgD0V6TgURW+9+KbCDhd90uRDWW3cXVDpNxht4trobh0vfmhI+66K+e1w/ZwVv0/j8XRVxwgWpnUFw7HQjbimEdcQVJm0U9Sq68pG1DJ9ECgYEAsD5Eac+qfur9vlywina7s+Ep4dcDZn+MDJ964G/OGIBmj/XIxTiWp+nEIk/dURIdsZKIQKAicVCIrTqyKKOxR43H3vI5FoqVem9O9+AZLrqf7vCnT/1FmAFZWHMtsRCgP6Sw23eRq53s+mC+dibUTg7YWDHCW8zXTBoGxYrYkhUCgYBjylC059dL3SXapRSpviDI/m+bx+x3v3mpbd5+in9sDhUxbTWZ0VLqCEdZe5ophZQKAacbAd3sQI3KeRklaGngbV7xltHYfMv9kNpLRFysE00HxLaxLiqoTvX+FXqFLpkc5V/OoDTI8dQFm7eSEyyiOX4orG6ZxQ3bfeLcC+rxkQKBgGNA7e5ZET+gv33xXUrdSyAmchvJpxSWSBzjw6OZ04tg5GG2nXbUQ6QUtmxMZes+NJLIXwtmI3+FRzOnlqXkafZOFn2sFBJpwXzOKr7V6dizKVa7GL6neAX+3H4/fz/0iQrOiPtP/y2TJt5qlViczXuYSKaCf16LocQt9BpT81txAoGBAJRcoFe7vVkASA70Ptf45yAni2alSENzisNe6mPxGRiE+RDMok4GG3OIfNWm7zXmUMxcDAFfLczSWlJhIwZ9IKcbpLrHJuKRB9/KB++pwhfc49D02YmBH0eLjozyPofzeXxJVIjnggBaHh9cdTwNtJdpNmdgOGdQuaooBNgA6Ibm -----END PRIVATE KEY-----
-
保存支付详情信息, 以便查看
# 应用信息 # APPID:9021000135620160 # 应用名称:sandbox ( 默认应用:2088721031641799 ) # 绑定的商家账号(PID):2088721031641799 # 支付宝网关地址 https://openapi-sandbox.dl.alipaydev.com/gateway.do ========== 沙箱账号 ============= # 商家账号 商户账号:amcphq7600@sandbox.com 登录密码:111111 商户PID:2088721031641799 # 买家账号 买家账号:icfnex8381@sandbox.com 登录密码:111111 支付密码:111111 用户UID:2088722031641802 用户名称:icfnex8381 证件类型:IDENTITY_CARD 证件账号:207337199311205833
-
安装支付宝SDK工具包
pip install python-alipay-sdk
-
生成支付地址的代码
from alipay import AliPay # 准备支付宝的应用数据 # 应用ID app_id = '9021000135620160' # 商户号 pid = '2088721031641799' # 应用公钥和应用私钥 app_public_key = open('app_public_key.pem').read() app_private_key = open('app_private_key.pem').read() # 自己系统的订单信息 # 订单号,支付宝后台会根据你的订单ID判断是否重复付款,同一个订单号只能付款一次 order_num = '202403120004' # 订单金额 amount = '888.00' # 支付页面显示的标题 subject = f'商城订单{order_num}支付' # 实例化一个支付对象 pay = AliPay(appid=app_id, app_private_key_string=app_private_key, # 应用私钥 alipay_public_key_string=app_public_key, # 应用公钥 app_notify_url=None, # 支付后回调到当前系统的url的地址 debug=True, # 沙箱环境下开启 debug ) # 生成手机网站应用支付地址参数 # url = pay.api_alipay_trade_wap_pay( # subject=subject, # 支付页面标题 # out_trade_no=order_num, # 订单号,(当前系统的) # total_amount=amount, # 订单金额 # # 需要将产品部署到服务器再进行配置 # return_url=None, # 支付成功后返回的商户地址 # notify_url=None # 支付后回调到当前系统的url的地址 # ) # 生成web网站支付地址参数 url = pay.api_alipay_trade_page_pay( subject=subject, # 支付页面标题 out_trade_no=order_num, # 订单号,(当前系统的) total_amount=amount, # 订单金额 # 需要将产品部署到服务器再进行配置 return_url='http://www.baidu.com', # 支付成功后返回的商户地址 # 支付宝会向商户服务器发送一个异步通知,以通知商户支付结果。 # 这个通知是通过 HTTP POST 请求发送到商户指定的 notify_url 地址 notify_url='http://127.0.0.1:8000/alipay/notify/' ) # 将支付网关和支付参数拼接即可得到支付页面 pay_url = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?' + url print(pay_url)