Django框架学习与开发

Django学习

常用命令:

1、新建一个项目

1
django-admin startproject 项目名

2、新建app (一个项目可以有多个app,通用的app也可以在多个项目中使用)

1
2
python manage.py startapp app名   
或者 django-admin.py startapp app名

3、创建数据库表 或 更改数据库表或字段(可在SQL等数据库中创建与models.py代码对应的表)

创建更改的文件

1
python manage.py makemigrations

将生成的py文件应用到数据库

1
python manage.py migrate

4、运行Django自带开发服务器

1
python manage.py runserver

公网访问:

1
python manage.py runserver 0.0.0.0:8000

本地访问:

1
2
3
python manage.py runserver
python manage.py runserver 8000
python manage.py runserver 127.0.0.1:8000

5、清空数据库

1
python manage.py flush

6、创建超级管理员

在创建前,得先执行python manage.py migrate

1
python manage.py createsuperuser

修改用户密码

1
python manage.py changepassword username

7、导入导出数据

​ 导入:

1
python manage.py loaddata app名.json

导出:

1
python manage.py dumpdata app名 >app名.json

8、django 项目环境终端

1
 python manage.py shell

9、数据库命令行

1
python manage.py dbshell

视图层:

1.base64编码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import base64,json
if __name__ == '__main__':
data={"name":"root","uid":10001}
data_json=json.dumps(data).encode()
print(data_json) #b'{"name": "root", "uid": 10001}'
#编码,把二进制转换成编码字符
base_data=base64.b64encode(data_json)
print(base_data) #b'eyJuYW1lIjogInJvb3QiLCAidWlkIjogMTAwMDF9'

#解码,把编码字符转换成二进制
data1="eyJuYW1lIjogInJvb3QiLCAidWlkIjogMTAwMDF9"
bytes_data=base64.b64decode(data1).decode()
#转换成字符串
print(bytes_data)
#转换成字典
dict_data=json.loads(bytes_data)
print(dict_data)

2.uuid :

uuid - 匹配一个格式化的 UUID 。
为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00。返回 一个 UUID 实例。

1
2
3
4
5
6
7
8
9
import uuid
if __name__ == '__main__':
#通用唯一识别码,用于表示一个产品的生产码的时候使用
#在web开发中,uuid是非常常用的随机字符串,往往见于url地址上用于区分唯一,在用户信息表中用于区分唯一
print(uuid.uuid1()) #1cdd50f0-6637-11ed-808e-709cd10a9410
#print(uuid.uuid3())
print(uuid.uuid4())
#print(uuid.uuid5())
#uuid3和uuid5都需要传入参数才能生成识别码

3.读取django配置:

1
2
3
4
5
6
from django.conf import settings
def demo3(request):
#读取django系统的配置,需要通过django.conf.settings对象来读取
print(settings.DEBUG)
print(settings.INSTALLED_APPS)
return HttpResponse('ok')

4.关于request对象:

request 是当用户请求django时,由wsgi.py中的application Web应用对象,会自动帮我们解析http请求的文本内容,并把对应的对象,赋值给request对象的.
request 对象是 django.core.handers.wsgi.WSGIRequest请求处理类的实例对象
WSGIRequest的父类是django.http.request.HttpRequest
在日常开发中,我们在django视图中使用request里面的绝大部分的属性方法都是继承于HttpRequest

1
2
from django.core.handers.wsgi import WSGIRequest
from django.http.request import HttpRequest

5.会话控制:

从打开浏览器访问一个网站,到关闭浏览器结束此次访问,称之为一次会话

HTTP协议是无状态的,导致会话状态难以保持

Cookies和Session就是为了保持会话状态而诞生的两个存储技术

Cookies

cookies是保存在客户端浏览器上的存储空间,然后可以通过浏览器的开发者工具的application可以查看cookies的值

  • cookies在浏览器上是以键-值对的形式进行存储的,键和值都是以ASCII字符串的形式存储(不能是中文字符串)
  • 存储的数据带有生命周期
  • cookies中的数据是按域存储隔离的,不同的域之间无法访问
  • cookies的内部的数据会在每次访问此网址时都会携带到服务器端,如果cookies过大会降低响应速度

Session

session是在服务器上开辟一段空间用于保留浏览器和服务器交互时的重要数据

实现方式:

  • 使用session需要在浏览器客户端启动cookie,且在cookie中存储sessionid
  • 每个客户端都可以在服务器端有一个独立的Session
  • 注意:不同的请求者之间不会共享这个数据,与请求者一一对应

实现会话控制的几种技术类型:

  1. url地址栏记录用户身份的参数[少见,很古老的技术了]
  2. cookie:在浏览器中由浏览器自动读写保存用户信息的一种小文件,能够存储的数据有限。10M左右[过时了,谷歌在2021年开始慢慢关闭这个cookie技术]
  3. session:在服务端中保存用户信息的一种小文件,能够存储的数据有限,根据服务端配置而定(需要掌握)
  4. token令牌:就是一段可以记录和识别用户身份的字符串,通过客户端语言[js/安卓/ios]保存在客户端的一种技术
1
2
3
4
5
6
7
8
*key,   变量名*
*value,变量值*
*max_age, 有效时间,单位:秒*
*expire, 过期的时间戳,单位也为秒[时间戳表示从1970-01-01 00:00:00至今的总秒数]*
*path=None, 当前cookie是否只能在指定公共路径下使用,None表示在同一个域名下,任意路径都可以使用*
*domain=None, 当前cookie是否只能在指定同一段域名下使用,None表示在当前服务器所在域名下使用*
*secure=False, 当前cookie是否只能在https协议下使用,False表示在http协议下也能使用*
*httponly=False, 当前cookie是否只能在http协议下使用,False表示在其他协议下也可以使用*
2.设置cookie:
1
2
3
4
5
6
7
8
9
10
11
12
13
def setcookie(request):
#设置cookie
response=HttpResponse('OK')
'''通过http响应,设置cookie
key,变量名
value,变量值
max_age,有效时间,单位:秒
expire,过期的时间戳,单位也为秒
# 如果cookie没有设置有效期,则默认当前cookie的有效期为本次会话时间内
'''
response.set_cookie(key="name",value="xiaoming",max_age=30)
return response

3.获取cookie:
1
2
3
4
5
6
7
8
9
10
def getcookie(request):
#获取所有cookie
"""因为cookie是由浏览器自动保管的,所以一旦客户端浏览器存在当前域名的cookie,
在每次客户端请求服务器时,浏览器都会自动把cookie附加请求头里面
在django中,可以通过request.COOKIES进行获取请求头中的cookie
"""
print(request.COOKIES)
#获取指定变量名的cookie,尽量不使用中括号
print(request.COOKIES.get("name"))
return HttpResponse('hello')
4.删除cookie:
1
2
3
4
5
6
7
8
9
10
def delcookie(request):
#删除cookie
"""因为cookie是保存在客户端中的数据,因此在服务端中没有直接删除cookie的操作
一般的删除cookie操作是通过设置当前变量的值为任意值,同时设置当前变量的有效期为0
这样浏览器接收到响应的信息后,就会自动删除
"""
response=HttpResponse('ok')
#默认,通过set_cookie删除cookie变量,仅仅删除一个,如果要批量删除,则通过遍历request.COOKIES来完成
response.set_cookie("name","",max_age=0)
return response

session:

1.设置session:
1
2
3
4
5
6
7
def set_session(request):
#设置session
print(request.session)
request.session["uname"]="root"
request.session["uid"]="10001"
return HttpResponse("设置session数据")

2.获取session数据:
1
2
3
4
5
6
7
8
def get_session(request):
#获取所有session
print(request.session.items()) # dict_items([('uname', 'root'), ('uid', '10001')])
#获取指定变量的session数据
print(request.session.get("uid"))
#获取session数据的有效期,默认值是两周
print(request.session.get_session_cookie_age())
return HttpResponse("获取session数据")
3.删除session:
1
2
3
4
5
6
7
8
9
def del_session(request):
#删除session中指定变量的数据
try:
request.session.pop("uid") #防止一起使用报错
except KeyError:
pass
#删除当前域名下所有的session数据
request.session.clear()
return HttpResponse("删除session数据")

6.获取http请求数据:

1.获取查询字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def index2(request):
#查看request对象提供的属性
print(request)
#获取当前对象的属性和方法
print(request.__dir__())
#获取查询字符串,返回字典,GET是一个属性方法,被cached_property是django内部用于优化性能的一种方法
print(request.GET)
'''打印效果:<QueryDict: {'name': ['xiaoming'], 'password': ['123456']}>
QueryDict为参数集对象,因此对于QueryDict参数集的操作,我们完全可以基于字典的操作进行操作
'''
#获取查询字符串指定参数的单个值,直接使用中括号来获取数据,不存在的键会报错,所以我们可以用get
print(request.GET['name'])
print(request.GET.get('name'))
#如果查询字符串的参数有多个值,则需要使用getlist方法来获取数据
print(request.GET.getlist('lve'))
return HttpResponse('ok')

2.获取请求体数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def index3(request):
#获取请求体数据
"""只有post/put/patch具有请求体"""
#接收客户端提交的html表单数据
print(request.POST)
#request.POST无法接收上传文件,上传需要使用单独的属性来接收:request.FILES
print(request.FILES) #获取所有上传文件
print(request.FILES.get('avatar')) #这是一个文件上传处理对象
#上传文件
#import os
#avatar=request.FILES.get('avatar')
#with open(f"{os.path.dirname(__file__)}/avatar.bmp",'wb+') as f:
# f.write(avatar.read())
'''要获取表单以外的请求体数据,使用request.POST是行不通的,可以使用request.body来获取原生的请求体数据'''
print(request.body)
#处理上传过来的json数据
import json
print(json.loads(request.body))
return HttpResponse('ok')

3.获取请求头数据:

1
2
3
4
5
6
7
8
9
10
11
def index4(request):
#获取http请求头
print(request.META) #获取原生的请求头数据
print(request.headers)
#获取自定义的请求头,属于客户端开发人员自己声明的请求头
print(request.headers.get("company"))
#获取内部请求头
print(request.META.get("REMOTE_ADDR"))
#获取所有自定义的请求头,django会自动把客户端提交过来的请求头的参数名转换
print(request.META.get("HTTP_COMPANY"))
return HttpResponse('hello')

7.路由绑定:

当我们创建的视图和应用程序越来越多时,我们就需要用到include来进行路由绑定,使得我们不容易弄混
include用法:
#前提是你得在子目录中有建立存储关于子目录相关的url地址
在urls.py文件的urlpatterns写入:
from django.urls import path,include #要导入include的包
例子模板:path(“公共url地址(又叫路由前缀)”,include(“子应用目录名.路由模块”))

1.路由进阶:

1
2
3
4
5
6
7
8
9
10
11
from django.urls import  path  普通路由,又是一个字符串路由
from django.urls import re_path 正则路由,会把url地址看成一个正则模式与客户端的请求url地址进行正则匹配
#正则路由
re_path("^profile/(?P<pk>[0-9]+)/moblie/(?P<moblie>1[3-9]\d{9})/$",views.profile), #?P<>是给正则表达式起别名
应用正则路由的视图函数:
例子:#django中要求正则的路由中<>中的变量名,要作为参数传入视图函数
def profile(request,pk,moblie):
print(f"pk={pk},moblie={moblie}")
print(f"pk.type={type(pk)},moblie.type{type(moblie)}") #pk.type=<class 'str'>,moblie.type<class 'str'>,为字符串类型
return HttpResponse('获取用户信息')

2.路径转换器(内置的):

  1. str - 匹配除了 ‘/‘ 之外的非空字符串。如果表达式内不包含转换器,则会默认匹配字符串。
  2. int - 匹配 0 或任何正整数。返回一个 int 。
  3. slug - 匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。比如,building-your-1st-django-site 。
  4. uuid - 匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00。返回一个 UUID 实例。
  5. path - 匹配非空字段,包括路径分隔符 ‘/‘ 。它允许你匹配完整的 URL 路径而不是像 str 那样匹配 URL 的一部分。
(1).在子路由文件下添加路由:
1
2
#路由转换器  <转换器调用别名:路径参数变量名>
path('demo1/<int:num>',views.demo1),
(2).视图层实例:
1
2
3
4
def demo1(request,num):
#路由转换器 内置转换器
print(f"num={num},num_type={type(num)}")
return HttpResponse("ok")

3.注册自定义的路径转换器:

