1. 创建/运行项目
shell
# 安装
pip install django==3.2
# 创建项目
django-admin startproject [项目名称]
# 进入项目目录
cd [项目目录]
# 创建app(项目目录下)
python manage.py startapp [app名称]
# 启动项目(项目目录下)
python manage.py runserver [默认127.0.0.1:8080]
2. 项目分支
python
mysite
├── manage.py 【项目的管理工具】
├── mysite
│ ├── __init__.py 【若import mysite则导入的是该文件】
│ ├── asgi.py 【异步】
│ ├── settings.py 【部分配置文件。程序启动时,先读取django内部配置,再读settings.py】
│ ├── urls.py 【主路由,在里面编写 /xxx/xxx/xxx ---> index 】
│ └── wsgi.py 【同步,主】 项目上线使用的是uwsgi,性能更高
└── web
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ └── __init__.py
├── models.py 【ORM,基于models可以对数据库进行简便的操作】
├── tests.py
└── views.py 【视图函数】
django内部配置文件是 [python目录]\Lib\site-packages\django\conf\global_settings.py
注意:以后创建项目时,不要一直用系统解释器,为每个项目:虚拟环境+项目文件
3. 虚拟环境
- venv,Python官方用于创建虚拟环境的工具
python
cd xxx/xxx/crm
python -m venv [虚拟环境名称] # 创建了一个对应python版本的虚拟环境
- virtualenv【推荐】
shell
pip install virtualenv
shell
cd xxx/xx/
virtualenv [虚拟环境名称] --python=python3.10
3.1 命令行创建
-
在
D:\envs\创建虚拟环境shellcd D:\envs virtualenv [虚拟环境目录] --python=python3.10 # 使用virtualenv指定创建Python3.10版本的虚拟环境

-
激活虚拟环境
-
win
shellcd D:\envs\[虚拟环境目录]\Scripts activate # 激活 (crm) D:\envs\[虚拟环境目录]\Scripts> # 激活后 -
mac
shellsource /[虚拟环境目录]/bin/activate
-

-
安装包
shell(crm) D:\envs\[虚拟环境目录]\Scripts>pip install [包名] # 将包安装在此虚拟环境中 -
在虚拟环境下创建django项目
D:\project\python\Practiceshell(crm) cd D:\project\python\Practice (crm) djanbo-admin startproject crm (crm) python manage.py startapp xxx (crm) python manage.py runserver -
退出虚拟环境
shelldeactivate
3.2 Pycharm创建
3.2.1 Django+虚拟环境【最新版本Django】
pip install django

注意:创建django最新版可以。
3.2.2 Django+虚拟环境【指定版本】


shell
pip install django==3.2
django-admin startproject [项目名] . # 这样把项目直接创建在.venv同级目录。 不加.则多一个文件夹包裹着

配置Django Server



4. 创建app
-
项目只需要一个app,目录结构建议如下

-
项目需要多个app,目录结构建议如下
django_practice .venv django_practice ... ... manage.py apps web backend api
4.1 多app创建方法

5. 纯净版Django

6. 打开项目/导出项目
-
给别人项目 即代码+requirements.txt
shellpip freeze > requirements.txt # 导出依赖,放在项目目录下 # 压缩文件,排除虚拟环境。 -
要一个项目
shell# 1.创建虚拟环境+项目和环境关联 pip install -r requirements.txt # 2.安装依赖 # 3.运行项目
7.路由系统
本质上:URL和函数的对应关系。
7.1 传统的路由
python
from django.contrib import admin
from django.urls import path
from apps.web import views
urlpatterns = [
path('home/', views.home),
path('news/<int:nid>/edit/', views.news),
path('article/', views.article),
]
python
from django.shortcuts import render, HttpResponse
def home(request):
return HttpResponse("成功")
def news(request, nid): # 会将动态的<int:nid>传值给函数中的参数nid
print(nid)
page = request.GET.get("page") # 会将?后面的参数传值给request.GET
return HttpResponse("新闻")
def article(request):
nid = request.GET.get("nid") # 会将?后面的参数传值给request.GET
print(nid)
return HttpResponse("文章")
- int,整数
- str,字符串 /
- slug,字母+数字+下滑线+ -
- uuid,uuid格式
- path,路径,可以包含 /
7.2 正则表达式路由
- 在django1版本用的多。
- 在django2+版本用的少

7.3 路由分发
7.3.1 include分发
inlucde + app(一般情况),将功能拆分不到不同的app中。

7.3.2 手动路由分发
手动路由分发,可以与app无关。
python
path('user/add/', views.login),
path('user/delete/', views.login),
path('user/edit/', views.login),
path('user/list/', views.login),
# 同样前缀,可以提取为这种形式
path('user/', ([
path('add/', views.login),
path('delete/', views.login), # /user/delete/
path('edit/', views.login),
path('list/', views.login),
], None, None)),
纯粹帮助提取功能的URL,防止重复编写。
7.3.3 路由分发的本质
-
URL对应函数
pythonpath('user/add/', views.login), -
URL对应元组
path('user/add/', (元素,appname元素,namespance元素) ),pythonpath('user/add/', include("apps.api.urls") ), path('user/add/', ([],None,None) ),
7.3.4导入模块与反射
python
# Python中的知识点
path = "app.api.urls"
import importlib
md = importlib.import_module(path) # 相当于from app.api import urls 导入模块
v1 = getattr(md, "url_patterns") # 相当于md.url_patterns 反射机制
print(v1)
7.3.5 实际应用场景
python
# 常规罗列
path('user/add/', views.login),
path('user/delete/', views.login),
path('user/edit/', views.login),
path('user/list/', views.login),
# 手动路由分发
path('user/', ([
path('add/', views.login),
path('delete/', views.login), # /user/delete/
path('edit/', views.login),
path('list/', views.login),
], None, None)),
# 手动路由分发
path('user/', include(([
path('add/', views.login),
path('delete/', views.login), # /user/delete/
path('edit/', views.login),
path('list/', views.login),
], None))),
# include分发
include("apps.api.urls") # 一般是每个app中urls
urlpatterns = [
]
7.4 name
给一个路由起个名字 + 根据名字反向生成URL。
python
urlpatterns = [
path('login/', views.login),
]
python
# 很多功能,很多URL
urlpatterns = [
path('login/', views.login, name="v1"),
path('auth/', views.auth, name="v2"),
]
有了名字后,以后一般有两处会用到:
-
在视图函数中生成URL
from django.urls import reverse url = reverse("v2") # /auth/ url = reverse("v1") # /login/ -
HTML模板,页面上有一个a标签,添加xx。
html<a href="/xxx/xxx/xx/">添加</a>html<a href="{% url 'v1' %}">添加</a> <a href="{% url 'v2' %}">添加</a> -
扩展
以后做权限管理,让name属性配合。
基于reverse反向生成url:


