1.项目创建
创建项目:
django-admin startproject django01
创建app:通常一个app对应一个功能模块,apps文件夹下放多个app
python manage.py startapp app01
django目录结构:
django01
– app01
---- migrations
---- admin.py
---- apps.py
---- models.py
---- tests.py
---- views.py 视图函数
– manage.py 项目管理工具
– django01
---- urls.py 主路由,写url与视图函数的对应关系
---- setting.py 设置
---- asgi.py
---- wsgi.py
2.路由
记录访问url时访问的与之对应的函数
访问http://127.0.0.1:8000/home/ ==> 显示成功
访问http://127.0.0.1:8000/home/?page=12 ==>通过reques.GET.get(‘page’)获取page值
访问http://127.0.0.1:8000/news/12 ==> 显示12,通过参数nid获取整数值
访问http://127.0.0.1:8000/snews/12f ==>显示12f,通过参数sid获取字符串值
int:整数
str:字符串
slug:字母 数字 _ -
uuid:uuid格式 (例:320ca328-bc71-4aec-a801-04248a8e586f)
path:路径格式,可以包含/
访问http://127.0.0.1:8000/user/1111223 ==>通过参数xid,获取正则表达式内容。有几个正则括号就需要几个参数。若给参数指定名字则采用:
re_path(r'user/(?P<xid>\d+)/(?P<yid>\w+)',views.user)
2.1路由分发
所有的路由都写在主路由里会很混乱,可以主路由只写分发,在各自的app下写对应的路由。
分发后通过不同的前缀匹配不同的app路由:
http://127.0.0.1:8000/app02/logout/
http://127.0.0.1:8000/app02/login/
http://127.0.0.1:8000/app01/user/
http://127.0.0.1:8000/app01/admin/
提取公共的url时这么写:
path('user/',([
path('add/',views.add),
path('delete/',views.delete),
path('update/',views.update),
],None,None))
2.2路由name
一. url较长时,通过name在视图函数中反向生成url起到简化作用
访问http://127.0.0.1:8000/app01/admin/ 时自动跳转到http://127.0.0.1:8000/app01/user/user/user/user/user666,此时的url链接由reverse(“v1”)生成
- URL中存在动态参数时,函数中如何反向生成?
1.参数对应有名称,使用kwargs字典
path('/login/<str:role>/',views.login,name="v1")
url=reverse("v1",kwargs={"role":"hhhhhhh"})
re_path(r'user/(?P<xid>\d+)/(?P<yid>\w+)',views.login,name="v2")
url=reverse("v2",kwargs={"xid":888,"yid":666})
2.参数无名称,使用args元组
re_path(r'user/(\d+)/(\w+)',views.login,name="v3")
url=reverse("v3",args=(666,"aaa"))
二. HTML的a标签中,反向生成url
<a href="XXX/XXX/XXX">首页</a>
使用路由name不用写url地址:
<a href=“{% url 'v1' %}”>首页</a>
- URL中存在动态参数时,html的url中如何反向生成?
1.参数对应有名称
path('/login/<str:role>/',views.login,name="v1")
<a href=“{% url 'v1' role='aaabbb' %}”>首页</a>
re_path(r'user/(?P<xid>\d+)/(?P<yid>\w+)',views.login,name="v2")
<a href=“{% url 'v2' xid=666 yid='aaa' %}”>首页</a>
2.参数无名称
re_path(r'user/(\d+)/(\w+)',views.login,name="v3")
<a href=“{% url 'v3' 666 'aaabbb' %}”>首页</a>
2.3路由namespace
当不同的app下的路由中,存在同名的name时,反向生成找不到是哪一个name,使用namespace区分。
主路由:
from django.urls import path, include
urlpatterns = [
path('app01/', include("apps.app01.urls",namespace='x1')),
path('app02/', include("apps.app02.urls",namespace='x2')),
]
app01的url:
path('/login',views.login,name="v1")
app02的url:
path('/login',views.login,name="v1")
两个app中name相同,在反向生成时使用:
url_app01=reverse("x1:login") ==>https://127.0.0.1:8080/app01/login
url_app02=reverse("x2:login") ==>https://127.0.0.1:8080/app02/login
注意:
使用namespace后报错:
需要在各自app下url设置一下app_name参数,指定主路由的path:
urlpatterns = [
path('login/',views.login,name="login"),
path('admin/',views.admin),
]
app_name="app01"
urlpatterns = [
path('login/',views.login,name="login"),
path('logout/',views.logout),
]
app_name="app02"
使用以下方式写,在元组第二个参数设置app_name,第三个参数设置namespace:
path('login/',([
path('add/',views.add),
],'app01',v1))
扩展:
多层嵌套,反向生成时需要写上多个namespace
反向生成login/delete/select使用x1:y1:z1
path('login/',([
path('add/',views.add),
path('delete/',([
path('select/',views.select,name='z1'),
],'y1','y1')),
],'x1','x1'))
2.4路由slash
设置是否给路由最后自动加上/
path('login/',views.login,name="login"),
默认访问以下两种都能成功:
http:127.0.0.1:8080/login/
http:127.0.0.1:8080/login
如果设置APPEND_SLASH=False
,则访问http:127.0.0.1:8080/login失败
2.5路由当前匹配对象
使用request.resolver_match
获取当前匹配路由的信息:
ResolverMatch(func=apps.app01.views.login, args=(), kwargs={}, url_name=login, app_names=['app01'], namespaces=['x1'], route=app01/login/)
作用:
用于用户鉴权,通过request.resolver_match.url.url_name获取当前访问的name,判断这个name是否在可访问的name列表就能实现鉴权
3.视图
一个url就对应一个视图view函数,通常创建一个views文件夹,在文件夹下面写各个功能对应的view文件。
3.1视图参数request
django视图函数参数默认带request
from django.shortcuts import HttpResponse
def login(request):
return HttpResponse('成功')
request对象包括:
- 浏览器请求包括的内容:请求头、请求体、请求方法…
- django给添加的内容: 如resolver_match等
常用的对象内容有:
request.path_info //当前url
request.GET //get请求url传递的所有参数
request.method //请求方式
request.body //post请求时的请求体(原始数据)
request.POST //post请求参数值(content-type为‘application/x-www-form-urlencoded’,参数格式为x=123&y=456才能获取到)
request.FILES //传输文件(content-type为'multipart/form-data')
request.headers //请求头
request.COOKIES //获取cookie值
request.resolver_match //当前路由
request.session
......
3.2视图函数返回值
四种格式:
HttpResponse 字符串/字节/文本(图片验证码)
JsonResponse json格式数据
render 渲染网页
redirect 重定向
1.redirect
使用反向生成的url或者使用url的name
from django.urls import reverse
url = reverse("auth")
return redirect(url) # name
return redirect("auth")
2.render
渲染html页面
return render(request,'login.html')
render寻找顺序:
默认读取DIRS路径,一般放在根目录的templates下。
若默认目录没有,则去每个注册app(按注册顺序)下面的templates找。
一般哪个app模板就写在哪个app下,根目录下的模板写公共的
3.3 响应头
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
3.4 视图FBV和CBV
FBV,视图用函数的形式编写。(目前主流)
CBV,视图用类的形式编写。
FBV需要自己判断是get还是post
CBV会根据方法判断是get还是post
4.静态资源
- 开发需要:css,js,图片
根目录的/static/
已注册app下的/static/下
静态资源寻找顺序:
项目根目录的static,再找已注册app中的static
建议多app开发各自app的图片放在各自 /static/app名字/
html页面使用时先load:这样写当static路径改变时只需要改static路径
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<img src="{% static 'api/1.png' %}">
</body>
</html>
- 媒体文件:用户上传的文件(excel,pdf,vedio…)
根目录的/media/下
访问需要在路由加上才能访问:
urlpatterns = [
path('api/', include('apps.api.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
5.模板
即使用render返回时寻找的模板文件
5.1 寻找html模板
settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], #在根目录下寻找templates里的html模板
'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',
],
},
},
]
- BACKEND:是实现Django模板后端API的模板引擎类的路径。
- DIRS :按搜索顺序定义引擎应该查找模板源文件的目录列表。
优先去项目根目录找 > 然后去每个已注册的app的templates目录找。 - APP_DIRS:告诉引擎是否应该在已安装的应用程序中查找模板,每个后端为其模板应存储在的应用程序内的子目录定义一个常规名称。
- OPTIONS:包含后端特定的设置
5.2 模板处理本质
render可以是任何类别的文件,本质为打开文件,写成html是为了方便有提示。
以下示例:显示【首页仙人掌】,弹窗显示刺头。
注意: 字符串类型需要加入引号,因为渲染完成先生成字符串再返回给浏览器。
以下示例:将js单独写在文件,弹窗n2。
5.3 模板语法
1.取列表、字典、字典列表的值
2.取对象的值
3.取函数、生成器的值
5.4 模板内置函数
5.5 模板自定义函数
内置函数不够用,可以自定义函数
1.app必须注册,app下新建templatetags文件夹,只能叫这个名称
2.在文件夹下写自定义模板方法,需导入:
from Django import template
register=template.Library()
1.filter使用
2.simple_tag使用
3.inclusion_tag使用
返回值会返回给括号里路径位置的html文件
使用场景示例:根据用户权限不同,展示不同功能界面
4.三种方式区别
1.使用@register.filter
- 参数:1~2个
- 处理if条件
2.使用@register.simple_tag
参数无限制 & 返回文本
3.使用@register.inclusion_tag(“”)
参数无限制 & 返回HTML片段
5.6 模板继承和母版
网页中有一些公共的不变的模板,如导航栏、底部、广告位等。将公共部门提取到一个母版里,其他模板继承它。
母版页面需要替换的部分使用占位:
{% block body %} {% endblock %}
继承页面继承母版来编辑需要变化的部分:
{% extends 'layout.html'%}
{% block body %}
<h1>主页</h1>
{% endblock %}
公共的css,js文件也可以使用母版,各个页面继承后放置专属的js,css文件
一般将母版放在根目录的templates中
5.7 模板的导入
公共小片段也可以使用include来实现
6.中间件
Django 中间件是修改 Django request 或者 response 对象的钩子,可以理解为是介于 HttpRequest 与 HttpResponse 处理之间的一道处理过程。浏览器从请求到响应的过程中,Django 需要通过很多中间件来处理。
原始编写中间件的方式:
通过反射来写:
6.1 process_response和process_request
一般继承MiddlewareMixin后只重写请求来之前和返回后的操作,不用原始方法
process_response需要返回值:
6.2 中间件执行顺序
1.多个中间件执行顺序:
存在多个中间件时,按照如图顺序先依次执行process_request再倒序执行process_response:每个中间件可以实现不同的内容
2.中间件有返回值执行顺序
如果在md2的process_request返回,则不会继续往下执行,而是按如图顺序执行:无权访问返回时可以用到该场景
3.路由和process_view执行顺序
中间件的process_request执行完成后进行路由匹配,路由匹配后进入到每一个中间件去执行process_view方法,再去执行视图函数。如果process_view有返回值,则从最后一个中间件的process_response开始返回。
process_response有返回值时,不执行后续中间件的process_response
如果用到视图函数则写在process_view中,如果都是前置操作直接使用process_request就行。
6.3 process_exception
视图函数出现异常可以捕获,自定义异常页面或者存入日志等。
6.3 process_template_response(了解)
视图函数返回是TemplateResponse对象时才触发执行
7.ORM操作
ORM (Object Realtional Mapping)即对象关系映射,它是一种基于关系型数据库的程序技术。ORM 允许使用类和对象对数据库进行操作,这大大提高了对数据库的控制,避免了直接使用 SQL 语句对数据库进行操作。
7.1 表操作–创建表
1.在app中的models.py中按照规则编写类 ===> 表结构。
from django.db import models
class UserInfo(models.Model):
name = models.CharField(max_length=16)
age = models.IntegerField()
2.运行python manage.py makemigrations
,django根据models中类生成一个 对数据库操作的配置文件 => migrations
3.运行python manage.py migrate
,读取已经注册给app中的migrations目录将配置文件 -> 转换成:生成表,修改表 SQL -> 连接数据库去运行。
生成表结构如图:id为自动创建为自增类型,无需自己写
注意:不要手动修改表结构
7.2 数据库配置
在setting中配置,如使用sqllite:
1.使用MySQL
1.项目根目录/项目名目录/init.py
import pymysql
pymysql.install_as_MySQLdb()
2.配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxxxxxxx', # 数据库名字
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
}
}
使用其他数据库:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydatabase',
'USER': 'mydatabaseuser',
'PASSWORD': 'mypassword',
'HOST': '127.0.0.1',
'PORT': 5432,
}
}
# 需要 pip install psycopg2
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
2.使用数据库连接池
https://pypi.org/project/django-db-connection-pool/
pip install django-db-connection-pool
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, # 池中没有连接最多等待的时间。
}
}
}
3.多数据库
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, # 池中没有连接最多等待的时间。
}
},
}
使用场景:
3.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 #指定数据库名字
后续开发时:指定使用哪个数据库
models.UserInfo.objects.using("default").create(title="武沛齐")
models.UserInfo.objects.using("bak").all()
或者编写Router类实现:
3.2分库
100张表,50表-A数据库【app01】;50表-B数据库【app02】
python manage.py makemigrations
python manage.py migrate app01 --database=default
python manage.py migrate app02 --database=bak
7.3 常见字段和参数
常见字段:
1.字符类型
CharField2.数值类型
- SmallIntegerField
- IntegerField
- BigIntegerField3.日期类型
- DateField 年月日时间类型
- DateTimeField 年月日小时分钟秒时间类型4.布尔类型
BooleanField -> 其实数据库不支持真假,根据SmallIntegerField创造出来出来。 0 15.小数类型
DecimalField -> 精确的小数
常见参数:
verbose_name #字段备注
max_length #最大长度
default #默认值
null #数据库可以为空
blank #页面填写可以为空,与null搭配
db_index #添加索引
unique #唯一索引
choices=((1, “男”), (0, “女”)) #指定数据库存储和显示的对应信息auto_now=True #数据被更新就会更新时间;
auto_now_add=True #数据第一次参数时产生。max_digits=10 # 精确小数总共有10位
decimal_places=2 #小数为2位
7.4 表关系
1.一对多
在多的这张表创建外键关联:to_field不写会默认关联id
CASCADE:级联删除
SET_NULL:删除时设置为空,要求可以为空
SET_DEFAULT:设置一个默认值
注意:foreignkey字段会自动在depart后加上_id,即自动生成depart_id
加上以下内容就会修改表名,不使用app的名称_表名:
2.多对多
在新的关系表创建关联:建议使用
或者在任意一个表指定关联:ManyToManyField生成的表字段只能id/bid/gid,不能增加其他字段
3.一对一
用户与博客,一个用户只能有一个博客
7.5 数据操作
1.单表数据操作
class Role(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
od=models.IntegerField(verbose_name="排序",default=0)
新增数据:
obj1 = models.Role.objects.create(title="管理员", od=1)
obj2 = models.Role.objects.create(**{"title": "管理员", "od": 1})
当作类去实例化新增:
obj = models.Role(title="客户", od=1)
obj.od = 100
obj.save()
obj = models.Role(**{"title": "管理员", "od": 1})
obj.od = 100
obj.save()
删除数据:
models.Role.objects.all().delete()
models.Role.objects.filter(title="管理员").delete()
更新数据:
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": "管理员"})
查找数据:
v1 = models.Role.objects.all()
for obj in v1:
print(obj, obj.id, obj.title, obj.od)
# 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)
#大于等于
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="户")
v3 = models.Role.objects.filter(title__startswith="户")
#title为null
v3 = models.Role.objects.filter(title__isnull=True)
v3 = models.Role.objects.filter(id=99)
# 不等于
v3 = models.Role.objects.exclude(id=99).filter(od=88)
print(v3.query)
#显示指定列
# 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])
#第一个对象 存在为对象不存在为null
v6 = models.Role.objects.filter(id__gt=0).first()
v7 = models.Role.objects.filter(id__gt=10).exists()
print(v7) # True/False
# 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')
2.一对多数据操作
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)
1.新增数据
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)
2.删除
# 找到部门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()
#depart__title="销售部"实现跨到另一个表取另一个表的title
models.Admin.objects.filter(depart__title="销售部", name='武沛齐').delete()
3.查询
#效率慢不要用对象跨表查询,非要用对象则使用第二种方法
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")
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)
有外键字段可以通过外键联表查询,部门表没有外键字段也可以通过表名进行关联:
如果有多个外键,联表不知道连哪个,则可以使用related_name进行指定:
4.更新
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="技术部")
3.多对多数据操作
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)
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 #设置一次提交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] select_related主动连表
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("返回")
4.一对一数据操作
8.cookie
第一次访问login将cookie保存在浏览器
再次请求home会将cookie携带
cookie设置:
max_age为None时,关闭浏览器后就获取不到cookie
9.session
基于文件存储session:
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'
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 # 离上一次登录超过两周过期
基于数据库存储
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.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,默认修改之后才保存
基于缓存redis存储
pip install django-redis
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:
from django_redis import get_redis_connection
conn = get_redis_connection("default")
conn.set("xx","123123")
conn.get("xx")
10.Form组件
可用于做校验
widget插件用于给字段添加样式等,会自动在html页面生成输入框等
render_value=True用于设置当校验不通过时,将原始输入保留在输入框不清空
#1.定义类
class LoginForm(forms.Form):
username = forms.CharField(
initial="root", #初始显示的值
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control","placeholder": "用户名"})
)
role = forms.ChoiceField(
label="角色",
required=True, #不能为空
choices=(("2", "测试人员"), ("1", "开发人员")),
widget=forms.Select(attrs={"class": "form-control"})
)
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)
)
通过data=request,post自动将数据通过定义的form校验
校验后form.cleaned_data得到的是输入的合法数据组成的字典
#在view方法中将form组件传递到页面
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():
return render(request, "login.html", {"form": form})
# 2.去数据库校验 1管理员 2客户
data_dict = form.cleaned_data
role = data_dict.pop('role')
if role == "1":
user_object = models.Administrator.objects.filter(active=1).filter(**data_dict).first()
else:
user_object = models.Customer.objects.filter(active=1).filter(**data_dict).first()
# 2.1 校验失败
if not user_object:
form.add_error("username", "用户名或密码错误")
return render(request, "login.html", {'form': form})
# 2.2 校验成功,用户信息写入session+进入项目后台
mapping = {"1": "ADMIN", "2": "CUSTOMER"}
request.session[settings.NB_SESSION_KEY] = {'role': mapping[role], 'name': user_object.username,
'id': user_object.id}
return redirect(settings.LOGIN_HOME)
#在页面中使用
<div class="box">
<h2 style="text-align: center;">用户登录</h2>
<form method="post">
{% 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 %}
<button type="submit" class="btn btn-primary">登 录</button>
<a href='{% url 'sms_login' %}' style="float: right;">短信登录</a>
</form>
</div>
10.1 Form组件钩子函数
以上校验只能进行简单校验或者正则表达式,当需要进行复杂校验时通过钩子函数实现,钩子函数以clean_字段名命名
class LoginForm(forms.Form):
role = forms.ChoiceField(
label="角色",
required=True,
choices=(("2", "测试人员"), ("1", "开发人员")),
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)
)
def clean_username(self):
user = self.cleaned_data['username']
# 校验规则
# 校验失败
if len(user) < 3:
from django.core.exceptions import ValidationError
raise ValidationError("用户名格式错误")
return user
def clean_password(self):
return md5(self.cleaned_data['password'])
def clean(self):
# 对所有值进行校验,无论前面的字段校验成功与否
user = self.cleaned_data.get('username')
pwd = self.cleaned_data.get('password')
if user and pwd:
pass
from django.core.exceptions import ValidationError
# 1.不返回值,默认 self.cleaned_data
# 2.返回值,self.cleaned_data=返回的值
# 3.报错,ValidationError -> self.add_error(None, e)
# print(self.cleaned_data)
# raise ValidationError("整xxxxx体错误")
发送短信验证码示例:
class SmsLoginForm(forms.Form):
role = forms.ChoiceField(
label="角色",
required=True,
choices=(("2", "客户"), ("1", "管理员")),
widget=forms.Select(attrs={"class": "form-control"})
)
mobile = forms.CharField(
label="手机号",
validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ],
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "手机号"})
)
code = forms.CharField(
label="短信验证码",
validators=[RegexValidator(r'^[0-9]{4}$', '验证码格式错误'), ],
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "短信验证码"})
)
def clean_code(self):
mobile = self.cleaned_data.get('mobile')
code = self.cleaned_data['code']
if not mobile:
return code
conn = get_redis_connection("default")
cache_code = conn.get(mobile)
if not cache_code:
raise ValidationError("短信验证码未发送或失效")
if code != cache_code.decode('utf-8'):
raise ValidationError("短信验证码未发送或失效")
return code
class MobileForm(forms.Form):
role = forms.ChoiceField(
label="角色",
required=True,
choices=(("2", "客户"), ("1", "管理员")),
widget=forms.Select(attrs={"class": "form-control"})
)
mobile = forms.CharField(
label="手机号",
required=True,
validators=[RegexValidator(r'^1[358]\d{9}$', '手机格式错误'), ]
)
def clean_mobile(self):
role = self.cleaned_data.get('role')
mobile = self.cleaned_data['mobile']
if not role:
return mobile
if role == "1":
exists = models.Administrator.objects.filter(active=1, mobile=mobile).exists()
else:
exists = models.Customer.objects.filter(active=1, mobile=mobile).exists()
if not exists:
raise ValidationError("手机号不存在-钩子")
# 2.发送短信 + 生成验证码
sms_code = str(random.randint(1000, 9999))
is_success = tencent.send_sms(mobile, sms_code)
if not is_success:
raise ValidationError("短信发送失败-钩子")
# 3.将手机号和验证码保存(以便于下次校验) redis -> 超时时间
conn = get_redis_connection("default")
conn.set(mobile, sms_code, ex=60)
return mobile
def sms_send(request):
""" 发送短信 """
res = BaseResponse()
# 校验数据合法性:手机号的格式 + 角色
form = MobileForm(data=request.POST)
if not form.is_valid():
res.detail = form.errors
return JsonResponse(res.dict, json_dumps_params={"ensure_ascii": False})
res.status = True
return JsonResponse(res.dict)
def sms_login(request):
if request.method == "GET":
form = SmsLoginForm()
return render(request, "sms_login.html", {'form': form})
res = BaseResponse()
# 1.手机格式校验
form = SmsLoginForm(data=request.POST)
if not form.is_valid():
res.detail = form.errors
return JsonResponse(res.dict)
role = form.cleaned_data['role']
mobile = form.cleaned_data['mobile']
# 3.登录成功 + 注册 (监测手机号是否存在)
# - 未注册,自动注册
# - 已注册,直接登录
if role == "1":
user_object = models.Administrator.objects.filter(active=1, mobile=mobile).first()
else:
user_object = models.Customer.objects.filter(active=1, mobile=mobile).first()
if not user_object:
res.detail = {"mobile": ["手机号不存在"]}
return JsonResponse(res.dict)
# 2.2 校验成功,用户信息写入session+进入项目后台
mapping = {"1": "ADMIN", "2": "CUSTOMER"}
request.session[settings.NB_SESSION_KEY] = {'role': mapping[role], 'name': user_object.username,
'id': user_object.id}
res.status = True
res.data = settings.LOGIN_HOME
return JsonResponse(res.dict)
11.ajax请求
http协议发送请求时:
- 通过form表单提交请求时,页面会刷新
- 通过ajax,不会刷新页面
本质,利用浏览器上XMLhttpRequest
<html>
<head>
</head>
<body>
<script>
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
// 已经接收到全部响应数据,执行以下操作
var data = xhr.responseText;
console.log(data);
}
};
xhr.open('POST', "/test/", true );
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;');
xhr.send('n1=1;n2=2;');
</script>
</body>
</html>
利用jQuery类库实现,内部封装实现简单
<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>
11.1 csrf处理
当使用ajax发送post\put\delete请求时,需要进行csrf处理
<script src="{% static 'js/csrf.js' %}"></script>
/**
* 根据cookie的name获取对应的值
* @param name
* @returns {null}
*/
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;
}
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFTOKEN", getCookie('csrftoken'));
}
}
})
或者直接通过$(“#smsForm”).serialize()将所有数据传递,默认也会处理csrf
$.ajax({
url: "{% url 'sms_login' %}",
type: "POST",
data: $("#smsForm").serialize(),
dataType: "JSON",
success: function (res) {
console.log(res);
if (res.status) {
// res.data = "/level/list/
location.href = res.data;
} else {
$.each(res.detail, function (k, v) {
$("#id_" + k).next().text(v[0]);
})
}
}
})