(1).在目录下写入注册自定义转换器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.urls.converters import StringConverter,register_converter

class MobileConverter(StringConverter):
regex = '1[3-9]\d{9}'

register_converter(MobileConverter,'mob')

'''
class MobileConverter(Object):
regex = '1[3-9]\d{9}'

def to_python(self,value):
#把路由上面的内容转换成python数据,并转换类型
#将匹配结果传递到视图内部时使用
return value

def to_url(self,value):
#将匹配结果用于反向解析传值时使用
return value

'''
(2).在目录中的urls.py文件进行导入自定义路由转换器,进行编写变量:
1
2
#自定义路由转换器
path('demo2/<mob:mobile>',views.demo2),
(3).在视图层中编写视图函数使用自定义的路由转换器:
1
2
3
4
def demo2(request,mobile):
#自定义路由器
print(f"mobile={mobile},mobile.type={type(mobile)}")
return HttpResponse("ok")

8.限制http请求:

常用的http请求:

1 post 添加/上传
2 get 获取/下载
3 put/patch 修改,其中put表示修改整体数据,而patch表示修改部分数据
4 delete 删除/废弃

可以限制http请求方法,来防止其他请求访问视图

  • 网页中的状态代码一般应该显示2xx的数字表示ok
  • 1xx表示特殊情况,表示请求没完成
  • 3xx表示该页面被别人跳转了
  • 4xx表示我们的客户端请求有问题
  • 5xx表示我们请求的服务端出错了

例子:

1
2
3
4
5
from django.views.decorators.http import require_http_methods
@require_http_methods(["DELETE"]) #请求方法要大写
def drop(request):
print(request.method) #获取本次客户端的http请求方法
return HttpResponse("删除数据"

9.响应数据:

1.响应html内容:

1
2
3
def index5(request):
#响应html内容
return HttpResponse(content='<h1>ok</h1>',status=201) #status为状态码,可以规定进入页面的状态码

2.响应json数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import json
from django.http.response import JsonResponse
def index6(request):
#响应json数据
data={
"name":"xiaoming",
"sex":True,
"age":16
}
#data=json.dumps(data)
'''返回json数据,可以通过HttpResponse的属性:content_type来执行的MIME格式
content_type的默认值为 text/html
'''
#return HttpResponse(content=data,content_type="application/json")
#在django中HttpResponse提供了非常多的子类,其中就有一个子类,专门用于返回json数据的
#from django.http.response.JsonResponse
'''利用JsonResponse来返回json数据'''
#return JsonResponse(data=data) #使用JsonResponse直接通过data属性设置数据即可,不需要设置content_type
'''JsonResponse返回非字典数据'''
# dt=[1,2,3]
# #当然,JsonResponse只能返回部分python内置的数据类型。如果是复杂的对象,资源是无法返回的。
# return JsonResponse(dt,safe=False) #要想把非字典数据返回的话,需要把safe属性值改为False

'''关于JsonResponse返回中文等多字节数据时的编码问题'''
dt={
"name":"小明",
"age":16
}
# 解决中文乱码问题,用json_dumps_params={'ensure_ascii':False}属性
#但是会不安全,在部分不支持中文的网络或服务器的环境下,容易出现乱码导致数据丢失
return JsonResponse(data=dt,json_dumps_params={'ensure_ascii':False})

3.返回图片信息:

1
2
3
4
5
def index7(request):
#返回图片信息
#web开发当中常见的图片格式: png,jpg(jpeg),gif,webp,svg,ico
data=open("1.png", "rb").read()
return HttpResponse(content=data,content_type='image/png')

4.下载文件信息:

1
2
3
4
5
6
7
8
9
def index8(request):
#下载文件信息
with open("day.zip","rb") as f:
data=f.read()
return HttpResponse(content=data,content_type='application/zip')
response=HttpResponse(content=data,content_type='application/zip')
给响应对象设置自定义响应头
response['company']="python310"
return response

5.页面跳转:

1
2
3
4
5
6
7
8
9
from django.shortcuts import redirect
from django.http.response import HttpResponseRedirect
#redirect 页面跳转函数
from django.urls import reverse
def index9(request):
"""页面跳转"""
# #往站外跳转
return redirect(to='http://www.baidu.com') #地址必须加上协议,如http协议
return HttpResponseRedirect('http://www.baidu.com') #也可以用这种方法,得导HttpResponseRedirect包

6.站内跳转:

1
2
3
4
5
6
7
8
9
10
11
#return redirect(to='/home/index6')
#reverse表示对视图进行逆向解析,获取当前视图名称对应的路由地址,也叫反向解析函数
#路由的path("url路径",视图函数,name="路由别名")
'''跳转到当前子应用下的指定路由别名对应的视图'''
return redirect(reverse("index6")) #reverse的参数值就是视图名称在路由中的name参数,也叫路由别名

# '''跳转到其他子应用下的指定路由别名对应的视图'''
# #congdangqian/home/index9 跳转到/user/info里面
# return redirect("/user/info")
#需要给子应用的路由前缀起一个别名,再结合子路由的别名进行跳转,再总路由中的include方法下给子路由的namespace赋值。
#例子:return redirect(reverse("us:user.info")) 且需要在子路由文件中给app_name赋值

模板层:

1.django的数据库操作:

1,得先在命令行或者数据库图形化工具下进行创建用于django项目的数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
###前提是得在djdemo的settings下设置数据库配置
#数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'register_info',
'USER':'root',
'PASSWORD':'Acky16140563.',
'HOST':'localhost',
'POST':'3306',
}
}

2,得在model文件下定义数据库中的表类:

1
2
3
4
5
6
7
from django.db import models

# Create your models here.

class RegisterUser(models.Model):
reg_username=models.CharField(max_length=100,blank=False)
reg_password=models.CharField(max_length=100,blank=False)

3.进行数据表的生成和迁移

1
2
3
###需要在终端上输入:
python manage.py makemigrations #在刚建的数据库中建立名为app_name的表
python manage.py migrate #把刚刚在model文件下定义的类的信息转换成数据库表中的列

模板语法

  • ‘’’ 变量 ‘’’:

  • ‘’’ 注释 ‘’’:


  • 多行注释:

    1
    2
    3
    {% comment %}  注释开始语
    中间为注释
    {% endcomment %} 注释结束语
  • ‘’’ 标签 ‘’’:

    1
    {% 标签名 %}

模板层中显示变量

视图层代码:

1
2
3
4
5
6
def index2(request):
"""模板中显示变量"""
data1=(1,2,3,4)
data2=[1,2,3,4]
return render(request,'index2.html',locals())

html代码:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板变量</title>
</head>
<body>
<p>data1={{ data1.1 }}</p> {# 获取元组的索引位置元素 django中的双花括号不能写小括号、中括号 #}
<p>data2={{ data2|last }}</p> {# 过滤器,获取元组、列表最后一个元素,不能使用-1,只能使用过滤器 #}
</body>
</html>

if判断标签语法

视图层代码:

1
2
3
4
5
6
7
def index4(request):
book_list1=[
{"id":10001,"name":"张三"},
{"id":10002,"name":"李四"}
]
return render(request,'index4.html',locals())

html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
table{
border:1px solid red;
border-collapse:collapse;
width:800px;
}
td{
border:1px solid red;
text-align:center;
}
</style>
</head>
<body>
<table>
<tr>
<td>序号</td>
<td>编号</td>
<td>标题</td>
</tr>
{% for book in book_list1 %}
<tr>
<td>{{ forloop.counter }}</td> //revcounter 为倒序计数
<td>{{book.id}}</td>
<td>{{book.name}}</td>
</tr>
{% endfor %}
</table>
</body>
</html>

1
2
3
4
5
6
7
8
9
10
{{ forloop.last }}
{{ forloop.first }} //进行标记判断,当是计数器为最后一个或者第一个时的返回值应该为true。

#取余
{{% if forloop.counter|divisibleby:2 %}} //取余用到过滤器divisibleby:取余数;

#字典在循环中无法自行解包, {{% for key,value in book_dict.items %}};
#items 后面加上reversed进行左右变量的转换(反向循环字典或列表,元组)

#forloop.parentloop //在嵌套循环中,指向当前循环的上级循环

过滤器

过滤器本质就是函数[可以是python函数,也可以是开发者自定义函数]

1
2
3
4
5
6
7
8
9
10
11
#单个无参数过滤器,变量默认作为过滤器的第一个参数,过滤器中return的内容作为结果被输出
{{ 变量| 过滤器 }}

#单个有参数过滤器,参数1是变量,英文冒号后面跟着的参数按顺序依次使用英文逗号排列(默认过滤器除了变量,只有一个参数)
{{ 变量|过滤器:参数2}}

#多个无参数过滤器,每个|(竖杠)左边内容作为右边过滤器的默认第一参数
{{ 变量|过滤器|过滤器2|过滤器3|.... }}

#多个有参数过滤器,每个过滤器的参数1是竖杠作为结果,最后一个过滤器返回结果被输出
{{ 变量|过滤器1:参数2 |过滤器2:参数2 |.... }}

filter(过滤器)本质就是一个函数,这种函数,允许我们直接在模板中使用。使用过滤器的情况一般就是希望在模板中对数据进行格式化处理或对数据进行规范输出和调整

文档:https://docs.djangoproject.com/zh-hans/3.2/ref/templates/builtins/#ref-templates-builitins-tags

内置过滤器

safe

safe声明当前视图提供的数据中,html代码不需要进行实体字符转义

如果来自客户端的留言等信息,在是否使用safe时,一定要考虑清楚,可以提前清楚script等不安全的标签

视图层代码

1
2
3
4
def index5(request):
'''内置过滤器'''
content1=">"
return render(request,'index5.html',locals())

html代码

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ content1 }}
{{ content1 | safe }}
</body>
</html>

内置过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|      过滤器       |                    用法                    |              代码               |
| :---------------: | :----------------------------------------: | :-----------------------------: |
| last | 获取列表/元组的最后一个成员 | {{list| last}} |
| first | 获取列表/元组的第一个成员 | {{list| first}} |
| **length** | **获取数据的长度** | {{list| length}} |
| **default** | **当变量没有值的情况下,系统输出默认值** | {{str| default="默认值"}} |
| **safe** | **让系统不要对内容的html代码进行实体转义** | {{htmlcontent| safe}} |
| upper | 字母转换大写 | {{str| upper}} |
| lower | 字母转换成小写 | {{str| lower}} |
| title | 每个单词首字母转换成大写 | {{str| title}} |
| **date** | **日期时间格式转换** | {{value| date:"D d M Y"}} |
| cut | 从内容中截取掉同样字符的内容 | {{content| cut:"helo"}} |
| escape | 把内容中的html特殊符号转换成实体字符 | {{content| escape}} |
| filesizeformat | 把文件大小的数值转换成单位表示 | {{filesize s| filesizeformat}} |
| **join** | **按指定字符拼接内容** | {{list| join("-")}} |
| random | 随机提取某个成员 | {{list| random}} |
| slice | 按切片提取成员 | {{list| slice":-2"}} |
| **truncatechars** | **按字符长度截取内容** | {{content| truncatechars:30}} |
| truncatewords | 按单词长度截取内容 | {{content| truncatewords:30}} |

示例代码:

视图层代码:

1
2
3
4
5
6
7
8
9
10
from django.utils import timezone
def index5(request):
'''内置过滤器'''
content1=">"
content2="<hello>"
now=timezone.now()
filesize=123456789
area=['河北省','石家庄','裕华区']
menu=['首页','商品列表','分类','商品标题']
return render(request,'index5.html',locals())

html代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.img{
max-weight: 400px;
}
</style>
</head>
<body>
{{ content1 }}
<p>{{ content1 | safe }}</p>
{{ title | default:"hello" }}
<hr>
{{ content2| escape }}
<p>{{ now | date:"Y\-M\-D H:i:s"}}</p>
<hr>
{{filesize| filesizeformat}}
{{area | join:"-"}}
{{menu| join:"/"}}
<p>{{area| random }}</p>
</body>
</html>
自定义过滤器

customer filter