7.5 namespace
意义:reverse反向生成url时使得相同的name对应不同的url。
-
主路由
pythonfrom django.urls import path, re_path, include # 很多功能,很多URL urlpatterns = [ path('api/', include("apps.api.urls", namespace='x1')), path('web/', include("apps.web.urls", namespace='x2')), ] -
api/urls.py
pythonfrom django.urls import path, re_path from . import views # 很多功能,很多URL urlpatterns = [ path('login/', views.login, name="login"), path('auth/', views.auth, name='auth'), ] -
web/urls.py
pythonfrom django.urls import path, re_path from . import views # 很多功能,很多URL urlpatterns = [ path('home/', views.home, name='home'), path('order/', views.order, name='order'), path('auth/', views.order, name='auth'), ]
以后在某个URL或者视图中反向生成:
python
from django.urls import reverse
url = reverse("x1:login") # /api/login/
url = reverse("x1:order") # /web/login/
url = reverse("x1:auth") # /api/login/
url = reverse("x2:auth") # /web/login/
7.5.1 include分发路由
在使用namespace的情况下需要设置app_name,否则报错
python
# 项目的urls.py文件中使用了namespace
urlpatterns = [
path('api/', include("apps.api.urls", namespace='x1')),
]
python
# 在apps/api/urls.py文件中
from django.urls import path, re_path
from apps.api import views
urlpatterns = [
path('login/', views.login, name="login"),
path('auth/', views.auth, name='auth'),
]
app_name = "api" # 这里若不设置app_name会报错
7.5.2 手动分发路由

python
urlpattern = [
path('api/', (
[
path('login/', views.login, name="login"),
path('auth/', views.auth, name="auth"),
], 'api', 'x1')), # 元组中第二个参数为app_name, 第三个参数为namespace。开发中一般会让app_name和namespace同名
]
- 关于namespace多层嵌套。使用reverse反向生成url时,需要将所有的namespace从外到内全都加进去。如示例x1:yy:xx

7.6 最后的 / 如何解决?
settings中配置 APPEND_SLASH = True(默认为True)
python
path('login/', views.login),
http://127.0.0.1:8000/login/ 成功
http://127.0.0.1:8000/login django,重定向301
http://127.0.0.1:8000/login/ 成功
python
path('login', views.login),
http://127.0.0.1:8000/login 成功
http://127.0.0.1:8000/login
http://127.0.0.1:8000/login/ 失败
APPEND_SLASH = False
python
path('login/', views.login),
http://127.0.0.1:8000/login/ 成功
http://127.0.0.1:8000/login 失败
python
path('login', views.login),
http://127.0.0.1:8000/login/ 失败
http://127.0.0.1:8000/login 成功
所以推荐 settings配置文件中不需要添加内容,但是在路由中路径后添加 / 以便于兼容用户访问时不添加 / 的情况
7.7 当前匹配对象
python
request.resolver_match

有什么用呀?
某用户,具有一些权限。 permissions = ["xx","login",'account']
某用户,具有一些权限。 permissions = ["login",'account']

补充:关于 partial
补充partial知识点(与django路由无关,看源码时有关)
python
def _xx(a1, a2):
return a1 + a2
data = _xx(11, 22)
print(data)
python
from functools import partial
def _xx(a1, a2):
return a1 + a2
yy = partial(_xx, a2=100)
data = yy(2)
print(data)
8. 视图
8.1 形式(文件 / 文件夹)
文件形式:创建项目 / app自带的views.py

文件夹形式:若视图中函数较多,不适合放在同一视图文件中,则需要删除views.py文件,创建views文件夹,在文件夹中创建不同名称的视图文件,而后只需要在urls.py中导入不同的视图文件即可。

8.2 相对和绝对导入urls