要声明自定义过滤器并且能在模板中正常使用,需要3个步骤。

  1. 确保当前需要使用过滤器的子应用已经注册到了INSTALLED_APPS中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'home',
    'user',
    'tem', #模板引擎
    'login',
    ]
  2. 编写过滤器,过滤器必须在子应用下的templatetags包(如果子应用目录下没有,就自己创建)里面创建对应的python文件中。tem.templatetags.my_filters.py的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from django import template

    register=template.Library()

    #自定义过滤器
    #@register.filter("过滤器别名")
    @register.filter("mobile")
    def moblie_filter(content,flag="****"): #过滤器必须要有一个以上的参数,提供给模板调用
    return content[:3]+flag+content[-3:]
    #必须要有返回值,否则模板中没有内容显示
  3. 在模板中通过load标签加载当前子应用已经声明的过滤器函数,load标签的使用最好写在模板的第一行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {% load my_filter %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <p>{{ content3 | mobile:"新年快乐" }}</p> {# 传入变量给过滤器中进行自定义更换隐私字符 #}
    </body>
    </html>

模板继承

模板分离
1
django中提供了{% include "模板文件名" %}标签模板分离技术

视图层代码

1
2
3
4
def index6(request):
"""模板分离-文章列表页面"""

return render(request,'index6.html',locals())

模板层:

  • 公共页面代码(header)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.header{
height:88px;
background-color: aquamarine;
}
</style>
</head>
<body>
<div class="header">
公共的头部html代码
</div>
</body>
</html>
  • 分离的独特内容页面代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    {% include "header.html" %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    .main{
    height:200px;
    text-align: center;
    border: 1px solid #f00;
    }
    </style>
    </head>
    <body>
    <div class="main">
    列表页独有的内容
    </div>
    </body>
    </html>

其实模板分离,这种方式,虽然达到了页面代码复用的效果,但是由此也会带来大量的碎片化模板,导致维护模板的成本上升。

因此,大部分框架中除了提供这种模板分离技术以外,还并行的提供了模板继承给开发者。

模板继承(主要使用)

模板继承可以用block标签把该文件页面独有的代码(子模板)继承公共部分(父模版),这样就不用因为公共部分(父模版)的随意变化导致的文件过多

  1. 显示子模版的时候,继承父模板的公共内容

    1
    {% extends "base.html" %} {# base.html 为父模板 #}
  2. 视图层代码:

    1
    2
    3
    def index7(request):
    """模板继承-用户页面"""
    return render(request,'index7.html',locals())
  3. 模板层代码:

    父模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    .header{
    height: 100px;
    background-color: orange;
    text-align: center;
    }
    .main{
    height: 300px;
    background-color: lightsteelblue;
    text-align: center;
    }
    .footer{
    height: 15px;
    background-color: #ff0;
    text-align: center;
    }
    </style>
    </head>
    <body>
    <div class="header">
    公共头部
    </div>
    <div class="main">
    {% block main %} {# 子模版可以通过声明block的变量名进行覆盖这片区域 #}
    主体内容
    {% endblock main %}
    </div>
    <div class="footer">
    公共脚部
    </div>
    </body>
    </html>

    子模版:

    1
    2
    3
    4
    {% extends "base.html" %}
    {% block main %}
    index7.html的主题内容
    {% endblock main %}

    也可以在父模板中用block标签写出默认值内容,然后再在子模版中用block标签把独特的内容将默认值覆盖,也可以用block进行标记,然后如果需要添加内容,也可以通过block添加内容

  4. block.super

    block标签中的super可以进行在block标签中引用没被覆盖时的内容(必须得用两个{})

    1
    2
    3
    4
    5
    6
    {% extends "base.html" %}
    {% block main %}
    <-- 可以通过{{block.super}}重新获取被覆盖的父模板内容 -->
    {{ block.super }}
    index7.html的主题内容
    {% endblock main %}
    1
    2
    3
    4
    5
    <div class="main">
    {% block main %} {# 子模版可以通过block的名字进行覆盖这片区域 #}
    主体内容
    {% endblock main %}
    </div>

使用模板层的三个步骤

django框架中使用模板引擎把视图中的数据更好的展示给客户端,需要三个步骤:

  • 在项目配置文件中指定保存模板文件的模板目录。(一般模板目录都是设置在项目根目录或者主应用目录下)
  • 在视图中基于django提供的渲染函数绑定模板文件和需要展示的数据变量
  • 在模板目录下创建对应的模板文件,并根据模板引擎内置的模板语法,填写输出视图传递过来的数据

1.配置模板目录:

  • 手动在djdemo中创建一个普通目录templates

  • 然后在settings.py中的模板引擎配置TEMPLATES中模板目录列表’DIRS’中添加templates的路径,用BASE_DIR/进行拼接

    1
    2
    3
    'DIRS': [
    BASE_DIR/'templates',
    ],
  • 在子应用定义列表INSTALLED_APPS 中注册子应用

2.绑定模板文件:

  • 在子应用目录下手动创建urls.py子路由文件,并往里面写入几行代码:

    1
    2
    3
    4
    5
    6
    from django.urls import path,re_path
    from . import views

    urlpatterns=[

    ]
  • 在总路由文件中写入子路由文件变量

    1
    path('tem/',include('tem.urls'))
  • 创建一个存储模板(html)文件的目录,并根据模板引擎内置的模板语法,填写输出视图传递过来的数据
    模板文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    </head>
    <body>
    {# django模板中的注释内容 #}
    {{ title }}
    </body>
    </html>
  • 视图函数编写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    from django.shortcuts import render
    from django.template.loader import get_template
    from django.http.response import HttpResponse
    # Create your views here.
    def index1(request):
    #模板引擎的基本使用
    data={}
    title="模板引擎的基本使用"
    data["title"]=title
    #return render(request,"index1.html",context={"title":title})
    #return render(request,"index1.html",data)
    #return render(request,"index1.html",locals()) #通过locals()获取当前函数中所有信息,收集成一个字典,并传参给模板
    #基于settings,py中配置的模板引擎获取DIRS配置的模板文件,转换成Template模板对象
    template=get_template("index1.html")
    #调用模板对象提供的render函数,把本次客户端的请求对象和视图中数据data传参到模板文件中,进行渲染
    #将来如果要对模板中的内容进行数据缓存[cache],可以对content进行保存起来,将来如果直接读取content的话,
    # 则django就不需要操作数据库或者进行渲染,因为模板渲染的过程就是正则替换的过程
    content=template.render(data,request)
    return HttpResponse(content)

静态文件

开发中在开启了debug模式时,django可以通过配置,允许用户通过对应的url地址访问django的静态文件。(必须手动创建)

settings.py 中代码:

1
2
3
4
STATIC_URL = '/static/'
STATICFILES_DIRS = ( os.path.join(BASE_DIR,'static'), )
或者:
STATICFILES_DIRS = [ BASE_DIR / 'static' ]

注意:
项目上线以后,关闭debug模式时,django默认是不提供静态文件的访问支持,项目部署的时候,我们会通过收集静态文件使用nginx这种web服务器来提供静态文件的访问支持。

视图进阶

新建一给子应用,cbv。接下来在这个子应用下面,我们学习类视图的操作。

1
python manage.py startapp cbv

在setting.py配置文件中注册子应用,setting.py, 代码:

1
2
3
4
INSTALLED_APPS=[
#....省略
"cbv",
]

创建子应用的路由文件,cbv.urls,代码:

1
2
3
4
5
from django.urls import path 
from . import views
urlpatterns=[

]

在总路由中进行子路由文件的注册。 ‘djdemo.urls ‘,代码

1
path("cbv/",include("cbv.urls"))

类视图

Class Base View,简称”CBV”,与我们之前编写的视图函数不同,类视图是类的结构编写视图代码的。可以让我们实现相关业务代码的整合。同时还在函数视图的基础上,可以实现对于客户端访问的http请求进行分流和限制。

类视图的基本定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from django.shortcuts import render
# Create your views here.
#1.类视图必须直接或间接继承于django.views.View
#2.在django的类视图中,所有提供给外界用户访问的视图方法名必须是http请求动作的小写名称
# 也就是说,方法名只能是 get,post,put,patch,delete ...
#3.使用类视图,可以有效的减少路由绑定代码
# 3.1 可以直接通过视图方法名就可以达到限制客户端访问当前视图方法的http请求
# 3.2 客户端访问对应的URL地址就会来到视图类中,访问这个地址所使用的http请求,就是将来视图类自动执行的方法
# 3.3 有了类视图,我们可以把多个函数视图的公共代码进行抽离或者封装成一个函数进行调用,有利于代码复用
#4. 同一个视图类中,方法名不能出现重复
from django.views import View
from django.http import HttpResponse
class IndexView(View):
#类视图的公共方法/公共属性
def ret(self,request,data):
print(request.method) #在类视图中,不仅可以通过视图方法中的参数,接收路由传递过来的请求对象,还可以通过self.request来获取客户端路由转发过来的请求对象
return HttpResponse(data)
def get(self,request):
"""只允许通过get请求访问,建议编写读取数据的页面,例如:首页,列表页。。。"""
#视图中的视图方法,与原来的函数视图中的代码是一模一样的。原来怎么写的,现在就怎么写
return self.ret(request,'hello')
def post(self,request):
"""只允许通过post请求访问,一般用于上传,添加数据的页面"""
return self.ret(request,'hello')
def put(self,request):
"""只允许通过put请求访问,一般用于修改,更新数据的页面"""
return self.ret(request, 'hello')
def patch(self,request):
"""只允许通过patch请求访问,一般用于修改,更新数据的页面"""
return self.ret(request, 'hello')
def delete(self,request):
"""只允许用delete请求访问,一般用于处理删除数据的页面"""
return self.ret(request, 'hello')

类视图的路由绑定

调用as_view的本质,就是调用内部的view视图函数

进入内部通过dispatch函数进行路由分发,通过请求方法,获取到请求的视图函数的同名方法

1
2
3
4
5
6
7
8
from django.urls import path
from . import views
#在django中,路由只识别函数视图,对于类视图,我们必须要通过as_view()帮我们把类的视图方法转换成函数视图
urlpatterns=[
#path('index/',views.IndexView.as_view())
#as_view()帮我们把类的视图方法转换成函数视图
path('index/',views.IndexView.as_view())
]

视图基类的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import json
from django.http import HttpResponse
"""
自定义基类视图
视图基类,可以专门封装一些公共的属性或者方法,提供给将来开发的时候,用于完成特定业务的。
这也可以封装成一个个视图基类,然后让具体的视图类直接继承,就不需要重复编写这些代码
"""
class ApiView(View):
# 可以把没有参数的类方法,转换成普通属性来调用
@property
def data(self):
"""用于接受并转换json数据成字典"""
# print(self.request.META)
ct = self.request.META.get("CONTENT_TYPE")
# print(f"ct={ct}")
if 'multipart/form-data' in ct:
"""表单上传数据(允许上传文件)"""
return self.request.POST.dict()
if 'application/x-www-form-urlencoded' in ct:
"""表单上传数据"""
return self.request.POST.dict()
if 'application/json' in ct:
try:
data = json.loads(self.request.body)
return data
except Exception:
return None

class HomeView(ApiView):
def post(self,request):
print(self.data)
return HttpResponse("ok")

def put(self,request):
print(self.data)
return HttpResponse("ok")

def patch(self,request):
print(self.data)
return HttpResponse("ok")

视图基类的相关扩展

1
2
3
4
5
6
7
8
9
10
11
在django提供的视图类View使用中,还提供了一些视图子类。
不过这块内容需要结合diango的数据库操作才可以使用,所以我们后面再来学习。
视图子类是django为了方便开发者快速提供基于不同http请求视图而提供的。

from django.views.generic import ListView,DetailView,CreateView,UpdateView,DeleteView

ListView: 列表视图,可以通过get请求访问,用于展示列表数据,内置了分页功能
DetailView: 详情视图,可以通过get请求访问,用于展示单个数据
CreateView: 添加视图,可以通过get/post请求访问,用于添加单个数据
UpdateView: 更新视图,可以通过get/post请求访问,用于更新单个数据
DeleteView: 删除视图,可以通过get请求访问,用于删除单个数据

中间件

MiddleWare,是通过Django请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变Django的输入输出。[输入指代的就是客户端向服务端django发送数据,输出指代django根据客户端要求处理数据的结果返回给客户端]

钩子就是编程开发的一个术语,hook,钩子可以理解为一段代码(要么是类,要么是函数),它的作用就类似日常生活中墙上的钩子,不需要的时候,挂在墙上不会占用房子的空间,但是需要的时候我们可以把一些物件挂在上面。

这种中间件在平时不使用情况下不会耗费任何的性能,如果编写了中间件以后,可以在特定的条件下,全局执行!!

文档:https://docs.djangoproject.com/zh-hans/3.2/topics/http/middleware

内置中间件

django框架内部声明了很多的中间件,这些中间件有着各种各样的用途,有些没有被使用,有些被默认开启使用了。而被开始使用的中间件,都是settings.py的MIDDLEWARE中注册使用的

1
2
3
4
5
6
7
8
9
10
#中间件,全局的钩子,django在接收请求和响应数据都会自动的执行一遍这里的代码。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', #安全监测相关的中间件,防止页面过期,js跨站脚本攻击xss
'django.contrib.sessions.middleware.SessionMiddleware', #session加密和读取和保存session相关
'django.middleware.common.CommonMiddleware', #通用中间件,用于给url进行重写,自动给URL后面加上/
'django.middleware.csrf.CsrfViewMiddleware', #防止网站遭到csrf攻击
'django.contrib.auth.middleware.AuthenticationMiddleware', #用户认证的中间件
'django.contrib.messages.middleware.MessageMiddleware', #错误提示信息的中间件【提示错误信息,一次性提示】
'django.middleware.clickjacking.XFrameOptionsMiddleware', #用于防止点击劫持攻击的iframe标签
]

Csrf攻击

跨站请求伪造,Cross-site request forgery,利用用户在不知情的情况下实现伪造表单提交给服务端进行攻击的手段。

csrf攻击原理

image

django中提供了一个Csrfmiddleware的中间件给开发者用于防止网站用户遭到这种攻击收发。

中间件主要是每次客户端通过post,patch,put等方式提交数据操作时,判断当前表单是否是隐藏了一个csrf_token的随机字符串令牌。如果有这个随机字符串。则中间件则会判断这个随机字符串是否由服务端提供的。

我们开发者只需要在每一个表单页面中,内置一个隐藏的输入框里面填写csrf_token则可以让当前页面的表单顺利提交数据到后台

csrf_token的生成是每次都是基于服务端的密钥进行随机生成的,所以如果没有密钥的情况下,则生成的随机token令牌则会轻易被django识别到。

1
2
3
4
5
{{csrf_token}}  #可以获取csrf的token,但是是会显现在页面中
{% csrf_token %} #可以获取csrf的token,这个是隐形的,不会显现在屏幕中
在视图函数中可以通过request.user可以获取当前登录该后台的用户名

将来我们使用ajax以json的方式提交数据,则不能使用{% csrf_token %},只能使用{{ csrf_token }}在json数据里面了

GZIP压缩

压缩所有现代浏览器的响应,节省宽带和传输时间

注意,GzipMiddleware目前被认为是一种安全风险,并且容易TSL/SSL提供的保护无效的攻击。

setting.py,代码:

1
2
3
4
5
6
7
8
9
10
11
#中间件,全局的钩子,django在接收请求和响应数据都会自动的执行一遍这里的代码。
MIDDLEWARE = [
'django.middleware.gzip.GZipMiddleware', #实际工作中,我们往往采用web服务器来实现gzip压缩
'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',
]

自定义中间件

django中提供了2种不同的中间件声明方式。主要在django1.9或者2.0出现

函数式中间件

在项目的主应用目录下创建一个专门存放中间件函数的模块 主应用djdemo/my_middleware.py 代码:

1
2
3
4
5
6
7
8
9
10
11
MIDDLEWARE = [
'django.middleware.gzip.GZipMiddleware', #实际工作中,我们往往采用web服务器来实现gzip压缩
'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',
'djdemo.my_middleware.user_source_middleware', #注册自己的中间件[不管是函数式中间件还是类中间件都是这样注册的]
]

因为中间件一旦注册了以后,会在全局生效,所以我们访问任何一个视图都可以看到中间件执行的过程。

类中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from django.utils.deprecation import MiddlewareMixin  #混入类,一般用于声明类的公共部分代码或公共属性的

class CustomMiddleware(MiddlewareMixin):
def process_request(self,request):
#方法名是固定的,该方法会在用户请求访问路由解析完成以后,调用试图之前自动执行
print("[类中间件] 客户端ip地址=", request.META.get("REMOTE_ADDR")) # 获取本次客户端的请求地址
print("[类中间件] 1.process_request在路由解析以后,产生request对象,视图执行之前,会执行这个方法")
#用途:权限,路由分发,cdn,用户身份识别,白名单,黑名单....
#注意,此方法不能使用return,使用则会报错!!!

def process_view(self,request,view_func,view_args,view_kwagrs):
#用途: 进行缓存处理,识别参数,根据参数查询是否建立缓存
print("[类中间件] 2.process_view在视图中接受了参数以后,没有执行内部代码之前")
#可以返回response对象,如果返回reponse对象以后,则当前对应的视图函数将不会执行
#return HttpResponse("ok")
#也可以不返回response,则默认返回None,django就会自动执行视图函数

def process_response(self,request,response):
print("[类中间件] 4.process_response在视图执行之后,才执行的")
#用途: 记录操作历史,记录访问历史,修改返回给客户端的数据,建立缓存
#必须返回response对象,否则报错!!
return response

def process_exception(self,request,exception):
print(exception)
#用途: 进行异常的处理或者记录错误日志
print("3. process_exception会在视图执行发生异常的时候才会执行")

def process_template_response(self,request,response): #知道一下就行了
#用途: 建立页面缓存
print("5.process_template_response只有在视图调用了模板之后,才会执行!!!")
return response

数据库

常用数据库两大阵营:

  1. 关系数据库[RDB]: 数据库中存储数据的表之间存在某种内在的关联关系,因为这种关系,所以我们称这一类型数据为关系型数据库。

    共同的特点: 都使用了SQL语句进行数据库操作。

    常见的数据库: mysql[MariaDB], PostgreSQL, Oracle, MSServer, DB2. sqlite, Access

  2. 非关系型数据库[Nosql]: 泛指那些不适用SQL语句进行数据库操作的所有其他数据库。Redis,MongoDB,Memcached,Elasticsearch/Sphinx/Solr,HBase/CouchDB

耦合性:类似成语里面的藕断丝连,表示2段程序之间的关联性和依赖性的强度。

工作中要追求代码的低藕性,就是降低2段程序之间的关联性和依赖性。 高内聚低耦合

ORM框架

O是object,也就是类对象的意思。

R是relation,翻译成中文是关系,也就是关系数据库中数据表的意思

M是mapping,是映射的意思。

ORM框架会帮我们把类对象和数据表进行一对一的映射,让我们可以通过类对象来操作对应的数据表。

ORM框架还可以根据我们设计的类自动帮我们生成数据库中的表格,省去了自己建表的过程。

django中内嵌了ORM框架,不需要直接编写SQL语句进行数据库操作,而是通过定义模型类,操作模型类来完成对数据库中表中的数据的增删改查和数据表创建等操作。

ORM的优点

  • 数据模型类都在一个地方定义,更容易更新和维护,也有利于重用代码。
  • ORM有现成的工具,很多功能都可以自动完成,比如数据消除、预处理、事务等等。
  • 它迫使你使用MVC架构,ORM就是天然的Model,最终使代码更清晰。
  • 基于ORM的业务代码比较简单,代码最少,语义性好,容易理解。
  • 新手对于复杂业务容易写出性能不佳的SQL,有了ORM不必编写复杂的SQL语句,只需要通过操作模型对象即可同步修改数据表中的数据。
  • 开发中应用ORM将来如果要切换数据库,只需要切换ORM底层对接数据库的驱动【修改配置文件的连接地址即可】

ORM缺点

  • ORM库不是轻量级工具,需要花很多精力学习和设置,甚至不同的框架,会存在不同操作的ORM。
  • 对于复杂的业务查询,ORM表达起来比原生的SQL要更加困难和复杂。
  • ORM操作数据库的i性能要比原生的SQL差。[ORM内部要拼接SQL语句]
  • ORM抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的SQL。【自己使用pymysql另外操作即可,用了ORM并表示当前项目不能使用别的数据库操作工具了。】

配置数据库连接

在setting.py中保存了数据库的连接配置信息,Django默认初始配置使用sqlite数据库。

我们可以通过一下步骤来使用django的数据库操作

1
2
3
4
1.配置数据库连接信息
2.在子应用的models.py中定义模型类
3.生成数据库迁移文件并执行迁移文件[注意:数据迁移是一个独立的功能,这个功能在其他web框架未必和ORM一块的]//不是必要的,有些公司,直接由DBA(数据管理员)创建数据表的,那么就不需要数据迁移了。
4.通过模型类对象提供的方法或属性完成数据表的增删改查操作
  1. 使用MySQL数据库首先需要安装驱动程序

    1
    pip install pymysql
  2. 在django的主应用目录的_init_.py文件中添加如下语句

    1
    2
    3
    from pymysql import install_as_MySQLdb

    install_as_MySQLdb()#让pymysql以MySQLDB的运行模式和Django的ORM对接运行

    作用是让Django的ORM能以mysqldb的方式调用pymysql

  3. 修改database配置信息,setting.py配置文件配置数据库连接信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'register_info',
    'USER':'root',
    'PASSWORD':'Acky16140563.',
    'HOST':'localhost',
    'POST':'3306',
    'POOL_OPTIONS':{ #数据库连接池配置
    'POOL_SIZE': 10, #默认情况下,打开的数据库连接的数量
    'MAX_OVERFLOW': 10 #负载情况下,允许溢出的连接数量
    }
    }
    }
  4. 在mysql中创建数据库

    1
    2
    create database school;  #mysql8.0默认就是utf8mb4
    create database school default charset=utf8mb4; #mysql8.0之前的版本

    mysql5.7,django3.0以下版本,错误解决

    数据库兼容问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #错误提示
    django.db.utils.ProgrammingError:(1064,"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED' at line 1")

    #解决方案
    #在settings.py的database选项中每一个数据库连接加上OPTIONS选项配置
    DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME': 'register_info',
    'USER':'root',
    'PASSWORD':'Acky16140563.',
    'HOST':'localhost',
    'POST':'3306',
    'POOL_OPTIONS':{ #数据库连接池配置
    'POOL_SIZE': 10, #默认情况下,打开的数据库连接的数量
    'MAX_OVERFLOW': 10 #负载情况下,允许溢出的连接数量
    },
    'OPTIONS':{
    'isolation_level': None
    }
    }
    }

定义模型类

  • 模型类被定义在”子应用/models.py”文件中
  • 模型类必须直接或者间接继承自django.db.models.Model类

在子应用目录下的models.py文件中定义模型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from django.db import models
#字段选项
SEX_CHOICES=(
(0,"保密"),
(1"男"),
(2,"女"),
)

# Create your models here.
#自定义的模型类,每一个类就对应着一个数据表,类名建议与表名关联
#模型类必须直接或间接继承于 django.db.models.MOdel
class RegisterUser(models.Model):
#模型中声明Meta用于表信息的相关内容
#db_table 用于设置当前模型类对应的数据表是哪一张,如果不设置,则表名对应为"子应用名_模型类名".lower()
#class Meta:
# db_table="student"
#设置表名的中文提示
#verbose_name="学生信息"
#verbose_name_plural=verbose_name #设置当前复述情况下,中文提示
#字段映射
id= models.AUtoField() #主键,这个主键id,默认情况下,我们不需要在模型类显式声明,django会自动帮我们补充,同时还会给id设置一个调用别名叫:pk
#age=models.SmallIntegerField(db_index=True) db_index设置索引
#sex=models.BooleanField(default=0,choices=SEX_CHOICES) bool值字段
reg_username=models.CharField(max_length=100,blank=False) reg_password=models.CharField(max_length=100,blank=False)

class LessonTable(models.Model):
day1=models.CharField(max_length=255,blank=False)
day2=models.CharField(max_length=255,blank=False)
day3=models.CharField(max_length=255,blank=False)
day4=models.CharField(max_length=255,blank=False)
day5=models.CharField(max_length=255,blank=False)
day6=models.CharField(max_length=255,blank=False)
day7=models.CharField(max_length=255,blank=False)

class MusicTable(models.Model):
music_url=models.CharField(max_length=255,blank=False)
1
2
3
4
5
6
7
class BaseModel(models.Model):
#auto_now_add=True 表示当前时间对象作为默认值存储在数据表中
created_time=models.DateTimeField(auto_now_add=True)
#auto_now=True 表示当前数据创建/修改时,采用当前时间对象作为默认值存储在数据表中
updated_time=models.DateTimeField(auto_now=True)
class Meta:
#设置当前模型为抽象模型,也就是说,这个模型不会被django当前一个数据表对应的模型,而是当成一个普通的类

db_table 用于设置当前模型类对应的数据表是哪一张,如果不设置,则表名对应为**”子应用名_模型类名”.lower()**

2)关于主键

django会为模型自动声明一个自动增长的主键列,每个模型只能有一个主键列

如果使用选项设置某个字段的约束属性为主键列后,django不会再创建自动增长的主键列

默认创建的主键列属性为id,可以用pk代替

3)属性命名规则
  • 不能是python的关键字或保留字

  • 不允许使用连续的2个下划线,这是由django的查询方式决定的。__是关键字来的,不能使用!!!

  • 定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:

    1
    2
    3
    属性名=models.字段类型(约束选项,verbose_name="注释")

    #在mysql数据表中的字段名如果在python是一个关键字/保留字。则选项中需要通过db_column()来进行关联绑定
4)字段类型
类型 说明
AutoField 自动增长的IntegerField,通常不用指定,不指定时django会自动创建属性名为id的自动增长属性,并且提供一个PK属性与主键进行关联
BooleanField 布尔类型,值为True或False
NullBooleanField 支持Null、True、False三种值
CharField 字符串,参数max_length表示最大字符个数,对应mysql中的varchar
TextField 大文本字段,一般大段文本(超过4000个字符)才使用
IntegerField 整数
DecimalField 十进制浮点数,参数max_digits表示总位数,参数decimal_places表示小数位数,常用于表示分数和价格 Decimal(max_digits=7,decimal_places=2)==>99999.99~0.00
FloatField 浮点数
ImageField 继承于FileField,对上传的内容进行校检,确保是有效的图片
5)约束选项
选项 说明
null 如果为True,表示允许为空,默认值是False。相当于python的None
blank 如果为True,则该字段允许为空白,默认值是False。相当于python的空字符串,””
db_column 数据表中真实的字段的名称,如果未指定,则使用属性的名称
db_index 若值为True,则在表中会为此字段创建索引,默认值是False。相当于SQL语句中的key
default 默认值,当不填写数据时,使用该选项的值作为数据的默认值
primary_key 如果为True,则该字段会成为模型的主键,默认值是False,一般不用设置,系统默认设置
unique 如果为True,咋该字段在表中必须有唯一值,默认值是False。相当于SQL语句中的unique
verbose_name 设置此字段在admin界面上的显示名称