注意实现:不要在项目根目录做相对导入。
原则:
- 绝对导入(推荐)
- 相对导入(层级深才用)
8.3 视图参数
python
urlpatterns = [
path('login/', account.login, name="login"),
path('auth/', order.auth, name='auth'),
]
python
from django.shortcuts import HttpResponse
def login(request):
return HttpResponse("login")
requests是什么呢?
对象,包裹,可以放很多东西。
requests是一个对象,存放了浏览器给咱们发过来的所有内容,所以含有:
- 请求相关所有的数据: 当前访问的url、请求方式、...
- django额外添加的数据
python
from django.shortcuts import HttpResponse
def login(request):
# 1.当前URL /api/login/
print(request.path_info)
# 2.URL传递的参数
print(request.GET)
print(request.GET.get("age")) # 获取get请求中的age参数
# 3.请求方式 GET/POST
print(request.method)
# 4.如果post请求,传递请求体(原始数据)
print(request.body)
# b'{"code":"083Sjmll2yla694F3bll2DguCM2SjmlG","unionId":"oP6QCsyT_9bk1dfSaVf0GEV5Y-yE"}' b'v1=123&v2=456'
# 4.1 请求体+请求头
# 当请求体为b'v1=123&v2=456'这种格式,且content-type为application/x-www-form-urlencoded时,可以通过request.POST.get("v1")直接获取请求体中传递的参数值
print(request.POST)
print(request.POST.get("v1"))
print(request.POST.get("v2"))
# 4.2 请求体+请求头 文件
print(request.FILES) # 文件格式 + multipart/form-data
print(request.FILES.get("n1"))
print(request.FILES.get("n2"))
# 5.请求头
# {'Content-Length': '', 'Content-Type': 'text/plain', 'Host': '127.0.0.1:8000', 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0', 'Sec-Ch-Ua': '" Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"macOS"', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-User': '?1', 'Sec-Fetch-Dest': 'document', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7', 'Cookie': 'csrftoken=CdidpKSGbLxzmOXnbmlkvrZep1eJmKLAA81T73UjcjxEnMOa4YOZqtc849AkYfUy'}
print(request.headers)
# 5.1 请求头有个特殊的cookie 这是Django setttings配置中自带的
# request.headers['cookie'] # 'csrftoken=CdidpKSGbLxzmOXnbmlkvrZep1eJmKLAA81T73UjcjxEnMOa4YOZqtc849AkYfUy;session=xxxx'
# {'csrftoken': 'CdidpKSGbLxzmOXnbmlkvrZep1eJmKLAA81T73UjcjxEnMOa4YOZqtc849AkYfUy'}
print(request.COOKIES)
# 6.requests中其他值
print(request.resolver_match)
return HttpResponse("login")
8.4 返回值
- HttpResponse
- JsonResponse
- render
- redirect
python
from django.shortcuts import HttpResponse, redirect, render
from django.http import JsonResponse
def auth(request):
pass
def login(request):
# 1.获取请求数据
print(request)
# 2.根据请求数据进行条件的判断 GET/POST GET.get("xx") POST.get("xx")
# 3.返回数据
# 3.1 字符串/字节/文本数据(图片验证码)
return HttpResponse("login")
# 3.2 JSON格式(前后端分离、app小程序后端、ajax请求)
data_dict = {"status": True, 'data': [11, 22, 33]}
return JsonResponse(data_dict)
# 3.3 重定向
return redirect("https://www.baidu.com")
# return redirect("http://127.0.0.1:8000/api/auth/")
# return redirect("http://127.0.0.1:8000/api/auth/")
return redirect("/api/auth/")
# return redirect("/api/auth/") # name
#
from django.urls import reverse
url = reverse("auth")
return redirect(url)
# return redirect("auth") # 这里的auth代表路由中的name参数 不推荐使用
# 3.4 渲染
# - a.找到 'login.html' 并读取的内容,问题:去哪里找?
# - 默认先去settings.TEMPLATES.DIRS指定的路径找。(公共)
# - 按注册顺序每个已注册的app中找他templates目录,去这个目录中寻找'login.html'
# - 一般情况下,原则,那个app中的的模板,去哪个那个app中寻找。
# - b.渲染(替换)得到替换完成的字符串
# - c.返回浏览器
return render(request, 'api/login.html')
8.5 响应头
python
from django.shortcuts import HttpResponse, redirect, render
from django.http import JsonResponse
def login(request):
res = HttpResponse("login")
res['xx1'] = "hahaha"
res['xx2'] = "hahaha"
res['xx3'] = "hahaha"
res.set_cookie('k1',"aaaaaaaa")
res.set_cookie('k2',"bbbbbb")
return res
8.6 FBV和CBV
- FBV,视图用函数的形式编写。(前后端不分离项目主流)
- CBV,视图用类的形式编写。(DRF分离项目主流)

请注意,这一些都是表象,本质一模一样。
9.静态资源
静态资源:
-
开发需要:css、js、图片。
- 根目录的 /static/ - 已经app目录下载 /static/ 文件夹下 -
媒体文件:用户上传的数据(excel/pdf/video)
- 根目录的 /media/ - 其他服务器
9.1 静态文件
python
INSTALLED_APPS = [
# 'django.contrib.admin',
# 'django.contrib.auth',
# 'django.contrib.contenttypes',
# 'django.contrib.sessions',
# 'django.contrib.messages',
'django.contrib.staticfiles',
"apps.api.apps.ApiConfig",
"apps.web.apps.WebConfig",
]
...
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
-
顺序:...
-
多app开发:各自app的图片放在各自
/static/app名字/。。。 -
在开发过程中
-
禁止
html<img src="/static/api/1.png"> -
建议
html{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录页面</h1> <a href="/xxx/xxxxx/">调换dao xx</a> <a href="{% url 'login' %}">跳转</a> <img src="{% static 'api/1.png' %}"> </body> </html>
-
9.2 媒体文件
python
from django.contrib import admin
from django.urls import path, re_path, include
from django.conf.urls.static import static # 导入
from django.conf import settings # 导入
from apps.api import views
urlpatterns = [
path('api/', include('apps.api.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # 添加


10.Git版本控制
11.中间件、ORM、缓存、session、cookie(全家桶)
- 先看全家桶
- 笔记 https://www.cnblogs.com/wupeiqi/articles/6216618.html
12. 导入配置文件
python
from django.conf import settings # 会先导入全局的global_settings,再去导入用户的settings内容
13. 模板
13.1 寻找html模板
python
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',
],
},
},
]
优先去项目根目录 > 每个已注册的app的templates目录找。
如何选择:
- 简单的项目,模板都放在根目录。
- 复杂的项目,模板放在各自的app中,公共部分放在templates目录。
扩展:修改内置app的模板也是同样的套路。
13.2 模板处理的本质
渲染完成后,生成了字符串,再返回给浏览器。这里如果alert("{``{n2}}")放在js文件中通过src属性引入的话,是不能被替换的。


13.3 常用语法

13.4 内置函数
在django模板语法中提供了内置函数。

13.5 自定义模板功能

自定义模板的三种方式:
-
filter
- 数据处理,参数:1~2个 - 数据处理,if条件 -
simple_tag
参数无限制 & 返回文本 -
inclusion_tag
参数无限制 & HTML片段
13.6 继承和模板
先继承,将全部内容拿到以后,再进行渲染。所以如果继承的模板中有占位符,也有传递参数


13.7 模板的导入

13.8 verbatim
python
{% verbatim %}
# 可将内容不被渲染
{% endverbatim %}
14. django中间件

- 类
- 定义方法
- 注册
14.1 原始方式
请求通过中间件进入,再从中间件出去,通过__call__回调函数体现

中间件注册:

14.2 MiddlewareMixin(建议)


注意:django1版本。



疑问:prcess_request的执行时,是否已执行了路由匹配?
request.resolver_match告诉我们答案。
注意:process_view是在django中源码中写死了。
流程(如下图) :这里请求发到Django,先要经过每一个中间件的process_request(如有),然后进行路由匹配,而后再从头执行每一个中间件的process_view方法(如有),而后将请求发到视图函数,后将视图函数中的response返回通过中间件的每一个process_response方法。其中如果md2的process_request直接返回响应,则直接进行md2的process_response,而后通过md1的process_response返回给客户端。如果md2的process_view直接返回响应,则直接从最后一个md3的process_response进入,依次通过md2、md1的process_response返回给客户端。

14.3 其他
process_exception
process_exception可以捕获视图函数中的异常,进而进行处理。它不是在response返回时调用的,而是在view执行过程中一旦抛出异常时立即调用。如果某个中间件的process_exception返回了一个 HttpResponse,异常处理链停止,这个HttpResponse继续往外传递,会像正常的 response一样,经过剩下的中间件的 process_response(反向执行),而不会再走process_exception


process_template_response
python
# 视图函数view.py中
def user():
from django.template.response import TemplateResponse
return TemplateResponse(request, 'app01/user.html') # 返回这个对象可以触发中间件中的process_template_response方法
# 中间件md.py中
def process_template_response(self, request, response)
return response
小结
- 定义中间类
- 类方法
- process_request
- process_view
- process_reponse
- process_exception,视图函数出现异常,自定义异常页面。
- process_template_response,视图函数返回
TemplateResponse对象 or 对象中含有.render方法。
应用场景
- 用户登录,后续用户验证
- 跨域请求,做添加响应头允许跨域访问(浏览器因为同源策略会阻止跨域请求,这时候需要服务器返回特定响应头允许跨域)

15. ORM操作
orm,关系对象映射,本质翻译的。

15.1 表结构
实现:创建表、修改表、删除表。
在app中的models.py中按照规则编写类 ===> 表结构。
-
编写类
pythonfrom django.db import models class UserInfo(models.Model): name = models.CharField(max_length=16) age = models.IntegerField() -
注册app
pythonINSTALLED_APPS = [ # 'django.contrib.admin', # Django提供的后台 # 'django.contrib.auth', # 后台权限认证 # 'django.contrib.contenttypes', # 复杂表设计 # 'django.contrib.sessions', # 前后端不分离项目使用 # 'django.contrib.messages', # A页面跳转B页面 数据传递使用 'django.contrib.staticfiles', 'apps.web.apps.WebConfig', 'apps.api.apps.ApiConfig', ] -
命令,django根据models中的类生成一个
对数据库操作的配置文件=>migrationspython manage.py makemigrations
-
-
哪个数据库?
-
数据库账户和密码?
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
-

常见问题:请不要再手动去修改数据的表结构 + 时刻保证 ORM和数据表是对应。
15.1.1 常见字段和参数
-
字段
CharField SmallIntegerField IntegerField BigIntegerField DateField DateTimeField BooleanField -> 其实数据库不支持真假,根据SmallIntegerField创造出来出来。 0 1 DecimalField -> 精确的小数 -
参数
pythonname = models.CharField(verbose_name="姓名", max_length=16) name = models.CharField(verbose_name="姓名", max_length=16, default="哈哈哈") # 经常查询,速度快(MySQL,https://www.bilibili.com/video/BV15R4y1b7y9) name = models.CharField(verbose_name="姓名", max_length=16, default="哈哈哈", null=True, blank=True, db_index=True) email = models.CharField(verbose_name="姓名", max_length=16, default="哈哈哈", null=True, blank=True, unique=True) # 在数据库存储时只能是:sh、bj (上海、北京一般用于页面显示中文) code = models.CharField(verbose_name="姓名", max_length=16, choices=(("sh", "上海"), ("bj", "北京")),default="sh")python# 不用 max_length=16 count = models.IntegerField(verbose_name="数量", default=1, null=True, blank=True, unique=True) code = models.IntegerField(verbose_name="性别",choices=((1, "男"), (2, "女")),default=1)pythonregister_date = models.DateField(verbose_name="注册时间", auto_now=True)pythonamount = models.DecimalField(verbose_name="余额", max_digits=10, decimal_places=2)
示例:
python
from django.db import models
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=16, db_index=True)
age = models.PositiveIntegerField(verbose_name="年龄")
email = models.CharField(verbose_name="邮箱", max_length=128, unique=True)
amount = models.DecimalField(verbose_name="余额", max_digits=10, decimal_places=2, default=0)
register_date = models.DateField(verbose_name="注册时间", auto_now=True)
class Goods(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
# detail = models.CharField(verbose_name="详细信息", max_length=255)
detail = models.TextField(verbose_name="详细信息")
price = models.PositiveIntegerField(verbose_name="价格")
count = models.PositiveBigIntegerField(verbose_name="库存", default=0)
15.1.2 表关系
- ORM - 表关系之一对多


- ORM - 表关系之多对多


在男女表中的任何一张表中添加models.ManyToManyField,Django帮助生成第三张表,结果和上面手动添加第三张表一样。

注意:ManyToManyField生成的表字段只能id/bid/gid
小结
设计自己项目的业务时,理清楚表与表之间的关系。
强调:设计项目表结构:表名和字段都不要拼音。
15.2 基本操作
orm,关系对象映射。
类 --> SQL --> 表
对象 --> SQL --> 数据
特点:开发效率高、执行效率低( 程序写的垃圾SQL )。
编写ORM操作的步骤:
-
settings.py,连接数据库
pythonDATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } -
settings.py,注册app
INSTALLED_APP = [ ... "app01.apps.App01Config" ] -
编写models.类
pythonclass UserInfo(models.Model): .... ..... -
执行命令
python manage.py makemigrations # 找到所有已注册的app中的models.py中的类读取 -> migrations配置 python manage.py migrate # 读取已注册的app下的migrations配置 -> SQL语句 -> 同步数据库
15.3 连接数据库
python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxxxxxxx', # 数据库名字
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
}
}
项目连接MySQL:
-
安装MySQL & 启动MySQL服务
-
手动创建数据库
-
django的settings.py配置
pythonDATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'xxxxxxxx', # 数据库名字 'USER': 'root', 'PASSWORD': 'root123', 'HOST': '127.0.0.1', # ip 'PORT': 3306, } } -
安装第三方组件
-
pymysql
pip install pymysql 项目根目录/项目名目录/__init__.py import pymysql pymysql.install_as_MySQLdb() -
mysqlclient
pip install mysqlclient 电脑上先提前安装MySQL。
-
其他数据库:
python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': 5432,
}
}
# 需要 pip install psycopg2
python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.oracle',
'NAME': "xxxx", # 库名
"USER": "xxxxx", # 用户名
"PASSWORD": "xxxxx", # 密码
"HOST": "127.0.0.1", # ip
"PORT": 1521, # 端口
}
}
# 需要 pip install cx-Oracle
15.4 连接池
django默认内置没有数据库连接池 。
pymysql -> 操作数据库
DBUtils -> 连接池
https://pypi.org/project/django-db-connection-pool/
pip install django-db-connection-pool
python
DATABASES = {
"default": {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'day04', # 数据库名字
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'POOL_OPTIONS': {
'POOL_SIZE': 10, # 最小
'MAX_OVERFLOW': 10, # 在最小的基础上,还可以增加10个,即:最大20个。
'RECYCLE': 24 * 60 * 60, # 连接可以被重复用多久,超过会重新创建,-1表示永久。
'TIMEOUT':30, # 池中没有连接最多等待的时间。
}
}
}
注意:组件django-db-connection-pool不是特别厉害。拿了另外一个支持SQLAchemy数据库连接池的组件。
15.5 多数据库
django支持项目连接多个数据库。
python
DATABASES = {
"default": {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'day05db', # 数据库名字
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'POOL_OPTIONS': {
'POOL_SIZE': 10, # 最小
'MAX_OVERFLOW': 10, # 在最小的基础上,还可以增加10个,即:最大20个。
'RECYCLE': 24 * 60 * 60, # 连接可以被重复用多久,超过会重新创建,-1表示永久。
'TIMEOUT': 30, # 池中没有连接最多等待的时间。
}
},
"bak": {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'day05bak', # 数据库名字
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'POOL_OPTIONS': {
'POOL_SIZE': 10, # 最小
'MAX_OVERFLOW': 10, # 在最小的基础上,还可以增加10个,即:最大20个。
'RECYCLE': 24 * 60 * 60, # 连接可以被重复用多久,超过会重新创建,-1表示永久。
'TIMEOUT': 30, # 池中没有连接最多等待的时间。
}
},
}
15.5.1 读写分离
192.168.1.2 default master [写]
组件
192.168.2.12 bak slave [读]
-
生成数据库表
python manage.py makemigrations # 找到所有已注册的app中的models.py中的类读取 -> migrations配置 python manage.py migrate python manage.py migrate --database=default python manage.py migrate --database=bak -
后续再进行开发时
pythonmodels.UserInfo.objects.using("default").create(title="武沛齐") models.UserInfo.objects.using("bak").all() -
编写router类,简化【后续再进行开发时】,帮助model操作找到对应的数据库
pythonclass DemoRouter(object): def db_for_read(self, model, **hints): from django.db.models.options import Options # print(model._meta, type(model._meta)) # 通过打印type,知道是这个类django.db.models.options.Options, 可以看model._meta有什么属性 print(model._meta.app_label) # app01,可以通过这个方法判断调用model的app print(model._meta.model_name) # userinfo,可以通过这个方法判断调用的model print(hints) # {} return "bak" def db_for_write(self, model, **hints): return "default"python# settings配置文件中添加配置 DATABASE_ROUTERS = ["utils.router.DemoRouter"]
python# views.py中则可以简化 models.UserInfo.objects.create(title="张三") models.UserInfo.objects.all()
15.5.2 分库(多个app ->多数据库)
100张表,50表-A数据库【app02】;50表-B数据库【app02】。
-
app01/models
pythonfrom django.db import models class UserInfo(models.Model): title = models.CharField(verbose_name="标题", max_length=32) -
app02/models
pythonfrom django.db import models class Role(models.Model): title = models.CharField(verbose_name="标题", max_length=32) -
命令
pythonpython manage.py makemigrationsshellpython manage.py migrate app01 --database=default # app01的表要生成到default数据库中shellpython manage.py migrate app02 --database=bak # app02的表要生成到bak数据库中
-
读写操作
pythonfrom django.shortcuts import render, HttpResponse from app01 import models as m1 from app02 import models as m2 def index(request): # app01中的操作 -> default v1 = m1.UserInfo.objects.all() print(v1) # app02中的操作 -> bak v2 = m2.Role.objects.using('bak').all() print(v2) return HttpResponse("返回") -
router配置,判断具体使用哪个数据库