注意:null是数据库范畴的概念,blank是表单验证范畴

修改过字段选项【添加或更改】均要执行makemigrations和migrate

Meta类

使用模型类内部Meta类来给模型赋予属性

1
2
3
4
#模型中声明Meta用于表信息的相关内容
#db_table 用于设置当前模型类对应的数据表是哪一张,如果不设置,则表名对应为"子应用名_模型类名".lower()
#class Meta:
# db_table="student"

ORM操作

ORM的基本操作的核心是模型类的管理器对象

管理器对象(增加数据)

每个继承自models.Model的模型类,都会有一个objects对象被同样继承下来。这个对象叫管理器对象

数据库的增删改查可以通过模型的管理器实现

1
2
3
4
5
6
7
8
9
10
11
12
class MyMode1(models.Model):
.......
#objects是管理器对象
#方法一
MyModel.objects.create(属性1=值1,属性2=值2,...) #成功后会返回创建好的实体对象,失败则会抛出异常

#方法二,创建MyModel实例对象,并调用save()进行保存
obj=MyModel(属性=值,属性=值)
Obj.属性=值
obj.save()


Django Shell

在Django提供了一个交互式的操作项目叫Django Shell 它能够在交互模式用项目工程的代码执行相应的操作

利用Django Shell 可以替代编写view的代码来进行直接操作

注意:项目代码发生变化时,重新进入Django Shell

启动方式:

1
python manage.py shell

使用方法:

1
2
3
4
5
6
7
8
from 要操作的应用名称.models import 你要操作的模型类
from login.models import RegisterUser
#然后即可在此模型类的表中插入数据
#方法一
r1=RegisterUser.object.create(属性=值...)
#方法二
r2=RegisterUser(属性=值....)
r2.save()
查询操作
  1. all()方法

    用法:MyModel.objects.all()

    作用:查询MyModel实体中所有的数据

    等同于select * from table

    返回值:QuerySet容器对象(可以把它当作一个数组),内部存放MyModel实例对象

  2. 可以在模型类中定义下划线_str_下划线方法,自定义QuerySet中的输出格式例如

    1
    2
    def _str_(self):
    return '%s_%s_%s_%s'%(self.title,self.price...)#自定义的输出格式
  3. values(‘列1’,’列2’)方法

    用法:MyModel.objects.values(…)

    作用:查询部分列的数据并返回

    等同于select 列1,列2 from xxx

    返回值: QuerySet,返回查询结果容器,容器内存字典,每个字典代表一条数据,格式为{‘列1’:值1,’列2’:值2}

  4. values_list(‘列1’,’列2’…)

    用法:MyModel.objects.values_list(….)

    作用:返回元组形式的查询结果

    等同于select 列1,列2 from xxx

    返回值:QuerySet容器对象,内部存放元组

    会将查询出来的数据封装到元组中,在封装到查询集合QuerySet中

  5. order_by()

    用法:MyModel.objects.order_by(‘列’,’’列)

    作用: 与all()方法不同,它会用SQL语句的ORDER BY子句对查询结果进行根据某个字段选择性的进行排序

    说明:默认是按照升序排序,降序排序则需要在列前增加’-‘表示

条件查询
  1. filter(条件)

    语法:MyModel.objects.filter(属性1=值1,属性2=值2)

    作用:返回包含此条件的全部数据集

    返回值:QuerySet容器对象,内部存放MyModel实例

    说明:当多个属性在一起时为”与”关系

  2. get(条件)

    语法:MyModel.objects.get(条件)

    作用:返回满足条件的唯一一条数据

    说明:该方法只能返回一条数据,查询结果多于一条数据则抛出Model.MultipleObjectsReturned异常,查询结果如果没有数据则抛出Model.DoesNotExist异常

  3. 查询谓词

    定义:做更灵活的条件查询时需要使用查询谓词

    说明:每一个查询谓词是一个独立的查询功能

    • (字段)__exact:等值匹配

    示例:

    1
    2
    Author.objects.filter(id__exact=1)
    #等同于select * from author where id = 1
    • __contains:包含指定值

      1
      2
      Author.objects.filter(name__contains='w')
      #等同于select * from author where name like '%w%'
    • __startswith:以xxx开始

    • __endswith:以xxx结束

    • __gt:大于指定值

      样例:

      1
      2
      Author.objects.filter(age__gt=50)
      #等同于select * from author where age > 50
    • __gte:大于等于指定值

    • __lt:小于

    • __lte:小于等于

    • __in:查找数据是否在指定范围内

      1
      Author.objects.filter(countyr__in=['中国','日本','韩国']) #等同于select * from author where country in ('中国','日本','韩国')
    • __range:查找数据是否在指定的区间范围内

      1
      2
      3
      #查找年龄在某一区间内的所有作者
      Author.objects.filter(age__range=(35,50))
      #等同于 select ... where Author between 35 and 50;
更新操作

1)更新单个数据

  1. ​ -通过get()得到要修改的实体对象

  2. ​ -通过对象.属性的方式修改数据

  3. 保存

    ​ -通过对象.save()保存数据

2)批量更新数据

直接调用QuerySet的update(属性=值)实现批量修改

示例:

1
2
3
4
5
6
#将id大于3的所有图书价格定为0元
books=Book.objects.filter(id__gt=3)
books.update(price=0)
#将所有书的零售价定为100元
books=Book.objects.all()
books.update(market_price=100)
删除操作

1)单个数据删除

  1. 查找查询结果对应的一个数据对象

  2. 调用这个数据对象的delete()方法实现删除

    示例:

    1
    2
    3
    4
    5
    try:
    auth=Author.objects.get(id=1)
    auth.delete()
    except:
    print(删除失败)

2)批量删除

  1. 查找查询结果集中满足条件的全部QuerySet查询集合对象

  2. 调用查询集合对象的delete()方法实现删除

    示例:

    1
    2
    3
    #删除全部作者中,年龄大于65岁的全部信息
    auths=Author.objects.filter(age__gt=65)
    auths.delete()

3)伪删除

  • 通常在业务中不会轻易地把数据真正删除,取而代之的是做伪删除,即在表中添加一个布尔类型字段(is_active),默认为True;执行删除时,将欲删除数据的is_active字段置为False
  • 注意:用伪删除时,确保显示数据的地方,均加了is_active=True的过滤查询

F对象和Q对象

F对象

一个F对象代表数据库中某条记录的字段的信息

作用:

​ -通常是对数据库中的字段值在不获取的情况下进行操作(也可以为标记),在业务数据更新时,为防止资源竞争和并发事件,用F对象就等于加了把锁,可以有条不紊地进行更新。

​ -用于类属性(字段)之间的比较

语法:

1
2
from django.db.models import F
F('列名')

示例代码: 更新book实例中所有的零售价涨10块

1
2
3
4
5
6
7
#用F对象的操作
Book.objects.all().update(market_price=F('market_price')+10)
#没有F对象的原始做法
books=Book.objects.all()
for book in books:
book.market_price=book.market_price+10
book.save()

实例2:对数据库中两个字段的值进行比较,列出哪儿些书的零售价高于定价

1
2
3
4
5
from django.db.models import F
from bookstore.models import Book
books=Book.objects.filter(market_price__gt=F('price'))
for book in books:
print(book.title,'定价:',book.price,'现价:',book.market_price)
Q对象

当在获取查询结果集使用复杂的逻辑或 |、逻辑非~等操作时可以借助于Q对象进行操作

如:想找出定价低于20元或清华大学出版社的全部书,可以写成

1
2
from django.db.models import Q
Book.objects.filter(Q(price__lt=20)|Q(pub="清华大学出版社"))

Q对象在数据包django.db.models中,需要先导入再使用

作用:在条件中用来实现除and(&)以外的or(|)或not(~)操作

语法:

1
2
3
4
from django.db.models import Q
Q(条件1)|Q(条件2) #条件1成立或条件2成立
Q(条件1)&Q(条件2) #条件1和条件2同时成立
Q(条件1)&~Q(条件2) #条件1成立且条件2不成立

聚合查询和原生数据库操作

聚合查询

聚合查询是指对一个数据表中的一个字段的数据进行部分或全部进行统计查询,查bookstore_book数据表中的全部书的平均价格,查询所有书的总个数等,都要使用聚合查询

聚合查询分为:整表查询和分组查询

1)整表查询

不带分组的聚合查询是指导将全部数据进行集中统计查询

聚合函数[需要导入]

​ **-导入方法: from django.db.models import ***

-聚合函数: Sum,Avg,Count,Max,Min

语法:MyModel.objects.aggregate(结果变量名=聚合函数(‘列’))

返回结果:结果变量名和值组成的字典

格式为:{“结果变量名”:值}

2)分组聚合

分组聚合是指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。