15.5.3 分库(单app)
100张表,50表-A数据库;50表-B数据库。

python
from django.shortcuts import render, HttpResponse
from app01 import models as m1
def index(request):
# app01中的操作 -> default
v1 = m1.UserInfo.objects.all()
print(v1)
# app01中的操作 -> bak
v2 = m1.Role.objects.using('bak').all()
print(v2)
return HttpResponse("返回")

15.5.4 注意事项
-
分库,表拆分到不用数据库。
一定不要跨数据库做关联 -> django不支持 怎么办? 尽可能的将有关联的表放在一个库中。
-
为什么表拆分到不同的库?
15.6 表关系
-
单表
pythonclass Role(models.Model): title = models.CharField(verbose_name="标题", max_length=32) -
一对多

-
多对多

如果关系表中只有3列。
pythonclass Boy(models.Model): """ 1 杰森斯坦森 2 汤普森 """ name = models.CharField(verbose_name="标题", max_length=32, unique=True) b = models.ManyToManyField(to="Girl") class Girl(models.Model): """ 1 ax 2 yu """ name = models.CharField(verbose_name="标题", max_length=32, unique=True)pythonclass Boy(models.Model): """ 1 杰森斯坦森 2 汤普森 """ name = models.CharField(verbose_name="标题", max_length=32, unique=True) class Girl(models.Model): """ 1 ax 2 yu """ name = models.CharField(verbose_name="标题", max_length=32, unique=True) b = models.ManyToManyField(to="Boy")pythonclass Boy(models.Model): name = models.CharField(verbose_name="标题", max_length=32, unique=True) class Girl(models.Model): name = models.CharField(verbose_name="标题", max_length=32, unique=True) class B2G(models.Model): bid = models.ForeignKey(to="Boy", on_delete=models.CASCADE) gid = models.ForeignKey(to="Girl", on_delete=models.CASCADE) address = models.CharField(verbose_name="地点", max_length=32) -
一对一
表,100列 -> 50A表 50B表 博客园为例: - 注册,用户名、密码,无法创建博客 - 开通博客 地址/