语法:
- QuerySet.annotate(结果变量名=聚合函数(‘列’)

返回值:

​ - QuerySet

实例:

1
pub_count_set=pub_set.annotate(myCount=Count('pub'))
原生数据库操作

django也可以支持直接用sql语句的方式通信数据库

查询:使用MyModel.objects.raw()进行数据库查询操作查询

语法:MyModel.objects.raw(sql语句,拼接参数)

返回值:RawQuerySet集合对象【只支持基础操作,比如循环】

实例:

1
2
3
books=models.Book.objecst.raw('select * from bookstore_book')
for book in books:
print(book)

SQL注入

使用原生语句时小心SQL注入

定义:用户通过数据上传,将恶意的sql语句提交给服务器,从而达到攻击效果

案例1:用户在搜索好友的表单框里输入’1 or 1=1’

1
s1=Book.objects.raw('select * from bookstore_book where id=%s'%('1 or 1=1'))

攻击结果:可查询出所有用户数据,’1=1’是恒等式,会会得出全表数据

sql注入防范

把刚才的sql拼接语句改成

1
s2=Book.objects.raw('select * from bookstore_book where id=%s',['1 or 1=1']

原生数据库操作-cursor

完全跨过模型类操作数据库-查询/更新/删除

  1. 导入cursor所在的包

    from django.db import connection

  2. 用创建cursor类的构造函数创建cursor对象,再使用cursor对象,为保证在出现异常时能释放cursor资源,通常使用with语句进行创建操作

    1
    2
    3
    from django.db import connection
    with connection.cursor() as cur:
    cur.execute('执行SQL语句','拼接参数')

关系映射

在关系型数据库中,通产不会把所有数据都放在同一张表中,不易于扩展,常见关系映射有:

  1. 一对一映射
  2. 一对多映射
  3. 多对多映射

一对一映射

一对一是表示现实事物间存在的一对一的对应关系

如:一个家庭只有一个户主,一个男人有一个妻子,一个人有一个唯一的指纹信息等

语法:OneToOneField(类名,on_delete=xxx)

1
2
3
4
5
class A(model.Model):
...
pass
class B(model.Model):
属性=models.OneToOneField(A,on_delete=xxx)

on_delete - 级联删除

  1. models.CASCADE 级联删除。Django模拟SQL语句约束ON_DELETE CASCADE的行为,并删除包含ForeignKey的对象
  2. models.PROTECT 抛出ProtectedError 以阻止被引用对象的删除;[等同于mysql默认的RESTRICT]
  3. SET_NULL 设置ForeignKey null; 需要null=True
  4. SET_DEFAULT 将ForeignKey设置为其默认值;必须设置ForeignKey的默认值

创建模型类

实例:

1
2
3
4
5
6
7
8
9
10
from django.db import models

class Author(mdoels.Model):
'''作家模型类'''
name=models.CharField('作家',max_length=50)

class Wife(models.Model):
'''作家妻子模型类'''
name=models.CharField("妻子",max_length=50)
author=models.OneToOneField(Author,on_delete=models.CASCADE) #增加一对一属性

创建数据

  • 无外键的模型类[Author]:

    1
    author1=Author.objects.create(name='王老师')
  • 有外键的模型类[Wife]:

    1
    2
    wife1=Wife.objects.create(name='王夫人',author=author1)  #关联王老师对象
    wife1=Wife.objects.create(name='王夫人',author_id=1) #关联王老师对应主键值

查询数据

  1. 正向查询:直接通过外键属性查询,则称为正向查询

    1
    2
    3
    4
    #通过wife找author
    from .models import Wife
    wife=Wife.objects.get(name='王夫人')
    print(wife.name,'的老公是',wife.author.name)
  2. 反向查询:没有外键属性的一方,可以调用反向属性查询到关联的另一方

    反向关联属性为’实例对象.引用类名(小写)’,如作家的反向引用为’作家对象.wife’

    -当反向引用不存在时,则会触发异常

    author1=Author.objects.get(name=’王老师’)

    author1.wife.name

一对多映射

一对多是表示现实事物间存在的一对多的对应关系

如:一个学校有多个班级,一个班级有多个学生,一本图书只能属于一个出版社,一个出版社允许出版多本图书

一对多需要明确出具体角色,在多表上设置外键

语法:当一个A类对象可以关联多个B类对象时

1
2
3
4
5
class A(models.Model): #一
...
pass
class B(models.Model): #多
属性=models.ForeignKey("一"的模型类,on_delete=xx)

ForeignKey必须指定on_delete模式

创建模型类

实例:

1
2
3
4
5
6
7
8
9
10
from django.db import models

class Publisher(models.Model):
'''出版社【一】'''
name=models.CharField('名称',max_length=50,unique=True)

class Book(models.Model):
'''书【多】'''
title=models.CharField('书名',max_length=50)
publisher=ForeignKey(Publisher,on_delete=models.CASCADE)

创建数据

先创建’一’再创建’多’

1
2
3
4
from .models import *
pub1=Publisher.objects.create(name='清华大学出版社')
Book.objects.create(title='C++',publisher=pub1)
Book.objects.create(title='Java',publisher_id=1)

查询数据

  1. 正向查询[通过Book查询 Publisher]

    1
    2
    3
    4
    5
    #通过publisher属性查询即可
    #book.publisher

    abook=Book.objects.get(id=1)
    print(abook.title,'的出版社是:',abook.publisher.name)
  2. 反向查询[通过Publisher查询对应的所有的Book]

    需要用到反向属性(下面例子中的book_set),反向属性的命名规则为关联的多的类名小写+_set

    django 默认每个主表的对象都有一个是外键的属性,可以通过它来查询到所有属于主表的子表的信息。这个属性的名称默认是以子表的名称小写加上_set()来表示(上面默认以b_set访问),默认返回的是一个querydict对象。
    related_name 可以给这个外键定义好一个别的名称

    1
    2
    3
    4
    5
    6
    pub1=Publisher.objects.get(name='清华大学出版社')
    books=pub1.book_set.all() #通过book_set获取pub1对应的多个Book数据对象
    #books=Book.objects.filter(publisher=pub1) #也可以采用此方式获取
    print("清华大学出版社的书有:")
    for book in books:
    print(book.title)

多对多映射

多对多表达对象之间多得多复杂关系,如:每一人都有不同的学校(小学,初中,高中…),每个学校都有不同的学生…

mysql中创建多对多需要依赖第三张表来实现

Django中无需手动创建第三张表,Django自动完成

语法:在关联的两个类中的任意一个类中,增加:属性=models.ManyToManyField(MyModel)

创建模型类

实例:一个作者可以出版多本图书,一本图书可以被多名作者同时编写

1
2
3
4
5
6
class Author(models.Model):
...
pass
class Book(models.Model):
...
authors=models.ManyToManyField(Author)

创建数据

方案1:先创建author再关联book

1
2
3
4
5
author1=Author.objects.create(name='吕老师')
author2=Author.objects.create(name='王老师')
#吕老师和王老师同时写一本python
book11=author1.book_set.create(title="python")
author2.book_set.add(book11)

方案2:先创建book再关联author

1
2
3
4
book=Book.objects.create(title='python1')
#郭小闹和吕老师都参与了pyhton1的创建
author3=book.authors.create(name='guoxiaonao')
book.authors.add(author1)

查询数据

  1. 正向查询有多对多属性的对象查另一方

    通过Book查询对应的所有的Author

    此时多对多属性等价于objects

    1
    2
    book.authors.all()  ->获取book对应的所有的author的信息
    book.authors.filter(age__gt=80) ->获取book对应的作者中年龄大于80岁的作者的信息
  2. 反向查询

    通过Author查询对应的所有的Book

    利用反向属性book_set

    1
    2
    author.book_set.all()
    author.book_set.filter()

admin后台管理

django提供了比较完善的后台管理数据库的接口,可供开发过程中调用和测试使用

django会搜集所有已注册的模型类,为这些模型类提供数据管理界面,供开发者使用

注册自定义模型类

若要自己定义的模型类也能在/admin后台管理界中显示和管理,需要将自己的类注册到后台管理界面

注册步骤:

  1. 在应用app中的admin.py中导入注册要管理的模型models类,如:from .models import Book
  2. 调用admin.site.register 方法进行注册,如:admin.site.register(自定义模型类)

模型管理类

作用:为后台管理界面添加便于操作的新功能

说明:后台管理器类须继承自django.contrib.admin里的ModelAdmin类

使用方法:

  1. 在应用app的admin.py里定义模型管理器类

    1
    2
    class xxxxManager(admin.ModelAdmin):
    ......
  2. 绑定注册模型管理器和模型类

    1
    2
    3
    from django.contrib import admin
    from .models import *
    admin.site.register(YYYY,xxxxManager) #绑定YYYY模型类与管理器类 xxxxManager

模型管理器类的属性以及作用

  1. list_display 去控制哪些字段会显示在Admin的修改列表页面中
  2. list_display_links 可以控制list_display中的字段是否应该链接到对象的”更改”页面
  3. list_filter 设置激活Admin修改列表页面右侧栏中的过滤器
  4. search_fields 设置启用Admin更改列表页面上的搜索框
  5. list_editable 设置为模型上的字段名称列表,这将允许更改页面上进行编辑
1
2
3
4
5
6
7
8
9
10
11
class UserManager(admin.ModelAdmin):
#列表页显示哪些字段的列
list_display = ['id','reg_username','reg_password']
#控制list_display中的字段,哪些可以链接到修改页
list_display_links =['reg_username']
#添加过滤器
list_filter =['id']
#添加搜索框[模糊查询]
search_fields = ['reg_username']
#添加可在列表页编辑的字段
list_editable = ['reg_password']

image

内建用户系统

django带有一个用户认证系统。它处理用户账户、组、权限以及基于cookie的用户会话。

用户可以直接使用django自带的用户表

基本字段

模型类位置:

1
from django.contrib.auth.models import User
模型字段 用处
username 用户名
password 密码
email 邮箱
first_name
last_name
is_superuser 是否是管理员账号(/admin)
is_staff 是否可以访问admin管理界面
is_active 是否是活跃用户,默认为True。一般不删除用户,而是将用户的is_avtive设为False
last_login 上一次的登录时间
date_joined 用户创建的时间

基本操作

创建用户
  1. 创建普通用户create_user

    1
    2
    from django.contrib.auth.models import User
    user=User.objects.create_user(username='用户名',password='密码',email='邮箱',....)
  2. 创建超级用户create_superuser

    1
    2
    from django.contrib.auth.models import User
    user=User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)
删除用户

django建议我们还是用伪删除

示例:

1
2
3
4
5
6
7
8
from django.contrib.auth.models import User
try:
user=User.objects.get(username='用户名')
user.is_active=False #记当前用户无效
user.save()
print("删除普通用户成功!")
except:
print("删除普通用户失败")
校验密码
1
2
from django.contrib.auth import authenticate
user=authenticate(username=username,password=password)

说明:

如果用户名密码校验成功则返回对应的user对象,否则返回None

修改密码

示例:

1
2
3
4
5
6
7
8
from django.contrib.auth.models import User
try:
user=User.objects.get(username='xiaonao')
user.set_password('654321')
user.save()
return HttpResponse("修改密码成功!")
except:
return HttpResponse("修改密码失败!")

自带登录系统功能

登录状态保持

利用了session,但session保存时间不可控

1
2
3
4
from django.contrib.auth import login
def login_view(request):
user=authenticate(username=username,password=password)
login(request,user)
登录状态校验

示例:

1
2
3
4
5
6
7
from django.contrib.auth.decorators import login_required
@login_required
def index_view(request):
#该视图必须为用户登录状态下才可以访问
#当前登录用户可通过request.user获取
login_user=request.user
....
登录状态取消
1
2
3
from django.contrib.auth import logout
def logout_view(request):
logout(request)

内建用户表扩展字段

两种方案:

  • 通过建立新表,跟内建表做1对1
  • 继承内建的抽象user模型类

方案二:

步骤:

  1. 添加新的应用
  2. 定义模型类继承AbstractUser
  3. settings.py中知指明AUTH_USER_MODEL=’应用名.类名’

注意:此操作要在第一次Migrate之前进行

示例:- user/models.py -添加user应用

1
2
3
4
5
from django.db import models
from django.contrib.auth.models import AbstractUser

class UserInfo(AbstractUser):
phone=models.CharField(max_length=11,default='')

settings.py添加配置

1
AUTH_USER_MODEL='user.UserInfo'

添加用户

1
2
from user.models import UserInfo
UserInfo.objects.create_user(username='guoxiao',password='123456',phone='13488871101')

缓存

定义:缓存是一类可以更快的读取数据的介质统称,也指其它可以加快数据读取的存储方式。一般用来存储临时数据,常用介质的是读取速度很快的内存

意义:视图渲染有一定成本,数据库的频繁查询过高;所以对于低频变动的页面可以考虑使用缓存技术,减少实际渲染次数;用户拿到响应的时间成本会更低

缓存配置

  1. 设置数据库缓存

    将缓存的数据存储在您的数据库中

    说明:尽管存储介质没有更换,但是当把一次负责查询的结果直接存储在表里,比如多个条件的过滤查询结果,可避免重复进行复杂的查询,提升效率;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # #数据库缓存设置,需要手动执行创建表的命令(my_cache_table)
    CACHES={
    'default':{
    'BACKEND':'django.core.cache.backends.db.DatabaseCache',
    'LOCATION':'my_cache_table',
    'TIMEOUT':300, #缓存保存时间 单位秒,默认值为300
    'OPTIONS':{
    'MAX_ENTRIES':300, #缓存最大数据条数
    'CYLL_FREQUENCY':2, #缓存条数达到最大值时,删除1/x的缓存数据
    }
    }
    }

    创建数据库缓存表的操作:

    1
    python manage.py createcachetable
  2. 本地内存缓存:

    数据缓存到服务器内存中

    配置:

    1
    2
    3
    4
    5
    6
    CACHES={
    'default':{
    'BACKEND':'django.core.cache.backends.locmem.LocMemCache',
    'LOCATION':'unique-snowflake',
    }
    }
  3. 文件系统缓存

    将缓存的数据存储到本地文件中

    1
    2
    3
    4
    5
    6
    7
    CACHES={
    'default':{
    'BACKEND':'django.core.cache.backends.filebased.FileBasedCache',
    'LOCATION':'/var/tem/django_cache', #这个是文件夹的路径
    #'LOCATION':'c:\test\cache',#windows下示例
    }
    }

缓存使用

整体缓存

  1. 视图函数中:

    样例:

    1
    2
    3
    4
    5
    from django.views.decorators.cache import cache_page

    @cache_page(30) #当前缓存保存的时间 单位s
    def my_view(request):
    ....
  2. 在路由中:

    示例:

    1
    2
    3
    4
    5
    from django.views.decorators.cache import cache_page

    urlpatterns=[
    path('foo/',cache_page(60)(my_view))
    ]

局部缓存

缓存api的使用:

先引入cache对象:

  1. 使用caches[‘CACHE配置key’]导入具体对象

    1
    2
    3
    from django.core.cache import caches
    cache1=caches['myalias']
    cache2=caches['myalias_2']
  2. from django.core.cache import cache 相当于直接引入CACHES配置项的’default’项

cache.set(key,value,timeout) - 存储缓存

  • key:缓存的key,字符串类型
  • value:python对象
  • timeout:缓存存储时间(s),默认为CACHES中的TIMEOUT值
  • 返回值:None

cache.get(key) - 获取缓存

  • key:缓存的key
  • 返回值:为key的具体值,如果没有数据,则返回None

cache.add(key,value) - 存储缓存

  • 注意:只在key不存在时生效
  • 返回值:True[存储成功]或者False[存储失败]

cache.get_or_set(key,value,timeout) - 如果未获取到数据,则执行set操作

  • 返回值:value

cache.set_many(dict,timeout) - 批量存储缓存

  • dict:key和value的字典
  • timeout:存储时间(s)
  • 返回值:插入不成功的key的数组

cache.get_many(key_list) - 批量存储缓存数据

  • key_list:包含key的数组
  • 返回值:取到的key和value的字典

cache.delete(key) - 删除key的缓存数据

  • 返回值:None

cache.delete_many(key_list) - 批量删除

  • 返回值:None

浏览器缓存

强缓存

不会向服务器发送请求,直接从缓存中读取资源

  1. 响应头 - Expires
    • 定义:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点(绝对时间)
    • 样例:Expires:Thu,02 Apr 2030 05:14:08 GMT
  2. 响应头 - Cache-Control
    • 在HTTP/1.1中,Cache-Control主要用于控制网页缓存。比如当’Cache-Control:max-age=120’ 代表请求创建时间后的120秒,缓存失效(相对时间)
    • 说明:目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用Cache-Control

协商缓存

强缓存中的数据一旦过期。还需要跟服务器进行通信,从而获取最新数据;如果强缓存的数据是一些静态文件,大图片等;

考虑到大图片这类比较费带宽且不易变化的数据,强缓存时间到期后,浏览器会去跟服务器协商,当前缓存是否可用,如果可用,服务器不必返回数据,浏览器继续使用原来缓存的数据,如果文件不可用,则返回最新数据

1.Last-Modified响应头和 If-Modified-Since请求头

  • Last-Modified为文件的最近修改时间,浏览器第一次请求静态文件时,服务器如果返回Last-Modified响应头,则代表该资源为需要协商的缓存
  • 当缓存到期后,浏览器将获取到的Last-Modified值做为请求头If-Modified-Since的值,与服务器发请求协商,服务端返回304响应码[响应体为空],代表缓存继续使用,200响应码代表缓存不可用[响应体为最新资源]

ETag响应头和If-None-Match响应头

  • ETag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,ETag就会重新生成
  • 缓存到期后,浏览器将Etag响应头的值作为If-None-Match请求头的值,给服务器发请求协商;服务器接到请求头后,比对文件标识,不一致则认为资源不可用,返回200响应码[响应体为最新资源];可用则返回304响应码

Last-Modified和ETag的相对区别

  • 精度不一样 - Etag准确率更高
  • 性能上 - Last-Modified高,Etag需要计算文件标识
  • 优先级 - Etag高;两个头同时出现,浏览器优先取Etag

分页

定义:分页是指在web页面有大量数据需要显示,为了阅读方便在每个页面中只显示部分数据

优点:

  • 方便阅读
  • 减少数据提取量,减轻服务器压力

- Django提供了Paginator类可以方便的实现分页功能

- Paginator类位于’django.core.paginator’模块中

Paginator对象

负责分页数据整体的管理

对象的构造方法:

1
paginator=Paginator(object_list,per_page)
  1. 参数;

    • object_list需要分页数据的对象列表
    • per_page每页数据个数
  2. 返回值:

    Paginator对象

  3. 属性:

    • count:需要分页数据的对象总数
    • num_pages:分页后的页面总数
    • page_range:从1开始的range对象,用于记录当前面码数
    • per_page:每页数据的个数

Paginator方法

paginator对象.page(number)

  • 参数number为页码信息(从1开始)
  • 返回当前number页对应的页信息
  • 如果提供的页码不存在,抛出InvalidPage异常

Paginator异常exception

InvalidPage:总的异常基类,包含以下两个异常子类

  • PageNotAnlnteger:当向page()传入一个不是整数的值时抛出
  • EmptyPage:当向page()提供一个有效值,但是那个页面上没有任何对象时抛出

Page对象

负责具体某一页的数据的管理

创建对象

  • Paginator对象的page()方法返回Page对象
  • page=paginator.page(页码)

Page对象属性

  • object_list:当前页上所有数据对象的列表
  • number:当前页的序号,从1开始
  • paginator:当前page对象相关的Paginator对象

Page对象方法

  • has_next():如果有下一页返回True
  • has_previous():如果有上一页返回True
  • has_other_page():如果有上一页或下一页返回True
  • next_page_number():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
  • previous_page_number():返回上一页的页码,如果上一页不存在,抛出InvaildPage异常

实例:

视图层:

1
2
3
4
5
6
7
8
9
10
11
12
#分页
def text_page(request):
#页码通过url进行传递的(两种方式)
#/text_page/4
#/text_page?page=1 查询字符串
page_num=request.GET.get('page',1)
all_data=['a','b','c','d','e']
#初始化Paginator对象
paginator=Paginator(all_data,2)
#初始化 具体的页码的Page对象
c_page=paginator.page(int(page_num))
return render(request,'text_page.html',locals())

前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页</title>
</head>
<body>
{% for p in c_page %}
<p>
{{ p }}
</p>
{% endfor %}

{% if c_page.has_previous %}
<a href="/tem/text_page?page={{ c_page.previous_page_number }}">上一页</a>
{% else %}
上一页
{% endif %}

{% for p_num in paginator.page_range %}
{% if p_num == c_page.number %}
{{ p_num }}
{% else %}
<a href="/tem/text_page?page={{ p_num }}">{{ p_num }}</a>
{% endif %}
{% endfor %}


{% if c_page.has_next %}
<a href="/tem/text_page?page={{ c_page.next_page_number }}">下一页</a>
{% else %}
下一页
{% endif %}

</body>
</html>

csv文件

定义:逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)