python
models.OneToOneField() # 实际上是使用外键Foreign Key+唯一索引Unique实现的
15.7 数据操作
15.7.1 单表
python
class Role(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
python
# obj1 = models.Role.objects.create(title="管理员", od=1)
# obj2 = models.Role.objects.create(**{"title": "管理员", "od": 1})
# 内存 -> save
# obj = models.Role(title="客户", od=1)
# obj.od = 100
# obj.save()
# obj = models.Role(**{"title": "管理员", "od": 1})
# obj.od = 100
# obj.save()

python
# models.Role.objects.all().delete()
models.Role.objects.filter(title="管理员").delete()
python
models.Role.objects.all().update(od=99)
models.Role.objects.filter(id=7).update(od=99, title="管理员")
models.Role.objects.filter(id=7).update(**{"od": 99, "title": "管理员"})
python
# QuerySet = [obj, obj]
v1 = models.Role.objects.all()
for obj in v1:
print(obj, obj.id, obj.title, obj.od)
# QuerySet = []
# v2 = models.Role.objects.filter(od=99, id=99)
v2 = models.Role.objects.filter(**{"od": 99, "id": 99})
for obj in v2:
print(obj, obj.id, obj.title, obj.od)
v3 = models.Role.objects.filter(id=99)
print(v3.query)
v3 = models.Role.objects.filter(id__gt=2)
print(v3.query)
v3 = models.Role.objects.filter(id__gte=2)
print(v3.query)
v3 = models.Role.objects.filter(id__lt=2)
print(v3.query)
v3 = models.Role.objects.filter(id__in=[11, 22, 33])
print(v3.query)
v3 = models.Role.objects.filter(title__contains="户")
print(v3.query)
v3 = models.Role.objects.filter(title__startswith="户")
print(v3.query)
v3 = models.Role.objects.filter(title__isnull=True)
print(v3.query)
python
v3 = models.Role.objects.filter(id=99)
print(v3.query)
# 不等于
v3 = models.Role.objects.exclude(id=99).filter(od=88)
print(v3.query)
python
# queryset=[obj,obj]
v3 = models.Role.objects.filter(id=99)
# queryset=[{'id': 6, 'title': '客户'}, {'id': 7, 'title': '客户'}]
v4 = models.Role.objects.filter(id__gt=0).values("id", 'title')
# QuerySet = [(6, '客户'), (7, '客户')]
v5 = models.Role.objects.filter(id__gt=0).values_list("id", 'title')
print(v5[0])
python
v6 = models.Role.objects.filter(id__gt=0).first()
# print(v6) # 对象
v7 = models.Role.objects.filter(id__gt=10).exists()
print(v7) # True/False
python
# asc
v8 = models.Role.objects.filter(id__gt=0).order_by("id")
# id desc od asc
v9 = models.Role.objects.filter(id__gt=0).order_by("-id", 'od')
15.7.2 一对多
python
class Depart(models.Model):
""" 部门 """
title = models.CharField(verbose_name="标题", max_length=32)
class Admin(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
pwd = models.CharField(verbose_name="密码", max_length=32)
depart = models.ForeignKey(verbose_name="部门", to="Depart", on_delete=models.CASCADE)
python
models.Admin.objects.create(name='武沛齐1', pwd='123123123', depart_id=2)
# models.Admin.objects.create(**{..})
obj = models.Depart.objects.filter(id=2).first()
models.Admin.objects.create(name='武沛齐2', pwd='123123123', depart=obj)
models.Admin.objects.create(name='武沛齐2', pwd='123123123', depart_id=obj.id)
python
# filter() # 当前表的字段 + depart__字段 -> 连表和条件
# 找到部门id=3的所有的员工,删除
# models.Admin.objects.filter(depart_id=3).delete()
# 删除销售部的所有员工
# obj = models.Depart.objects.filter(title="销售部").first()
# models.Admin.objects.filter(depart_id=obj.id).delete()
# models.Admin.objects.filter(depart__title="销售部", name='武沛齐').delete()
python
# 1. select * from admin queryset=[obj,obj,]
v1 = models.Admin.objects.filter(id__gt=0)
for obj in v1:
print(obj.name, obj.pwd, obj.id, obj.depart_id)
# 2. select * from admin inner join depart queryset=[obj,obj,]
v2 = models.Admin.objects.filter(id__gt=0).select_related("depart") # select_related中放模型中定义的外键字段名
for obj in v2:
print(obj.name, obj.pwd, obj.id, obj.depart_id, obj.depart.title)
# 3. select id,name.. from admin inner join depart queryset=[{},{}]
v3 = models.Admin.objects.filter(id__gt=0).values("id", 'name', 'pwd', "depart__title")
print(v3)
# 4. select id,name.. from admin inner join depart queryset=[(),()]
v4 = models.Admin.objects.filter(id__gt=0).values_list("id", 'name', 'pwd', "depart__title")
print(v4)
python
# 查询
# models.Admin.objects.filter(id=2).update(name='xxx', pwd='xxxx')
# models.Admin.objects.filter(name="武沛齐").update(depart_id=2)
# models.Admin.objects.filter(id=2).update(depart__title="技术部") -> 只能更新自己表字段

15.7.3 多对多

python
from django.db import models
class Boy(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32, db_index=True)
class Girl(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32, db_index=True)
class B2G(models.Model):
bid = models.ForeignKey(to="Boy", on_delete=models.CASCADE)
gid = models.ForeignKey(to="Girl", on_delete=models.CASCADE)
address = models.CharField(verbose_name="地点", max_length=32)
python
def index(request):
# models.Boy.objects.create(name="宝强")
# models.Boy.objects.create(name="羽凡")
# models.Boy.objects.create(name="乃亮")
#
# models.Girl.objects.bulk_create(
# objs=[models.Girl(name="小路"), models.Girl(name="百合"), models.Girl(name="马蓉")],
# batch_size=3
# )
# 创建关系
# models.B2G.objects.create(bid_id=1, gid_id=3, address="北京")
# models.B2G.objects.create(bid_id=1, gid_id=2, address="北京")
# models.B2G.objects.create(bid_id=2, gid_id=2, address="北京")
# models.B2G.objects.create(bid_id=2, gid_id=1, address="北京")
# b_obj = models.Boy.objects.filter(name='宝强').first()
# g_object = models.Girl.objects.filter(name="小路").first()
# models.B2G.objects.create(bid=b_obj, gid=g_object, address="北京")
# 1.宝强都与谁约会。
# queyset=[obj,obj,obj]
# q = models.B2G.objects.filter(bid__name='宝强').select_related("gid")
# for item in q:
# print(item.id, item.address, item.bid.name, item.gid.name)
# q = models.B2G.objects.filter(bid__name='宝强').values("id", 'bid__name', 'gid__name')
# for item in q:
# print(item['id'], item['bid__name'], item['gid__name'])
# 2.百合 都与谁约会。
# q = models.B2G.objects.filter(gid__name='百合').values("id", 'bid__name', 'gid__name')
# for item in q:
# print(item['id'], item['bid__name'], item['gid__name'])
# 3.删除
# models.B2G.objects.filter(id=1).delete()
# models.Boy.objects.filter(id=1).delete()
return HttpResponse("返回")
15.7.4 一对一

16. cookie和session
16.1 cookie

python
127.0.0.1 v1.david.com
127.0.0.1 v2.david.com
# set_cookie中的参数domain只对域名生效,对ip无效

16.2 配置session
16.2.1 文件版
python
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',
]
# session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
SESSION_FILE_PATH = 'xxxx' # 在项目目录下生成xxxx目录即可
SESSION_COOKIE_NAME = "sid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存
16.2.2 数据库存储
python
INSTALLED_APPS = [
# 'django.contrib.admin',
# 'django.contrib.auth',
# 'django.contrib.contenttypes',
'django.contrib.sessions', # 将这个app取消注释,并且通过migrate命令生成对应表
# 'django.contrib.messages',
'django.contrib.staticfiles',
"app01.apps.App01Config",
]
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',
]
# session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
SESSION_COOKIE_NAME = "sid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存
16.2.3 redis缓存
python
INSTALLED_APPS = [
# 'django.contrib.admin',
# 'django.contrib.auth',
# 'django.contrib.contenttypes',
# 'django.contrib.sessions',
# 'django.contrib.messages',
'django.contrib.staticfiles',
"app01.apps.App01Config",
]
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',
]
# session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default' # 指定settings.py中CACHES配置具体使用哪个
SESSION_COOKIE_NAME = "sid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
SESSION_COOKIE_SECURE = False # 是否Https传输cookie
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期
SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存
17. 缓存
-
服务器 + redis安装启动
-
django
-
安装连接redis包
shellpip install django-redis==5.4.0 # 最新版django-redis-6.0.0 仅支持Django4.2+ -
python
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} # "PASSWORD": "密码", } } } -
手动操作redis
pythonfrom django_redis import get_redis_connection conn = get_redis_connection("default") conn.set("xx","123123") conn.get("xx")
-
18. 通过orm创建数据
18.1 离线脚本
想使用ORM插入一些数据,伪造启动django。一般在项目根目录下创建scripts目录下放一个py文件
python
# 伪造django启动
import os
import sys
import django
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目根目录
sys.path.append(base_dir)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'model6_order.settings')
django.setup()
# 离线脚本运行完之后防止反复运行,就给他注释掉
from web import models
from utils.encrypt import md5
models.Administrator.objects.create(
username='admin',
password=md5('admin'),
mobile='13829102910'
)
18.2 shell命令行
shell
python manage.py shell
# 临时交互命令行, 一次性输入也可以通过orm创建一条数据
>>> from web import models
>>> from utils.encrypt import md5
>>> models.Administrator.objects.create(username='root',password=md5('root'),mobile='13829102911')
19. 项目知识点
19.1 Ajax请求
本质,利用浏览器上XMLhttpRequest。
利用jQuery类库,内部封装,使用简单。
html
<html>
...
<body>
<script src='jquery.js'></script>
<script>
$.ajax({
url:"....",
type:"post",
data:{n1:123,n2:456},
success:function(res){
console.log(res);
}
})
</script>
</body>
</html>
19.1.1 应用场景
- 提交数据,页面可以刷新 -> form表单
- 提交数据,不想刷新,ajax形式提交。
19.2. CSRF Token
19.2.1 form表单形式
html
<form>
{% csrf_token %} <input type='hidden' value='xxxx' />
<input type='submit' />
</form>
19.2.2 ajax方式
html
<form>
{% csrf_token %} <input type='hidden' value='xxxx' />
<input type='submit' />
</form>
浏览器打开网址时,django在cookie中也给我们返回了一段值
html
$.ajax({
url:"...",
type:"get",
data:{user:"david", pwd:"xxx"},
header:{
"X-CSRFToken":"cookie中的值"
}
success:function(arg){
}
})
19.3. Form组件
19.3.1 form表单提交
- 生成HTML标签 + 携带数据。
-
保留原来提交的数据,不再担心form表单提交时页面刷新。
-
显示默认值,做编辑页面显示默认值。
-
html
<h2 style="text-align: center">用户登录</h2>
<form action="/login/" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group" style="position: relative; margin-bottom: 25px">
<label>{{ field.label }}</label>
{{ field }}
<span style="color:red;position: absolute;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
{{ form.non_field_errors.0 }}
<button type="submit" class="btn btn-primary">登 录</button>
<span style="color:red">{{ error }}</span>
<a href="/sms/login" style="float: right">短信登录</a>
</form>
- 数据校验,对用户提交的数据格式校验
python
### views.py
from django.shortcuts import HttpResponse, render, redirect
from web import models
from utils.encrypt import md5
from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
class LoginForm(forms.Form):
role = forms.ChoiceField(
label="角色", # 用于模板渲染时可以使用for-endfor生成
choices=(("1", "管理员"), ("2", "客户")),
widget=forms.Select(attrs={"class": "form-control"})
)
username = forms.CharField(
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "用户名"})
)
password = forms.CharField(
label="密码",
min_length=6,
max_length=10,
validators=[RegexValidator(r'^[0-9]+$', '密码必须是数字'), ],
widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "密码"}, render_value=True),
# 密码字段提交后默认不保留,如果想要保留需要添加render_value=True
)
# 自定义方法(钩子)
def clean_username(self): # 函数名规定为 clean_字段名
username = self.cleaned_data['username']
# 校验规则
# 如果校验失败
if len(username) < 3:
from django.core.exceptions import ValidationError
raise ValidationError("用户名格式错误")
# 如果校验成功
return username # 只要返回就是通过
def clean(self):
# 在每个字段的校验通过后,对所有值进行校验,无论前面字段校验成功与否
username = self.cleaned_data.get('username')
pwd = self.cleaned_data.get('password')
# 1.不返回值,默认self.cleaned_data
# 2.返回值,self.cleaned_data = 返回的值
# 3.报错,ValidationError -> self.add_error(None, e)
# raise ValidationError("整体错误")
def _post_clean(self):
pass
def login(request):
if request.method == "GET":
form = LoginForm()
return render(request, 'login.html', {"form": form})
# 1.接受并获取数据(数据格式或是否为空验证-Form组件 & ModelForm组件)
form = LoginForm(data=request.POST)
if not form.is_valid(): # 开始校验
# form.cleaned_data
# form.errors {"username":[...], "password":[...]}
# form.errors['__all__'] 这是clean()方法的结果
# form.non_field_errors().0
return render(request, 'login.html', {"form": form})
print(form.cleaned_data) # {"username":'xx', 'password':'xx', 'role':'xx'}
role = form.cleaned_data.get('role') # get获取第一个值 getlist获取列表中所有值。例如多选框
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password') # get获取第一个值 getlist获取列表中所有值。例如多选框
password = md5(password)
# 2.去数据库进行校验 1管理员 2客户
role_mapping = {"1": "ADMIN", "2": "CUSTOMER"}
if role not in role_mapping:
return render(request, 'login.html', {'form': form, 'error': "角色不存在"})
if role == "1":
user_obj = models.Administrator.objects.filter(active=1, username=username, password=password).first()
else:
user_obj = models.Customer.objects.filter(active=1, username=username, password=password).first()
# 2.1 校验失败
if not user_obj:
return render(request, 'login.html', {'form': form, 'error': "用户名或密码错误"})
# 2.2 校验成功,用户信息写入session+进入项目后台
request.session['user_info'] = {'role': role_mapping[role], 'name': user_obj.username, 'id': user_obj.id}
return redirect("/home/")
19.3.2 form的校验流程
-
每个字段的内部:required + validators +
min_length=6,max_length=10 -
字段的钩子方法
pythondef clean_username(self): user = self.cleaned_data['username'] # 校验规则 # 校验失败 if len(user) < 3: from django.core.exceptions import ValidationError raise ValidationError("用户名格式错误") return user print(form.cleaned_data) -
clean(用的少)
pythondef clean(self): # 在每个字段的校验通过后,对所有值进行校验,无论前面字段校验成功与否 username = self.cleaned_data.get('username') pwd = self.cleaned_data.get('password') # 1.不返回值,默认self.cleaned_data # 2.返回值,self.cleaned_data = 返回的值 # 3.报错,ValidationError -> self.add_error(None, e) # raise ValidationError("整体错误") -
_post_clean(用的少)
pythondef _post_clean(self): pass
19.3.3 Form目的
-
生成标签
- 循环 + 单独某个字段
- label页面显示文本信息
- 自定义错误信息位置,position
-
校验
-
定义
-
每个字段校验
-
字段上定义校验规则 正则、空、长度
-
clean_方法名
校验成功:
self.cleaned_data["字段"] = 值
校验失败
self.errors["字段"] = 错误
-
-
所有字段校验
-
clean
pythontry: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data self.errors["__all__"] = 错误 -
_post_clean
-
-
-
校验源码
- form.is_valid
- form.errors
- form.full_clean()
-
业务校验
pythonform = 对象 if not form.is_valid(): print(form.errors) form.errors.username.0 form.errors.__call__.0 -> 模板语言中 form.errors.__ 不支持 form.non_field_errors() <==> form.errors.__call__ 在页面上去适合的位置显示错误信息 else: print(form.cleaned_data) ...

-
扩展
如果想要让某个错误信息,展示在特定的字段旁,就可以使用: form.add_error("password", "用户名或密码错误")
-
19.4 短信登录
19.4.1 展示数据


19.4.2 发送短信
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
$.ajax({
url:"/xxx/",
type:"GET",
data:{mobile:"1888888"},
success:function(res){
...
}
})
javascript
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
向后端发送请求:
-
form
- GET/POSThtml<form method='GET'> </form>html<form method='POST'> {% csrf_token %} ... </form> -
ajax
javascript$.ajax({ url: "/xxx/xx", type: "GET", data: {mobile: "1888888"}, success: function (res) { console.log(res); } })javascript$.ajax({ url: "/xxx/xx", type: "POST", data: {mobile: "1888888"}, headers:{ "X-CSRFTOKEN":"...." }, success: function (res) { console.log(res); } })javascript$.ajax({ url: "/xxx/xx", type: "PUT", data: {mobile: "1888888"}, headers:{ "X-CSRFTOKEN":"...." }, success: function (res) { console.log(res); } })javascript$.ajax({ url: "/xxx/xx", type: "DELETE", data: {mobile: "1888888"}, headers:{ "X-CSRFTOKEN":"...." }, success: function (res) { console.log(res); } })
19.4.3 登录
- Ajax提交,展示效果好。
- Form提交。
html
<form id='f1'>
<input ...
</form>
javascript
$.ajax({
url:"/sms/login/",
type:"POST",
data:{
mobile:'xxx',
code:'xxx'
}
})
javascript
$.ajax({
url:"/sms/login/",
type:"POST",
data:$("#f1").serialize()
})
# 好处,不需要加csrf header
19.5 总结
-
发送Ajax
html$.ajax({ url:"...", type:"GET", data:{}, dataType:"JSON", success:function(res){ } }) -
csrf认证
-
请求体中
- 传统的form 添加一行{% csrf_token %} - jQuery $("#smsForm").serialize() + Ajax -
请求头中
集成在了csrf.js中,在html中导入csrf.js即可 $.ajaxSetup({ beforeSend... }) $.ajax({ url:"...", type:"GET", data:{}, dataType:"JSON", headers:{ ... }, success:function(res){ } })
-
-
Django的自定义请求头
- 自动化添加 HTTP_ 前缀 - 自动化添加 HTTP_ 前缀,前端的-,转换成后端的_ -
Form组件 & 源码执行流程 & 钩子执行顺序
- 未使用钩子
- 使用钩子流程 & 结合源码
-
jQuery + location.href + 错误信息展示
-
保留代码,便于后续开发。(model6_order)