说明:可被常见制表工具,如excel等直接进行读取

python中生成csv文件

python提供了内建库 - csv;可直接通过该库操作csv文件

案例:

1
2
3
4
import csv
with open('eggs.csv','w',newline='')as csvfile:
writer=csv.writer(csvfile)
writer.writerow(['a','b','c'])

csv文件下载

在网站中,实现下载csv,注意如下:

  • 响应Content-Type类型需修改为text/csv。这告诉浏览器该文档是CSV文件,而不是HTML文件
  • 响应会获得一个额外的Content-Disposition标头,其中包含CSV文件的名称。它将被浏览器用于开启”另存为…”对话框

案例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import csv
from django.http import HttpResposne
from .models import Book

def make_csv_view(request):
response=HttpResponse(content_type='text/csv')
response['Content-Disposition']='attachment;filename="mybook.csv"'
all_book=Book.objects.all()
writer=csv.writer(response)
writer.writerow(['id','title'])
for b in all_book:
writer.writerow([b.id,b.title])
return response

文件上传

定义:用户可以通过浏览器将图片等文件传至网站

场景:

  • 用户上传头像
  • 上传流程性的文档[pdf,txt等]

上传规范 - 前端[html]

文件上传必须为POST提交方式

表单<form>中文件上传时必须有带有enctype=”multipart/form-data”时才会包含文件内容数据。

表单中用<input type="file" name="xxx">标签上传文件

示例:

1
2
3
4
5
6
7
8
9
10
<form action="/tem/getfile/" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div id="login_box">
<h2>文件上传</h2>
<div class="input_box">
<input type="file" class="form-control-file" name ='hunter' id="exampleFormControlFile1">
</div>
<button type="submit" class="btn btn-primary mb-2" onclick="getInput()">上传</button><br>
</div>
</form>

上传规范 - 后端[django]

视图函数中,用request.FILES去文件框的内容

file=request.FILES[‘xxx’]

说明:

  1. FILES的key对应页面中file框的name值
  2. file绑定文件流对象
  3. file.name 文件名
  4. file.file 文件的字节流数据

配置文件的访问路径和存储路径

在setting.py中设置MEDIA相关配置;Django把用户上传的文件,统称为media资源

Django把用户上传的文件,统称为media资源

1
2
3
#file: settings.py
MEDIA_URL='/media/'
MEDIA_ROOT=os.path.join(BASE_DIR,'media')

MEDIA_URL和MEDIA_ROOT需要手动绑定

步骤:主路由中添加路由

1
2
3
from django.conf import settings
from django.conf.urls.static import static
urlpatterns+=static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)

说明:等价于做了MEDIA_URL开头的路由,Django接到该特征请求后去MEDIA_ROOT路径查找资源

文件写入

  1. 方案1:传统的open方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @csrf_exempt
    def upload_view(request):
    if request.method=='GET':
    return render(request,'text_upload.html')
    elif request.method=='POST':
    a_file=request.FILES['myfile']
    print("上传文件名是:",a_file.name)
    filename=os.path.join(settings.MEDIA_ROOT,a_file.name)
    with open(filename,'wb') as f:
    data=a_file.file.read()
    f.write(data)
    return HttpResponse("接收文件:"+a_file.name+"成功")
  2. 方案2:借助ORM

    得借助orm层的字段:FileField(upload=’子目录名’)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @csrf_exempt
    def upload_view_dj(request):
    if request.method=='GET':
    return render(request,'test_upload.html')
    elif request.method=='POST':
    title=request.POST['title']
    a_file=request.FILES['myfile']
    Content.objects.create(desc=titile,myfile=a_file)
    return HttpResponse('---upload is ok---')

    orm层示例:

    1
    2
    3
    4
    5
    from django.db import models

    class Content(models.Model):
    title=models.CharField('文章名字',max_length=11)
    picture=models.FileField(upload_to='picture')

发送邮件

业务场景:

  • 业务告警
  • 邮件验证
  • 密码找回

邮件相关的三个协议

  1. SMTP
    • SMTP的全称是”Simple Mail Transfer Protocol”,即简单邮件传输协议(25号端口)
    • 它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转
    • 属于”推送”协议
  2. IMAP
    • IMAP全称是Internet Mail Access Protocol,即交互式邮件访问协议,是一个应用层协议(端口是143)
    • 用来从本地邮件客户端(Outlook Express、Foxmail、Mozilla、Thunderbird等)访问远程服务器上的邮件
    • 属于”拉取”协议
  3. POP3
    • POP3事是Post Office Protocol 3 的简称,即邮局协议的第三个版本,是TCP/IP协议族中的一员(默认端口是110)
    • 本协议主要用于支持使用客户端远程管理在服务器上的电子邮件
    • 属于”拉取”协议

IMAP跟POP3的区别

两者均为”拉取”型协议,负责从邮件服务器中下载邮件

  • IMAP具备摘要浏览功能,可预览部分摘要,再下载整个邮件

  • IMAP为双向协议,客户端操作可反馈给服务器

  • POP3必须下载全部邮件,无摘要功能

  • POP3为单向协议,客户端操作无法同步服务器

邮件收发全过程

image

django就是充当邮件客户端的身份

Django发邮件

Django中配置邮件的功能,主要为SMTP协议,负责发邮件

原理
  • 给Django授权一个邮箱
  • Django用该邮箱给对应收件人发送邮件
  • django.core.mail封装了电子邮件的自动发送SMTP协议
授权步骤

以QQ邮箱为例:

  1. 申请QQ号

  2. 用QQ号登录QQ邮箱并修改设置

    • 用申请到QQ号和密码登录 到https://mail.qq.com
    • 修改’QQ邮箱->设置->账户->”POP3/IMAP….服务”
    • 拿到授权码
  3. Django配置

    1
    2
    3
    4
    5
    6
    EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend'
    EMAIL_HOST='smtp.qq.com' #腾讯QQ邮箱SMTP服务器地址
    EMAIL_PORT=25 #SMTP服务的端口号
    EMAIL_HOST_USER='xxxx@qq.com' #发送邮件的QQ邮箱
    EMAIL_HOST_PASSWORD='******' #在QQ邮箱->设置->账户->"POP3/IMAP...服务"里得到的在第三方登录QQ邮箱授权码
    EMAIL_USE_TLS=False #与SMTP服务器通信时,是否启动TLS链接(安全链接)默认False
函数调用
1
2
3
4
5
6
7
from django.core import mail
mail.send_mail(
subject, #题目
message, #消息内容
from_email, #发送者[当前配置邮箱]
recipient_list=['xxx@qq.com'], #接收者邮件列表
)

项目部署

项目部署是指在软件开发完毕后,将开发机器上运行的软件实际安装到服务器上进行长期运行

  1. 在安装机器上安装和配置同版本的环境[py,数据库等]

  2. django项目迁移

    虚拟机linux系统下做的迁移,示例:linux命令如下

    1
    2
    sudo scp /home/tarena/django/mysite1 #要复制的项目
    root@88.77.66.55:/home/root/xxx #要迁移到的外部目录,得输入用户名和
  3. 用uWSGI替代python manage.py runserver方法启动服务器

  4. 配置nginx反向代理服务器

  5. 用nginx配置静态文件路径,解决静态路径问题

uWSGI

uWSGI网关接口配置

WSGI(Web Server Gateway Interface)Web服务器网关接口,是Python应用程序或框架和Web服务器之间的一种接口,被广泛使用

使用python manage.py runserver通常只在开发和测试环境中使用

当开发结束后,完善的项目代码需要在一个高效稳定的环境运行,这时可以使用WSGI

image

image

uWSGI是WSGI的一种,它实现了http协议 WSGI协议以及uwsgi协议

uWSGI功能完善,支持协议众多,在python web圈热度极高

uWSGI主要以学习配置为主

配置uWSGI

添加配置文件 项目同名文件夹/uwsgi.ini,例如:mysite1/mysite1/uwsgi.ini

文件以[uwsgi]开头,有如下配置项:

  1. 套接字方式的 IP地址:端口号 [此模式需要有nginx]

    socket=127.0.0.1:8000

  2. Http通信方式的 IP地址:端口号

    http=127.0.0.1:8000

1
2
3
[uwsgi]
1.socket=127.0.0.1:8000
2.http=127.0.0.1:8000
  1. 项目当前工作目录

    示例:

    chdir=/home/tarena/…/my_project

  2. 项目中wsgi.py文件的目录,相对于当前工作目录

    示例:

    wsgi-file=my_project/wsgi.py

  3. 进程个数

    process=4

  4. 每个进程的线程个数

    threads=2

  5. 服务的pid记录文件

    pidfile=uwsgi.pid

  6. 服务的日志文件位置

    daemonize=uwsgi.log

  7. 开启主进程管理模式

    master=true

特殊说明

Django的settings.py需要做如下配置

  1. 修改settings.py将DEBUG=True改为DEBUG=False(正式上线时是要更改该配置,要不然用户会看到我们的报错信息)
  2. 修改settings.py将ALLOWED_HOSTS=[]改为ALLOWED_HOSTS=[‘网站域名’]或者[‘服务监听的ip地址’]
uWSGI的运行管理
  1. 启动uWSGI

    cd到uWSGI配置文件所在目录下

    uwsgi –ini uwsgi.ini

    **ps aux|grep ‘uwsgi’**检测是否启动uwsgi是否启动和停止

  2. 停止uWSGI

    cd到uWSGI配置文件所在目录

    uwsgi –stop uwsgi.pid

  3. 重启uWSGI

    uwsgi –reload uwsgi.pid

uWSGI的运行说明

  1. 无论是启动后还是关闭,都需要执行ps aux|grep ‘uwsgi’确认是否符合预期
  2. 启动成功后,进程在后台执行,所有日志均输出在配置文件所在目录的uwsgi.log钟
  3. Django中代码有任何修改,需要重新启动uwsgi

uWSGI测试

在浏览器端输入http://127.0.0.1:8000/url进行测试

注意: 此时端口8000被uWSGI进程监听,并非runserver

如果当前有与其返回,则uWSGI启动成功

uWSGI常见问题汇总
  1. 启动失败:端口被占用

    原因:有其他进程占用uWSGI启动的端口;

    解决方案:可执行 sudo lsof -i:端口号 查询出具体进程;杀掉进程后,重新启动uWSGI即可

  2. 停止失败:stop无法关闭uWSGI

    原因:重复启动uWSGI,导致pid文件中的进程号失准

    解决方案:ps出uWSGI进程,手动kill掉

nginx

  • Nginx是轻量级的高性能Web服务器,提供了诸如HTTP代理和反向代理、负载均衡等一系列重要特性
  • C语言编写
  • nginx作用:
    - 负载均衡,多台服务器轮流处理请求
    - 反向代理
  • 原理:客户端请求nginx,再由nginx将请求转发uWSGI运行的django

反向代理:就是nginx可以接收来自五湖四海的网民的请求,并将这些请求分发给多台django机器中

image*

负载均衡:均衡的把请求分发给django机器

image

nginx的运行
  1. 启动nginx

    cd 到/usr/local/nginx/sbin目录中

    输入命令**./nginx**来启动nginx

  2. 停止nginx

    cd 到/usr/local/nginx/sbin目录中

    输入命令./nginx -s stop或者./nginx -s quit

    .nginx -s stop 是快速停止,可能会导致数据没有完全保存

    .nginx -s quit 是有序停止

  3. 重启nginx

    命令: ./nginx -s reopen 或者 ./nginx -s reload

  4. 查询nginx进程

    用于检测nginx是否启动成功

    命令: ps -ef | grep nginx

  5. 命令大全

    killall nginx #杀死所有nginx进程

    nginx -t #检测配置文件是否有语法错误,然后退出

    nginx -v #显示版本信息并退出

    nginx -V #显示版本和配置选项信息,然后退出

    nginx -t #检测配置文件是否有语法错误,然后退出,因为有些文件权限没给,所以前面加个sudo

    nginx -T #检测配置文件是否有语法错误,转储并退出

    nginx -q #在检测配置文件期间屏蔽非错误信息

    nginx -?,-h #打开帮助信息

    nginx -p prefix #设置前缀路径(默认是:/usr/share/nginx/)

    nginx -c filename #设置配置文件(默认是:/etc/nginx/nginx.conf)

    nginx -g directives #设置配置文件外的全局指令

nginx和uwsgi排错

常见问题排查

排查问题宗旨 是 看日志!看日志! 看日志!! 看日志!!!

  1. nginx日志位置:

​ 异常信息存放地:/usr/local/nginx/logs/error.log

​ 正常访问信息存放地:/usr/local/nginx/logs/access.log

  1. uwsgi日志位置:

    ​ 项目同名目录下,uwsgi.log

  2. 访问127.0.0.1:80 地址,502响应

    ​ 502响应代表nginx反向代理配置成功,但是对应的uwsgi未启动

  3. 访问127.0.0.1:80/url 404响应

    - 路由的确不在django配置中(黄色页面报错)
    - nginx配置错误,未禁止掉 try_files
    

nginx静态文件配置问题

静态文件配置步骤

  1. 创建新路径-主要存放Django所有静态文件 如:/home/dreamcreator/djdemo_static/

  2. 在Django settings.py中添加新配置

    1
    2
    STATIC_ROOT = '/home/dreamcreator/djdemo_static/static'
    #注意此配置路径为 存放所有正式环境中需要的静态文件
  3. 进入项目,执行python3 manage.py collectstatic 执行该命令后,Django将项目中所有静态文件复制到STATIC_ROOT中,包括Django内建的静态文件

Nginx配置中添加新配置

1
2
3
4
5
6
7
8
server{
....
location /static{
#root 第一步创建文件夹得绝对路径
root /home/dreamcreator/djdemo_static;
}
....
}

404/500页面

定义和配置

在模板文件夹内添加404.html 模板,当视图触发Http404异常时将会被显示

404.html仅在发布版中(即settings.py中得DEBUG=False时)才起作用,当响应处理函数触发Http404异常时就会跳转到404界面

邮箱告警

定义和配置

当正式服务器上代码运行有报错时,可将错误追溯信息发至指定的邮箱

settings.py中 - 在基础邮箱授权后 添加如下配置:

1
2
3
4
5
6
7
8
#关闭调试模式
DEBUG = False

#错误报告接收方
ADMINS = [('guoxiaonao','xxxx@example.com'),('wanglaoshi','xxxx@example.com')]

#发送错误报告方,默认为 root@localhost账户,多数邮件服务器会拒绝
SERVER_EMAIL = 'email配置中的邮箱'

过滤敏感信息

报错邮件中会显示一些错误的追踪,这些错误追踪会出现如password等敏感信息,Django已经将配置文件中的敏感信息过滤修改为多个星号,但是用户自定义的视图函数需要手动过滤敏感信息

可过滤如下信息

  • 局部变量

    1
    2
    3
    4
    5
    6
    7
    8
    from django.views.decorators.debug import sensitive_variables

    @sensitive_variables('user','pw','cc')
    def process_info(user):
    pw=user.pass_word
    cc=user.credit_card_number
    name=user.name
    ...

    说明:

    1. 若报错邮件中牵扯到user,pw,cc等局部变量的值,则会将其替换成****,而name变量还显示其真实值
    2. 多个装饰器时,需要将其放在最顶部
    3. 若不传参数,则过滤所以局部变量的值
  • POST提交数据

    1
    2
    3
    4
    5
    6
    7
    from django.views.decorators.debug import sensitive_post_parameters

    @sensitive_post_parameters('password','username')
    def index(requet):
    s=request.POST['username']+request.POST['abcd']
    #'abcd'并不存在,此时引发error
    #POST中username及password的值会被替换成 ******

Celery框架

概念:Celery是一个简单、灵活且可靠的,处理大量信息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调度。celery是一个基于Python开发的模块,可以帮助我们对任务进行分发和处理

组成:

  1. 消息中间件(message broker)
  2. 任务执行单元(worker)
  3. 任务执行结果存储(task result store)

消息中间件

​ Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ,Redis等等

任务执行单元

​ Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中

任务结果存储

​ Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP,redis等

另外,Celery还支持不同的并发和序列化手段

  • 并发:Prefork,Eventlet,gevent,threads/single threaded
  • 序列化:pickle,json,yaml,msgpack,zilb,bzip2 compression,Cryptographic message signing等等

image

redis

redis服务端启动

1
2
cd /usr/local/redis/bin
./redis-server redis.conf

redis服务端关闭

1
2
cd /usr/local/redis/bin
./redis-server stop

redis服务端重启

1
2
cd /usr/local/redis/bin
./redis-server restart

redis客户端启动

如果把redis-cli文件复制到了/usr/local/bin/目录下的话

1
2
#任意一个目录下
redis-cli

如果没有

1
2
cd /usr/local/redis/bin
./redis-cli

redis客户端关闭

  1. Ctrl+c
  2. 客户端内输入 quit
  3. 客户端内输入 exit

redis内部库的切换

  1. 进入redis客户端
  2. 输入命令 select 你要切换库的序号
  3. 输入 keys * 可以看到该库中所有的键

django部署Celery

Celery的环境部署

  1. 在django项目外创建一个给celery的单独目录
  2. 手动创建config.py文件用于存储celery配置,再建一个main.py文件用于跟django连接的主配置
  3. 在celery单独创建要使用task的目录然后在该目录中创建tasks.py文件用于写任务

image


Django框架学习与开发
https://moonfordream.github.io/posts/Django框架学习与开发/
作者
Moon
发布于
2024年5月19日
许可协议