Django 简介

Django发布于2005年,是一个由 Python 编写的一个开放源代码的 Web 应用框架(源代码是开源的,遵守BSD版权)。使用 Django,只要很少的代码,Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的 Web 服务Django本身基于 MVC 模型,即 Model(模型)+ View(视图)+ Controller(控制器)设计模式,也有很多人把它称MVT(MTV)模式。MVC 模式使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。

MTV 模型

Django 的 MTV 模式本质上和 MVC 是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django 的 MTV 分别是指:

  • M 表示模型(Model):编写程序应有的功能,负责业务对象与数据库的映射(ORM)。
  • T 表示模板 (Template):负责如何把页面(html)展示给用户。
  • V 表示视图(View):负责业务逻辑,并在适当时候调用 Model和 Template。

除了以上三层之外,还需要一个 URL 分发器,它的作用是将一个个 URL 的页面请求分发给不同的 View 处理,View 再调用相应的 Model 和 Template,MTV 的响应模式

用户操作流程图:

解析:

用户通过浏览器向我们的服务器发起一个请求(request),这个请求会去访问视图函数:

  • 绿色路线:如果不涉及到数据调用,那么这个时候视图函数直接返回一个模板也就是一个网页给用户。
  • 红色路线:如果涉及到数据调用,那么视图函数调用模型,模型去数据库查找数据,然后逐级返回。视图函数把返回的数据填充到模板中空格中,最后返回网页给用户。

Django版本

django各个版本对python的要求:

Django version Python versions
1.11 2.7, 3.4, 3.5, 3.6
2.0 3.4, 3.5, 3.6, 3.7
2.1, 2.2 3.5, 3.6, 3.7

Django的安装

环境要求

  • 操作系统: Windows
  • python版本:>=3.5
  • Django版本:2.2

pip命令安装

⾸先使用virtualenvwrapper-win建⽴⼀个虚拟开发环境,然后使用pip安装

1
2
3
mkvirtualenv django-env   # 新建虚拟环境
workon django-env # 进入虚拟环境
pip install django==2.2 -i http://mirrors.aliyun.com/pypi/simple/ #安装指定版本的django

安装完毕后,测试⼀下是否安装成功

1
2
3
4
#在虚拟开发环境中
>>>python #开启python
>>>import django
>>>django.get_version()

⼿动安装

到django官⽹上下载:https://www.djangoproject.com/download/2.2.9/tarball/安装包,然后解压,到解压⽬录下,打开虚拟开发环境,执行以下命令:

1
python -m pip install .

Django 创建第一个项目

测试版本说明:

  • Python 3.8.4
  • Django 2.2

Django 管理工具

安装 Django 之后,您现在应该已经有了可用的管理工具 django-admin.py,Windows 如果没有配置环境变量可以用 django-admin。我们可以使用 django-admin.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
C:\Users\L15096000421>django-admin
Type 'django-admin help <subcommand>' for help on a specific subcommand.
Available subcommands:
[django]
check
compilemessages
createcachetable
dbshell
diffsettings
dumpdata
flush
inspectdb
loaddata
makemessages
makemigrations
migrate
runserver
sendtestemail
shell
showmigrations
sqlflush
sqlmigrate
sqlsequencereset
squashmigrations
startapp
startproject
test
testserver

创建项目

使用 django-admin.py 来创建 HelloWorld 项目。先切换到指定⽬录,开启虚拟环境,然后用以下指令创建⼀个项⽬

1
django-admin startproject HelloWorld

创建完成后我们可以查看下项目的目录结构:

1
2
3
4
5
6
7
8
9
$ cd HelloWorld/
$ tree
.
|-- HelloWorld
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| `-- wsgi.py
`-- manage.py

目录说明:

  • HelloWorld: 项目的容器。
  • manage.py: 一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互。
  • HelloWorld/init.py: 一个空文件,告诉 Python 该目录是一个 Python 包。
  • HelloWorld/settings.py: 该 Django 项目的设置/配置。
  • HelloWorld/urls.py: 该 Django 项目的 URL 声明; 一份由 Django 驱动的网站”目录”。
  • HelloWorld/wsgi.py: 一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目。

接下来我们进入 HelloWorld 目录输入以下命令,启动服务器:

1
python manage.py runserver 0.0.0.0:8000

0.0.0.0 让其它电脑可连接到开发服务器,8000 为端口号。如果不说明,那么端口号默认为 8000。

在浏览器输入你服务器的 ip(这里我们输入本机 IP 地址: 127.0.0.1:8000) 及端口号,如果正常启动,输出结果如下:

如果要让远程客户端连接需要修改配置⽂件,其中0.0.0.0:9000是可选的,0.0.0.0说明任何ip都可以访问。

1
2
#修改setting.py中的这⼀行
ALLOWED_HOSTS = ['*']

如果需要让页面显示中文,需要在setting.py中修改国际化的配置

1
2
3
4
5
# 语言 国际化
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
# 不使用世界时间,改为False,数据库储存的时间和当地时间一致
USE_TZ = False

建立应用

⼀个django项⽬中可以包含多个应用,和flask只是叫法上不同,在flask中说法为可以在一个app上建议多个模块

可以使用以下命令建⽴应用:

1
2
#命令: python manager.py startapp 应用名称
实例: python manager.py startapp app

创建应用后,项⽬的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
── App
│ ├── admin.py 站点管理
│ ├── apps.py 应用程序⾃身信息
│ ├── __init__.py
│ ├── migrations 数据迁移
│ │ └── __init__.py
│ ├── models.py 模型
│ ├── tests.py
│ └── views.py 视图响应函数
├── db.sqlite3 sqlite数据库
├── hellodjango 项⽬
│ ├── doc.py
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ ├── settings.cpython-36.pyc
│ │ ├── urls.cpython-36.pyc
│ │ └── wsgi.cpython-36.pyc
│ ├── settings.py 系统配置
│ ├── urls.py 路由映射表
│ └── wsgi.py wsgi协议
└── manage.py 项⽬管理命令

修改项⽬的配置⽂件setting.py:

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'App', # 安装自己的应用
]

简单视图函数

在django中view代表视图函数,接收用户的请求,进行处理然后返回html源代码给客户端。在框架中,我们可以在应用(app)的views.py中写⾃⼰的视图函数, 进行相应的处理

视图函数只是⼀个普通的python函数,它的第⼀个参数必须是⼀个请求(request)对象,这是框架传入的,我们可以通过请求对象获取提交参数等内容。它的返回值必须是⼀个web响应对象,可以文本、html源代码、重定向、404等内容。下⾯我们写⼀个简单视图函数,返回“Hello Django”

1
2
3
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello Django")

其中HttpResponse函数需要引⼊模块django.http,我们可以直接写HttpResponse,然后通过快捷键alt+enter,在下拉框中选择要导⼊的模块。

alt+enter 代码飘红、飘⻩、查看定义、引⼊包都可以使用。

基本路由

添加完视图函数后,还⽆法在浏览器中查看,必须添加路由。所谓路由就是将用户请求转换为访问对应的视图函数。项⽬路由在urls.py中

1
2
3
4
5
6
7
8
from django.conf.urls import path
from django.contrib import admin
from App import views
urlpatterns = [
path('admin/', admin.site.urls),
# 路由由两部分组成,第⼀部是⼀个匹配字符串(和flask规则相同),匹配用户的请求路径,第⼆部分是视图函数
path('', views.index)
]

匹配字符串匹配用户在地址栏中键⼊的url,当用户在地址栏中键⼊http://localhost:9000时会显示Hello World

简单模板

在上⾯我们已经能够展示简单的web⻚⾯,但要显示⼀个html⻚⾯,需要使用模板,在app下建⽴templates⽬录,在templates⽬录下建⽴⼀个index.html⽂件

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>搜狐</title>
</head>
<body>
当你的才华还撑不起你的野心的时候,你就应该静下⼼来学习;<br>
当你的能⼒还驾驭不了你的⽬标时,就应该沉下⼼来,历练;
</body>
</html>

然后修改views中视图函数index

1
2
3
from django.shortcuts import render
def index(request):
return render(request,'index.html')

也可以在html中使用变量和流程控制结构,生成复杂的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 }}</title>
</head>
<body>
当你的才华还撑不起你的野⼼的时候,你就应该静下⼼来学习;<br>
当你的能⼒还驾驭不了你的⽬标时,就应该沉下⼼来,历练;
</body>
</html>

然后修改视图函数

1
2
3
from django.shortcuts import render
def index(request):
return render(request,'index.html',content={'title':'草榴'})

简单模型

django⾃带了⼀个sqlite数据库,可以用于测试,生产环境⼀般不用。在配置⽂件中已经设置好默认的sqlite数据库。

1
2
3
4
5
6
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', #数据库引擎
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), #数据库⽂件路径
}
}

在pycharm左边栏中选—-database,然后点选”+”,弹出sqlite的配置窗⼝

将File改为项⽬中sqlite⽂件:db.sqlite3,然后点击测试,看看能否连接成功。第⼀次配置时,需要下载sqlite的驱动。完成后点击ok。

然后到appmodels.py中创建⼀个User类:

1
2
3
4
5
6
class User(models.Model):
uname = models.CharField(max_length=20)
password = models.CharField(max_length=32)

class Meta:
db_table = 'app_user' # 指定表名,默认为应用名_类名小写

到命令行下,输⼊:

1
2
python manage.py makemigrations #生成数据库迁移⽂件
python manage.py migrate #生成数据库表

然后到pycharm左边栏database中查看:

双击app_user添加数据:

到此为⽌,我们已经给app_user表添加了数据。然后给app添加⼀个模板:list.html

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>
</head>
<body>
<table width="80%" align="center" border="1" cellspacing="0">
<caption>用户列表</caption>
{% for user in users %}
<tr>
<td>{{ user.uname }}</td>
<td>{{ user.password }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>

继续给app添加⼀个视图操作函数:

1
2
3
4
5
from app.models import User
def userlist(req):
#获取app_user表中所有数据
persons = User.objects.all()
return render(req,"list.html", context={'users':persons})

在项⽬的路由表中添加路由:

1
2
3
4
5
6
urlpatterns = [
url('admin/', admin.site.urls),
url(' ',views.index),
url(r'^list/$',views.userlist), # 使用正则匹配url
url('list/',views.userlist), # 有可以直接使用路径匹配
]

到浏览器中访问:http://localhost:9000/list/

Django 模型(M)

模型使用步骤:

  1. 配置数据库
  2. models.py定义模型类
  3. 激活模型
  4. 使用模型

Django默认使用的是sqlite,但在生产环境中⼀般会用mysql、postgrsql、oracle等关系型数据库。

数据库配置

在虚拟开发环境(django-env)中,安装mysql的数据库驱动mysqlclient

1
pip install mysqlclient

在项⽬的settings.py⽂件中找到DATABASES 配置项,将其信息修改为:

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', #mysql数据库引擎
'NAME': 'test', #数据库名
'HOST':'localhost', #数据库服务器地址
'USER': 'test', #mysql数据库用户名
'PASSWORD': 'test123', #密码
'PORT':3306, #端⼝号,可选
}
}

ORM

对象关系映射(Oject Relational Mapping,简称ORM)模式是⼀种为了解决⾯向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,⾃动生成sql语句,将程序中的对象⾃动保存到关系数据库中。优点:

  • 隐藏了数据库访问的细节,简化了sql的使用,提⾼了开发效率
  • 解耦业务逻辑层(view)和数据处理层(model),简化了开发流程,提⾼了系统的可移植性
  • 提⾼了安全性

缺点:

  • 执行效率低
  • 对复杂的sql无能为力
  • 增加了学习成本

基本概念

面向对象概念 ⾯向关系概念
对象 记录(一行)
属性 字段(属性,列)
  • ⼀个模型类对应⼀个表
  • 每个模型类都必须继承django.db.models.Model

模型属性

模型中的属性和数据库表的字段对应,必须定义。模型的属性需要定义成类属性

属性定义语法为:

1
属性 = models.字段类型(选项)

实例:

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

# Create your models here.
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=128)

class Meta:
db_table = 'user' # 指定表名

属性命名规则:

  • 不能是python的保留关键字

  • 不允许使用连续的下划线,因为连续下划线在查询中会用到

  • 定义属性时需要指定字段类型

  • 主键⼀般不用⾃⼰定义,django会⾃动创建⾃增⻓主键列,如果你⾃⼰定义了主键,则django不会再⾃动生成主键

    字段类型

字段名称 字段说明 参数
AutoField ⼀个根据实际Id⾃动增⻓的IntegerField(通常不指定 ⾃动⽣成)
CharField 字符串,默认的表单样式是TextInput max_length=字符⻓度
TextField ⼤⽂本字段,⼀般超过4000使⽤,默认的表单控件是Textarea
IntegerField 整数
DecimalField 使⽤python的Decimal实例表示的⼗进制浮点数 max_digits总位数;decimal_places小数位数
FloatField ⽤Python的float实例来表示的 浮点数
BooleanField true/false 字段,此字段的默认表单控制是CheckboxInput
NullBooleanField 支持null,true,false三种值
DateField 使用Python的datatime.date实例表示的日期,该字段默认对应的表单控件是⼀个TextInput auto_now和 auto_now_add、default这三个参数不能同时存在
TimeField 使⽤Python的datetime.time实例表示的时间 参数同DateField
DateTimeField 使⽤Python的datetime.datetime实例表示的日期和时间 参数同DateField
ImageField 继承了FileField的所有属性和⽅法,但对上传的对象进⾏校验,确保它是个有效的image 不能同时共存
  • auto_now: 每次保存对象时,⾃动设置该字段为当前时间,用于”最后⼀次修改”的时间戳,它总是使用当前⽇期,默认为false
  • auto_now_add: 当对象第⼀次被创建时⾃动设置当前时间,用于创建的时间戳,它总是使用当前⽇期,默认为false

字段选项

适用于任何字段,可以实现字段的约束,在生成字段时通过方法的关键字参数指定

可选参数 说明
null 如果 True ,Django将NULL在数据库中存储空值。默认 是 False 。不要在字符串字段上使⽤。null是数据库范畴的概念
blank 如果 True ,该字段允许为空。默认是 False 。同null不同, 如果字段有 blank=True ,则表单验证将允许输⼊空值。如果字段有 blank=False,则需要该字段
db_column ⽤于此字段的数据库列的名称。如果没有给出,Django将使⽤该字段的名称
db_index 如果为True ,将为此字段创建数据库常规索引。
unique 如果为True,该字段在整个表格中必须是唯一的
primary_key 如果为True,此字段是模型的主键
default 默认值,当前字段如果不给值则执行默认值

定义模型

我们可以在应用的models.py中定义模型:

1
2
3
4
5
6
from django.db import models
class 模型名(models.Model):
属性名 = models.字段名(字段选项/参数)
.....
class Meta: #可选,任何非字段的设置可以写到Meta中
db_table = 'user' #指定表名为uesr
  • 数据库的表名等于:应用名_模型名,如果想指定表名,可以在Meta中使用db_table指定

  • 如果没有指定主键,Django将⾃动给表创建⼀个⾃增⻓主键id

    1
    id = models.AutoField(primary_key=True)
  • Meta中常用设置:

    名称 说明
    db_table 数据库中的表名
    abstract 当设置为True时,说明当前模型是抽象基类
    managed 如果设置False,则迁移不会创建或删除表,默认是True
    ordering ⽤于记录排序,ordering = [‘pub_date’]或降序ordering = [‘- pub_date’]
  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from django.db import models
    from django.utils import timezone
    #用户类
    class User(models.Model):
    uid = models.AutoField(primary_key=True) #⾃增主键
    uname = models.CharField(max_length=60)
    password = models.CharField(max_length=32)
    user_type = ((1,'超级管理员'),(2,'普通用户'))
    type = models.IntegerField(default=2,choices=user_type)
    regtime = models.DateTimeField(default=timezone.now) #缺省值是当前时间
    ip = models.IntegerField(null=True)
    login_type = ((1,'允许登录'),(2,'禁⽌登录')) #用户⾃定义类型对应mysql的enum类型
    allowed = models.IntegerField(default=1,choices=login_type)
    email = models.CharField(max_length=100,null=True)
    memo = models.CharField(max_length=1000,null=True)
    class Meta:
    db_table = 'user' #表名

激活模型

  • 创建迁移⽂件 (此刻表并没有创建到库中)

    1
    python manage.py makemigrations
  • 执行迁移 (将模型创建到库中)

    1
    python manage.py migrate

然后在应用的migrations⽬录中应该生成了迁移⽂件

1
2
3
4
5
6
7
├── app
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── __init__.py

生成的表结构如下

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`uname` varchar(60) NOT NULL,
`password` char(32) NOT NULL,
`type` enum('超级管理员','普通用户') DEFAULT '普通用户',
`regtime` datetime DEFAULT NULL,
`ip` int(11) DEFAULT NULL,
`allowed` enum('允许登录','禁⽌登录') DEFAULT '允许登录',
`email` varchar(100) DEFAULT NULL,
`memo` varchar(1000) DEFAULT NULL,
PRIMARY KEY (`uid`)
)

注意:任何对字段或表的修改都需要重新迁移

  • 反向迁移:可以根据数据库中表⾃动创建模型

    1
    python manage.py inspectdb > App/models.py

模型的使用

1
2
3
4
5
6
7
8
9
10
from app.models.User import User #导⼊User模型
from hashlib import md5
user = User(uname='admin',password=md5(b'123').hexdigest()) #实例化⼀个新对象
user.save() #insert 插⼊数据库

#便捷插⼊
User.objects.create(username='妈咪哄',password='111')

# 批量插⼊记录(不支持多对多)
User.objects.bulk_create(User.objects.create(username='哈哈',password='111'),User.objects.create(username='嘻嘻',password='111'))

1
2
u1 = User.objects.get(pk=1)
u1.delete() # 删除该记录
  • 数据的逻辑删除

    对于重要数据,⼀般不会直接删除,会在表中增加⼀个字段⽐如: is_deleted,如果删除的话,将这个字段置为True,以后查询的时候不在查询,这种操作称为逻辑删除

1
2
3
4
5
6
7
u2 = User.objects.get(pk=2)
u2.uname = '⼩甜甜' #update 更新
u2.save()

# 修改多条记录
users = User.objects.all()
users.update(password=md5(b'345').hexdigest())

数据查询

要从数据库检索数据,⾸先要获取⼀个查询集(QuerySet),查询集表示从数据库获取的对象集合,它可以有零个,⼀个或多个过滤器。返回查询集的方法,称为过滤器,过滤器根据给定的参数缩⼩查询结果范围,相当于sql语句中where或limit。

  • 在管理器上调用过滤器方法会返回查询集
  • 查询集经过过滤器筛选后返回新的查询集,因此可以写成链式过滤
  • 惰性执行:创建查询集不会带来任何数据库的访问,直到调用数据时,才会访 问数据库
  • 以下对查询集求值:迭代、切⽚、序列化、与if合用、repr()/print()/len()/list()/bool()
管理器的方法 返回类型 说明
模型类.objects.all() QuerySet 返回表中所有数据
模型类.objects.filter() QuerySet 返回符合条件的数据
模型类.objects.exclude() QuerySet 返回不符合条件的数据
模型类.objects.order_by() QuerySet 对查询结果集进⾏排序
模型类.objects.values() QuerySet 返回⼀个Queryset,其中每个对象为⼀个 字典
模型类.objects.values_list() QuerySet 和values()基本相同,但每个对象是⼀个元 组
模型类.objects.reverse() QuerySet 对排序的结果反转
模型类.objects.only(字 段) QuerySet 只显示指定字段
模型类.objects.defer(字 段) QuerySet 去除指定字段
模型类.objects.get() 模型对象 返回⼀个满⾜条件的对象;如果没有找到符合条件的对象,会引发模型类.DoesNotExist异常;如果找到多个,会引发模型类.MultiObjectsReturned 异常
模型类.objects.first() 模型对象 返回第⼀条数据
模型类.objects.last() 模型对象 返回最后⼀条数据
模型类.objects.earliest() 模型对象 根据指定字段返回最早增加的记录
模型类.objects.latest(field) 模型对象 根据field字段返回最近增加记录
模型类.objects.exists() bool 判断查询的数据是否存在
模型类.objects.count() int 返回查询集中对象的数⽬

返回查询集

  • all()

    1
    2
    # 获取所有数据,对应SQL:select * from User
    User.objects.all()
  • filter(**kwargs) 返回QuerySet包含与给定查找参数匹配的新查询集

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #等价sql:select * from User
    User.objects.filter()

    #等价sql:select * from User where uname = 'admin'
    User.objects.filter(uname='admin')

    #等级sql:select * from User where uid > 1 and type = 2
    User.objects.filter(uid__gt=1,type=2)

    #链式调用,等价于User.objects.filter(uid__gt=1,type=2)
    User.objects.filter(uid__gt=1).filter(type=2)
  • exclude(**kwargs)

    1
    2
    # 不匹配,对应SQL:select * from User where name != 'admin'
    User.objects.exclude(name='admin')
  • order_by(*fields)

    • 参数是字段名,可以有多个字段名,默认是升序
    • 如果要按某个字段降序,在字段名前加’-‘; “-uid”表示按uid降序排列
    1
    2
    3
    4
    5
    6
    #按uid升序排列 等价于 order by uid
    User.objects().order_by('uid')
    #按uid降序排列 等价于 order by uid desc
    User.objects.order_by('-uid')
    #多列排序 等价于 order by password,uid desc
    User.objects.order_by('password','-uid')
  • values()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #返回所有字段
    User.objects.values()

    #返回数据:
    [{'uid': 2, 'uname': '⼩甜甜', 'password':
    '59f2443a4317918ce29ad28a14e1bdb7'type': '普通用户', 'regtime':
    None, 'ip': None, 'allowed': '允许登录', 'email': None, None},...]

    #返回指定字段
    User.objects.values('uname','password')
    [{'uname': '⼩甜甜', 'password':'59f2443a4317918ce29ad28a14e1bdb7'},...]
  • reverse()

    1
    2
    User.objects.order_by('id').reverse() # 降序
    User.objects.order_by('-id').reverse() # 升序
  • distinct() 去重

    1
    User.objects.values('password').distinct()

返回单个值

下⾯这些方法后⾯不能再跟其他过滤方法,因为他们不返回查询集

  • get() 只匹配一条数据

    1
    2
    3
    u = User.objects.get(pk=1)  # 正常
    u = User.objects.get(uid__gt=20)#MultipleObjectsReturned 匹配到了多条数据
    u = USer.objects.get(uid__lt=1) #DoesNotExist 匹配失败
  • first()和last()

    1
    2
    User.objects.all().first() #返回结果集中第⼀条数据
    User.objects.all().last() #返回结果集中最后⼀条数据
  • count()

    • 返回结果集记录数⽬,等价于select count(*)
    • 不会返回整个结果集,相⽐len方法更有效
    1
    User.objects.count()
  • exists()

    • 判断查询集中是否有记录,有返回True,否则返回False

      1
      User.objects.filter(uid=3).exists()

查询集限制

查询集类似列表,可以使用下标进行限制,类似sql语句中的limit子句。但索引不能是负数

  • 索引
  • 切片
1
2
3
4
5
User.objects.all()[0] #等同于:limit 0,1
User.objects.all()[2] #等同于:limit 2,1
User.objects.all()[0:2] #等同于limit 2
User.objects.all()[:2] #等同于limit 2
User.objects.all()[::2]

字段查询

相当于sql语句中where子句,它可以为filter、exclude和get方法提供参数。

1
属性名称__比较运算符=值 #是两个下划线
操作符 说明 事例
exact 精确判等 uname = ‘admin’uname__exact = 'admin' uname__exact = None #uname is null
iexact 不区分大小写判等 name__iexact = ‘admin’
contains 模糊查询,等价like ‘% 值%’ uname__contains = ‘admin’
startswith 以..开头 uname__startswith = ‘a’
istartswith (不区分大小写)以… 开头 uname__istartswith = ‘a’
endswith 以…结尾 uname__endswith = ‘m’
iendswith (不区分大小写)以…结尾 uname__iendswith = ‘m’
isnull 判空(等价 = None) uname__isnull = True #等价于 uname is null
in 包含 uid__in = [1,2,4] # in后面必须是可迭代对象
range 范围测试(相当 between and) uid__range = [2,5] #uid>=2 and uid <=5
gt/gte 大于/大于等于 uid__gt = 2
lt/lte 小于/小于等于 uid__lte = 2
regex 正值匹配 uname__regex= r’^a’
iregex 不区分大小写正则匹配 uname__iregex= r’^a’
  • in后面可以跟⼀个子查询,但要求子查询只能返回⼀个字段

    1
    2
    3
    4
    User.objects.filter(uid__in = (2,3,4))
    User.objects.filter(uid__in = [2,3,4])
    res = User.objects.filter(uid__gt = 1).values('uid') #查询集中只有⼀个字段uid
    User.objects.filter(uid__in = res)
  • 日期查询

    • year、month、day、week_day、hour、minute、second

      1
      2
      等价sql: select * from User where year(regtime) = 2018
      User.objects.filter(regtime__year = 2018)

统计查询

需要先导⼊模块:

1
from django.db.models import Max,Min,Sum,Avg,Count
  • 聚合查询:对多行查询结果的⼀列进行操作

    1
    2
    3
    4
    #统计记录总数: select count(*) from user
    User.objects.aggregate(Count('uid')) #{'uid__count': 4}
    User.objects.aggregate(Max('uid')) #{'uid__max': 5}
    User.objects.aggregate(Min('uid')) # {'uid__min':2}
  • 分组统计

    1
    2
    3
    4
    5
    6
    7
    8
    #等价sql: select type,count(*) from user group by type
    res = User.objects.values('type').annotate(Count('uid'))
    #统计结果: [{'type': '普通用户', 'uid__count': 3}, {'type': '超级管理员', 'uid__count': 1}]


    #查看生成的sql语句
    print(res.query)
    #SELECT `user`.`type`, COUNT(`user`.`uid`) AS `uid__count` FROM `user` GROUP BY `user`.`type` ORDER BY NULL

Q对象和F对象

需要先导⼊模块:

1
from django.db.models import Q,F
  • Q对象可以对关键字参数进行封装,从而更好的应用多个查询,可以组合&(and)、|(or)、~(not)操作符。

    1
    2
    3
    4
    #原生sql:select * from user where uid = 2 or uid = 3
    User.objects.filter(Q(uid=2)|Q(uid=3))
    User.objects.filter(Q(uid__gte=2) & Q(uid__lte=3))
    User.objects.filter(~Q(uid__gte=2))
  • F对象:用于比较表中两个字段

    1
    2
    #等价sql:select * from user where uid < type
    User.objects.filter(uid__lte = F('type'))

    原始sql

您可以使用它 Manager.raw()来执行原始查询并返回模型实例,或者您可以完全避免模型层并直接执行⾃定义SQL。

  • raw

    1
    2
    3
    4
    5
    6
    7
    8
    9
    users = User.objects.raw("select * from user")
    # 会造成SQL
    users = User.objects.raw("select * from user where username='{}'".format('单强'))
    print("select * from user where username='{}'".format('单强'))
    users = User.objects.raw("select * from user where username='{}'".format('单强'))
    # 原生SQL使用下面的写法,可以防止SQL注入
    users = User.objects.raw("select * from user where username=%s",["sddfsdf' or '1"])
    print(list(users))
    print(type(users))
  • 自定义sql,聚合使用下面的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from django.db import connection
    # with语句相当与cursor= connection.cursor() 和 cursor.close(),简化了语
    with connection.cursor() as cursor:
    cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s",[para])
    cursor.execute("SELECT foo FROM bar WHERE baz = %s", [para2])
    row = cursor.fetchone()
    # 返回列表套字典
    with connection.cursor() as cursor:
    cursor.execute("select * from publisher")
    columns = [col[0] for col in cursor.description]
    res = [dict(zip(columns, row)) for row in cursor.fetchall()]
    print(res)

模型成员

模型类和数据库中表对应,模型类的对象和记录对象,模型类本身没有数据库访问功能,但模型类中有⼀个Manager类的对象,通过管理器对象可以实现和数据库的访问。

当我们没有为模型类定义管理器时,Django会为模型类生成⼀个名为objects的管理器,⾃定义管理器后,Django不再生成默认管理器objects

管理器是Django的模型进行数据库操作的接⼝,Django应用的每个模型都拥有⾄少⼀个管理器。Django⽀持⾃定义管理器类,继承⾃models.Manager

⾃定义管理器类主要用于两种情况:

  • 修改原始查询集
  • 向管理器类中添加额外的方法,如向数据库中插⼊数据。

重命名管理器

在模型类中⾃定义⼀个新的管理器,则原有的objects管理器不会存在,以后对数据库的操作使用⾃⼰定义的管理器

模型类

1
2
3
4
5
6
7

class Artical(models.Model):
aid = models.AutoField(primary_key=True)
title = models.CharField(max_length=100,null=False)
content = models.TextField(max_length=10000,null=True)
....
art_manager = models.Manager() #⾃定义了⼀个新的管理器,名字为art_manager

视图views

1
2
3
def article_get(request):
articles = Artical.art_manager.all() #使用art_manager进行查询
return render(request,"articlelist.html",context={'articls':articles})

⾃定义管理器

  • 修改原始查询集(由all()获取的查询集)

    • 修改管理器的get_queryset方法,可以改变all方法返回的原始查询集
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #⾸先⾃定义Manager的子类
    class ArticleManager(models.Manager):
    def get_queryset(self):
    return super().get_queryset().filter(ispublished=True) #获取已发表的⽂章


    #模型类
    class Artical(models.Model):
    ....
    objects = models.Manager() #可以有多个管理器
    publishManager = ArticleManager()

    # 视图views
    def article_publish(request):
    published = Artical.publishManager.all()
    return HttpResponse("已经发表{}".format(published[0].title))
  • 给管理器类中添加额外的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class ArticleManager(models.Manager):
    def get_queryset(self):
    return super().get_queryset().filter(ispublished=True)

    #新增⼀个创建对象的方法
    def create(self,title,content,publishingdate,comments,likenum,ispublished):
    article = Artical()
    article.title = title
    article.content = content
    article.publishingdate = publishingdate
    article.comments = comments
    article.likenum = likenum
    article.ispublished = ispublished
    article.save()
    return article

    #views.py中
    def article_add(request):
    # art = Artical(title='⼩时代',content="混乱的逻辑")
    # art.save()
    Artical.publishManager.create('中美贸易战','川朴⼀⾖⽐','2018-10-8',0,0,1)
    return HttpResponse("")

模型对应关系

关系数据库最强⼤的地方在于“关系”,也即表和表之间是有关联的,这种关联有三种类型

  • ⼀对⼀
  • ⼀对多
  • 多对多

⼀对⼀

⼀个学生有⼀个档案,⼀个档案属于⼀个学生,那么学生表和档案表就是⼀对⼀关系。学生表是主表,档案表是从表,从表中有⼀个外键和学生表关联,并且要求外键取值唯⼀。对应关键字为:OneToOneField

模型类Student

1
2
3
4
5
6
7
8
9
10
11

class Student(models.Model):
sno = models.CharField(max_length=6,primary_key=True)
sname = models.CharField(max_length=100,null=False)
ssex = models.CharField(max_length=2,default='男',null=True)
sage = models.IntegerField(null=True)
sclass = models.CharField(max_length=10,null=True)
def __str__(self):
return "no:{},name:{}".format(self.sno,self.sname)
class Meta:
db_table = 'student'

模型类Archives

1
2
3
4
5
6
7
8
9
class Archives(models.Model):
idcard = models.CharField(max_length=18, unique=True)
address = models.CharField(max_length=200,null=True)
# on_delete=models.CASCADE 级联删除,删除学生会连同档案⼀块删除
student = models.OneToOneField(Student, on_delete=models.CASCADE)
def __str__(self):
return "{},{}".format(self.idcard,self.address)
class Meta:
db_table = 'archives'
  • 增加数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def addstudent(request):
    student = Student()
    student.sno = '180502'
    student.sname = '杨康'
    student.sage = 22
    student.save()
    return HttpResponse("增加了⼀个学生")

    def addarchives(request):
    stu = Student.objects.get(pk='180503')
    arc = Archives()
    arc.idcard = '130098384893838953'
    arc.student = stu #学生对象必须已经保存到数据库,否则错误
    arc.save()
    return HttpResponse("增加档案")
  • 删除数据

    1
    2
    3
    4
    def deletestudent(request):
    student = Student.objects.get(pk='180503')
    student.delete()
    return HttpResponse("删除学生")
  • 正向查询

    1
    2
    3
    4
    5
    6
    7
    8
    def findstudent(request):
    # 获取学生信息
    student = Student.objects.first()
    print(student)
    # 通过学生对象获取档案信息
    archive = student.archives
    print(archive)
    return HttpResponse(student)
  • 反向查询

    1
    2
    3
    4
    5
    6
    def findarchives(request):
    #获取档案记录
    archive = Archives.objects.first()
    #通过档案获取关联学生信息
    student = archive.student
    return HttpResponse(student)
  • 跨关系查询

    1
    2
    3
    4
    5
    6
    7
    def lookup(request):
    #根据档案查学生
    # student = Student.objects.get(archives__pk=1)
    student =Student.objects.get(archives__idcard='13009488384383838')
    #根据学生查档案
    archive = Archives.objects.get(student__sno='180501')
    return HttpResponse(archive)
  • on_delete

    • CASECADE 默认,默认级联删除数据
    • PROTECT 保护模式,当从表中存在级联记录的时候,删除主表记录会抛出保护异常,从表中不存在级联数据的时候.是允许删除的
    • SET_XXX
      • NULL 外键字段本身必须允许为空
      • DEFAULT 外键字段本身有默认值
    • DO_NOTHING 什么都不做

⼀对多

⼀个出版社可以出版多本书,⼀本书只能被⼀个出版社出版。出版社和图书表属于⼀对多,⼀对多⼀般将主表中的主键并到从表中做外键。在模型中用ForeignKey表示多对⼀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Publisher(models.Model):
pname = models.CharField(max_length=100,null=True)

def __str__(self):
return self.pname
class Meta:
db_table = 'publisher'

class Book(models.Model):
bname = models.CharField(max_length=200,null=True)
#多对⼀模型通过ForeignKey表示多对⼀
#如果publisher定义在book之后,第⼀个参数应该用字符串'Publisher'
publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE,
null=True,
db_column='pid', #表中字段名
related_name='books') #通过出版社查图书时使用的关系名
def __str__(self):
return self.bname
class Meta:
db_table = 'book'
  • 增加

    1
    2
    3
    4
    5
    6
    pub = Publisher.objects.first()
    pub.books.create(bname='红岩')
    book = Book.objects.get(pk=1)
    pub.books.add(book) #book必须已经保存到数据库
    books = Book.objects.filter(pk__lt=5)
    pub.books.bulk_create(list(books))
  • 删除和更新

    1
    2
    3
    pub = Publisher.objects.first()
    pub.books.all().delete() #删除出版社出版的所有图书
    pub.books.all().update(bname='ddd')
  • 正向查询

    1
    2
    3
    4
    5
    6
    7
    def findpublisher(req):
    pub = Publisher.objects.first()
    print(pub)
    # pub = Publisher()
    book = pub.book_set.all()
    print(book)
    return HttpResponse("查询出版社")
  • 反向查询

    1
    2
    3
    def findbook(req):
    book = Book.objects.first()
    return HttpResponse(book.publisher.pname)
  • 跨关系查询

    1
    2
    3
    4
    5
    6
    7
    8
    def loopup(req):
    # 根据图书获取出版社
    pub = Publisher.objects.get(book__bname='花样年华927937')
    print(pub)
    # 根据出版社获取图书
    books = Book.objects.filter(publisher__pname='科技出版社5829')
    print(books)
    return HttpResponse("跨关系查询")

多对多

⼀个买家可以购买多件商品,⼀件商品可以被多个买家购买,买家和商品之间构成多对多关系,多对多关系必然会生成⼀张中间表:买家-商品表,记录商品和买家的关系,该表包含商品表主键和买家表的主键

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
from django.db import models
# Create your models here.
class Buyer(models.Model):
bname = models.CharField(max_length=30)
level = models.IntegerField(default=1)



class Goods(models.Model):
gname = models.CharField(max_length=100)
price = models.FloatField()
buyer = models.ManyToManyField(Buyer) #这种写法⾃动生成第三张表,但我们无法直接控制
def __str__(self):
return self.gname +" "+ str(self.price)

#手动创建中间表
class Orders(models.Model):
buyer = models.ForeignKey(Buyer,on_delete=models.CASCADE,db_column='bid')
goods = models.ForeignKey('Goods',on_delete=models.CASCADE,db_column='gid')
num = models.Integer(default=1)

class Goods(models.Model):
gname = models.CharField(max_length=100)
price = models.FloatField()
buyer = models.ManyToManyField(Buyer,through='Orders')
  • 购买商品

    1
    2
    3
    4
    5
    def sellgoods(req):
    goods = Goods.objects.get(pk=randint(1,Goods.objects.count())
    goods.buyer.add(Buyer.objects.get(pk=randint(1,Buyer.objects.count())))
    goods.save()
    return HttpResponse("剁⼿成功")
  • 删除商品

    1
    2
    3
    4
    buyer = Buyer.objects.get(pk=13)
    goods = Goods.objects.filter(pk__lt=10)
    buyer.goods_set.clear() #删除所有商品
    buyer.goods_set.remove(Goods.objects.get(pk=2)) #删除指定商品
  • 正向查询

    1
    2
    3
    4
    5
    def findgoods_by_buyer(req):
    buyer = Buyer.objects.get(pk=13)
    res = buyer.goods_set.all()
    print(res)
    return HttpResponse("由买家查询商品")
  • 反向查询

    1
    2
    3
    4
    5
    def findbuyer_by_goods(request):
    goods = Goods.objects.last()
    buyer = goods.buyer.all()
    print(buyer)
    return HttpResponse("由商品查询买家")

模型继承

django中的数据库模块提供了⼀个⾮常不错的功能,就是⽀持models的⾯向对象,可以在models中添加Meta,指定是否抽象,然后进行继承

1
2
3
4
5
6
7
class Animal(models.Model):
xxx
class Meta:
abstract = True/False

class Dog(Animal):
xxx

默认模型就是允许继承的,但是默认的继承处理方式不是很合理:

  • 默认在⽗类中定义的字段会存在⽗类的表中,子类的数据通用部分会存在⽗表中,子类特有数据会在子表中,子类通过外键进行级联
  • 默认方式比较垃圾,效率⽐较低

开发中,需要将⽗类抽象化,在元信息中使用abstract=True

  • 抽象化的父类不会再数据库生成表了
  • 子类会将父类中的通用数据,复制到子表中

Django 模板(T)

模板用于快速生成动态⻚⾯返回给客户端,模板是⼀个⽂本,用于分离⽂档的表现形式和内容。 模板定义了占位符以及各种用于规范⽂档该如何显示的模板标签。模板通常用于产生HTML,但是Django的模板也能产生任何基于⽂本格式的⽂档。

模板包含两部分:

  • html代码
  • 模板标签

一、模板位置

  • 在应用中建⽴templates⽬录,好处不需要注册,不好的地方,有多个应用的时候不能复用⻚⾯

  • 第⼆种是放在⼯程的⽬录下,好处是如果有多个应用,可以调用相同的⻚⾯,需要注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    TEMPLATES = [
    {
    'BACKEND':
    'django.template.backends.django.DjangoTemplates',
    'DIRS': [os.path.join(BASE_DIR, 'templates')], #模板绝对路径
    'APP_DIRS': True, #是否在应用⽬录下查找模板⽂件
    'OPTIONS': {
    'context_processors': [
    'django.template.context_processors.debug',
    'django.template.context_processors.request',
    'django.contrib.auth.context_processors.auth',
    'django.contrib.messages.context_processors.messages',
    ],
    },
    },
    ]

Django 模板查找机制: Django 查找模板的过程是在每个 app 的 templates⽂件夹中找(行不只是当前 app 中的代码只在当前的 app 的 templates ⽂件夹中找)。各个 app 的 templates 形成⼀个⽂件夹列表,Django 遍历这个列

表,⼀个个⽂件夹进行查找,当在某⼀个⽂件夹找到的时候就停⽌,所有的都遍历完了还找不到指定的模板的时候就是 Template Not Found (过程类似于Python找包)。这样设计有利当然也有弊,有利是的地方是⼀个app可以用另⼀个app的模板⽂件,弊是有可能会找错了。所以我们使用的时候在templates 中建⽴⼀个 app 同名的⽂件夹,这样就好了。

二、模板的渲染

loader加载

好处是可以加载⼀次模板,然后进行多次渲染

1
2
3
4
5
6
7
8
from django.template import loader #导⼊loader
def index(request):
temp = loader.get_template('index.html')
print(temp.__dict__)
# 渲染模板,生出html源码
res = temp.render(context={'content':'hello index'})
print(res)
return HttpResponse(res)

render加载

1
2
3
4
5
6
from django.shortcuts import render
render(request,templatesname,context=None)
参数:
request: 请求对象
templatesname:模板名称
context:参数字典,必须是字典

##三、模板语法

django模板中包括两部分:变量和内置标签。变量会在模板渲染时被其值代替,内置标签负责逻辑控制

变量

变量在模板中的表示为:,变量名就是render中context中的键。变量可以基本类型中的数值、字符串、布尔,也可以是字典、对象、列表等。django提供了点号来访问复杂数据结构。

  • 列表、元组的元素可以使用索引引用,不能使用负索引,语法:变量.索引
  • 字典: 字典变量.key
  • 对象: 对象.属性 对象.方法名(方法不能有参数)

当模板系统在变量名中遇到点时,按照以下顺序尝试进行查找

  • 字典类型查找

  • 属性查找

  • 方法调用

  • 列表类型索引

如果模板中引用变量未传值,则会被置为空,不会报错,除⾮你对其进行了操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def handle_var(request):
num = 10
name = "伟大的意大利左后卫"
students = [10,20,30,[50,60]]
student = {'name':'马尔蒂尼','age':30}
# locals(): 返回包含当前范围的局部变量的字典。
return render(request,"变量.html",locals())


{# 变量 #}
{# 复杂类型的访问可以使用点,但不能使用下标 #}
{{ num }}
{{ name }}
{# --{{ students[1] }} 不能使用下标 #}
{{ students.0 }}----{{ students.3.0 }}
{{ student.name }}

过滤器

过滤器是在变量显示之前修改它的值的⼀个方法,过滤器使用管道符。过滤器可以串联调用

1
{{ 变量|方法}}

常见的过滤器方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|     方法名      |            作用             |                       示例                        |
| default | 缺省值 | {{ li|default:"缺省值" }}
| default_if_none | 如果变量是none 则显示缺省值 | {{ value|default_if_none:'hello' }} |
| cut | 从字符中删除指定字符 | {{ value | cut:' ' }} 删除value所有空格 |
| length | 获取字符串或列表的⻓度 | {{ str1\|length}} |
| lower | 将所有字⺟都变为⼩写 | |
| upper | 将所有字⺟都变为⼤写 | |
| truncatechars | 截取字符串前n个字符 | {{ value|truncatechars:9 }} |
| date | 格式化⽇期字符串 | {{value|date:"Y-m-d H:i:s" }} |
| add | 增加变量的值 | {{ num|add:'3'}} |
| divisibleby | 把变量的值除以指定值 | {{ value|divisibleby:"3" }} |
| first | 获取列表第一个元素 | {{ value|first }} |
| last | 获取列表最后一个元素 | |
| join | 将列表内容连接为一个字符串 | {{ value|join:'-' }} |
| autoescape | 设置或取消转译 | {% autoescape off %}{{ data }}{% endautoescape %} |

⾃定义过滤器

内置过滤器功能有限,如果不能满足需求,可以自己定义过滤器。

  • 在app里创建⼀个包:templatetags

  • 在包里创建⼀个py⽂件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from django import template
    # 实例化⾃定义过滤器注册对象
    register = template.Library()
    # name代表在模板中使用的过滤器的名称
    @register.filter(name='hello')
    def hello(value,arg):
    """
    :param value: 传给hello过滤的值
    :param arg: hello自带的参数
    :return:
    """
    return value + str(arg)
  • 在模板中使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {% load customfilter %} #加载自定义过滤器的模块
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    {{ name |hello:' how are you' }} #使用自定义过滤器
    </body>
    </html>

内置标签

语法:

1
{% tag %}

if标签

1
2
3
4
5
6
7
{% if express1 %}
# to do
{% elif express2 %}
# to do
{% else %}
# to do
{% endif %}
  • if表达式中使用以下运算符(优先级从高到低):
    • < >= <= == !=
    • in 、not in
    • is、is not
    • not
    • and
    • or
  • 不要在表达式中使用(),可以使用if嵌套实现功能
  • 不⽀持 if 3 < b < 5这种写法

for

遍历可迭代对象

1
2
3
{% for x in y %}
...
{% endfor %}
  • 反向迭代(reversed)

    1
    2
    3
    {% for value in c [1,2,3,4,5] reversed %}
    <span>{{ value }}---</span>
    {% endfor %}
  • empty 当可迭代对象为空或不存在时执行,否则不执行

    1
    2
    3
    4
    5
    {% for value in c %}
    <span>{{ value }}---</span>
    {% empty %}
    数据不存在
    {% endfor %}
  • 字典迭代

    1
    2
    3
    4
    # e = {'a1':20,'b1':40}
    {% for k,v in e.items %}
    <div>{{ k }}---{{ v }}</div>
    {% endfor%}
  • 获取for循环迭代的状态

    变量名称 变量说明
    forloop.counter 获取迭代的索引 从1开始
    forloop.counter0 获取迭代的索引 从0开始
    forloop.revcounter 迭代的索引从最⼤递减到1
    forloop.revcounter0 迭代的索引从最⼤递减到0
    forloop.first 是否为第⼀次迭代
    forloop.last 是否为最后⼀次迭代
    forloop.parentloop 获取上层的迭代对象
    1
    2
    3
    4
    5
    6
    7
    8
    {% for i in c %}
    <li>{{ forloop.first }}</li>
    <li>{{ forloop.last }}</li>
    <li>{{ forloop.counter }}</li>
    <li>{{ forloop.counter0 }}</li>
    <li>{{ forloop.revcounter }}</li>
    <li>{{ forloop.revcounter0 }}</li>
    {% endfor %}

ifequal/ifnotequal

用于判断两个值相等或不等的

1
2
3
4
5
6
{% ifequal var var %}

{% endifequal %}
{% ifnotequal var var %}

{% endifnotequal %}

注释

单行注释

1
{# 注释的内容 #}

多行注释

1
2
3
{% comment %}
...
{% endcomment %}

跨站请求伪造 csrf

防⽌⽹站受第三方服务器的恶意攻击(确定表单到底是不是本⽹站的表单传递过来的)。csrf相当于在表达中增加了⼀个隐藏的input框,用于向服务器提交⼀个唯⼀的随机字符串用于服务器验证表单是否是本服务器的表单。

使用:

settings.py

1
2
3
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]

表单⾥

1
2
3
4
5
<form action="" method="post">
{% csrf_token %}
<input type="text" name="username">
<p><input type="submit"></p>
</form>
  • 全局禁用csrf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #在settings中设置
    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',
    ]
  • 局部禁用csrf

    1
    2
    3
    4
    5
    #在不想检验csrf的视图函数前添加装饰器@csrf_exempt。
    from django.views.decorators.csrf import csrf_exempt,csrf_protect
    @csrf_exempt
    def csrf1(request):
    pass
  • ajax验证csrf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Ajax提交数据时候,携带CSRF:
    a. 放置在data中携带
    <form method="POST" action="/csrf1.html">
    {% csrf_token %}
    <input id="username" type="text" name="username" />
    <input type="submit" value="提交"/>
    <a onclick="submitForm();">Ajax提交</a>
    </form>
    <script src="/static/jquery-1.12.4.js"></script>
    <script>
    function submitForm(){
    var csrf = $('input[name="csrfmiddlewaretoken"]').val();
    var user = $('#user').val();
    $.ajax({
    url: '/csrf1.html',
    type: 'POST',
    data: { "user":user,'csrfmiddlewaretoken': csrf},
    success:function(arg){
    console.log(arg);
    }
    })
    }
    </script>

    注意:csrf的意义在于给每⼀个表单都设置⼀个唯⼀的csrf的值并且cookie也存储⼀份当提交表单过来的时候判断cookie中的值和csrf_token中的值是否都为本⽹站生成的如果验证通过则提交否则403

模板导⼊标签( include)

可以把指定html⽂件代码导⼊到当前⽂件,实现模板代码的复用/重用。语法格 式

1
{% include '路径/xxx.html' %}

url标签

在模板中url标签可用于反向解析

1
2
3
<h2><a href="{% url 'App:index' %}">动态生成路由地址不带参的跳转</a></h2>
<h2><a href="{% url 'App:args1' 1 2 %}">动态生成路由地址带参的跳转</a></h2>
<h2><a href="{% url 'App:args1' num1=1 num2=2 %}">动态生成路由地址带关键字参数的跳转</a></h2>

四、模板继承

在整个⽹站中,如何减少共用⻚⾯区域(比如站点导航)所引起的重复和冗余代码?Django 解决此类问题的⾸选方法是使用⼀种优雅的策略—— 模板继承 。

本质上来说,模板继承就是先构造⼀个基础框架模板,行后在其子模板中对它所包含站点公用部分和定义块进行重载

1
2
3
{% extends %}继承父模板
{% block %}子模板可以重载这部分内容
{{block.super}}调用父模板的代码

使用继承的⼀种常⻅方式是下⾯的三层法

  • 创建base.html模板,在其中定义站点的主要外观感受。这些都是不常修改甚⾄从不修改的部分。
  • 为每种类型的⻚⾯创建独⽴的模板,例如论坛⻚⾯或者图⽚库。这些模板拓展相应的区域模板
  • ⾃⼰的⻚⾯继承⾃模板,覆盖⽗模板中指定block

注意事项:

1
2
3
4
5
1. 如果在模板中使用 {% extends %} ,必须保证其为模板中的第⼀个模板标记。否则,模板继承将不起作用
2. ⼀般来说,基础模板中的 {% block %} 标签越多越好。
3. 如果发觉⾃⼰在多个模板之间有重复代码,你应该考虑将该代码放置到⽗模板的某个 {% block %} 中。
4. 不在同⼀个模板中定义多个同名的 {% block %} 。
5. 多数情况下, {% extends %} 的参数应该是字符,但是如果直到运行时方能确定⽗模板名称,这个参数也 可以是个变量。

五、静态资源配置

什么是静态资源:css、js、images 需要从外部导⼊的资源

  1. 创建static⽂件夹(通常放在根⽬录下)

  2. 需要在settings注册

    1
    2
    3
    4
    STATIC_URL = '/static/'
    STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
    ]
  3. 在模板中使用静态资源

    1
    2
    3
    {% load static %} #放置到模板开头
    <img src="/static/img/img.jpeg" alt=""> #硬编码
    <img src="{% static 'img/img.jpeg' %}" alt=""> #动态写法,建议用这种

六、jinja2模板引擎配置

  • 安装jinja2模板引擎

    1
    pip install jinja2
  • 设置jinja2环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 在应用⽬录下,创建jinja2_env.py配置⽂件
    from django.contrib.staticfiles.storage import staticfiles_storage
    from django.urls import reverse
    from jinja2 import Environment
    def environment(**options):
    env = Environment(**options)
    env.globals.update({
    'static': staticfiles_storage.url,
    'url': reverse,
    })
    return env
  • 配置(setting.py)

    • 独⽴使用jinja2,不使用Django模板引擎

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      #独⽴使用jinja2
      INSTALLED_APPS = [
      #'django.contrib.admin', # 注释了admin
      .....
      ]

      #模板配置
      TEMPLATES = [{
      'BACKEND':'django.template.backends.jinja2.Jinja2',#jinja2模版
      'DIRS': [
      os.path.join(BASE_DIR, 'templates'),#模版⽂件位置
      ],
      'APP_DIRS': True,
      'OPTIONS': {
      'context_processors': [
      'django.template.context_processors.debug',
      'django.template.context_processors.request',
      'django.contrib.auth.context_processors.auth',
      'django.contrib.messages.context_processors.messages',
      ],
      'environment': 'App.jinja2_env.environment', # 配置环境,jinja的配置⽂件位置
      },
      },
      ]
    • 两个同时使用

      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
      #模板配置
      TEMPLATES = [{
      'BACKEND': 'django.template.backends.jinja2.Jinja2',#jinja2模版
      'DIRS': [
      os.path.join(BASE_DIR, 'templates2'),#修改模版⽂件位置
      ],
      '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',
      ],
      'environment': 'App.jinja2_env.environment', # 配置环境,jinja的配置⽂件位置
      },
      },
      {
      'BACKEND': 'django.template.backends.django.DjangoTemplates',
      'DIRS': [os.path.join(BASE_DIR, 'templates')]
      ,
      'APP_DIRS': True,
      'OPTIONS': {
      'context_processors': [
      'django.template.context_processors.debug',
      'django.template.context_processors.request',
      'django.contrib.auth.context_processors.auth',
      'django.contrib.messages.context_processors.messages',
      ],
      },
      },
      ]
  • 静态资源和url

    1
    2
    3
    4
    <p>
    <a href="{{ url('app:index') }}">dddd</a>
    </p>
    <img src="{{ static('images/1.jpg') }}" alt="">

Django 视图(request,response对象)和路由(V)

Django中的视图主要用来接受Web请求,并做出响应。此响应可以是⽹⻚,重定向或404错误,XML⽂档或图像等的HTML内容。在mvt模式中,视图负责从模型中获取数据,然后展示在模板中,是联系模型和模板的桥梁,是业务逻辑层

视图响应的过程:

当用户从浏览器发起⼀次请求时,⾸先django获取用户的请求,然后通过路由(urls)将请求分配到指定的是视图函数。视图函数负责进行相应的业务处理,处理完毕后把结果(可能是json、html等)浏览器

路由

当用户在您的Web应用程序上发出⻚⾯请求时,Django会获取url中请求路径(端⼝之后的部分),然后通过urls.py⽂件查找与路径相匹配的视图,然后返回HTML响应或404未找到的错误(如果未找到)。在urls.py中,最重要的是 “urlpatterns”列表。这是您定义URL和视图之间映射的地方。映射是URL模式中的path对象,例如

1
2
3
4
5
6
from django.conf.urls import patterns, include
from app import views
urlpatterns = [
path('hello/', views.hello, name='hello'),
path('blog/', include('blog.urls')),
]

path对象有四个参数

  • 模式串:匹配用户请求路径的字符串(和flflask⼀样)
  • 视图函数:匹配上用户指定请求路径后调用的是视图函数名
  • kwargs:可选参数,需要额外传递的参数,是⼀个字典
  • 名称(name):给路由命名,在代码中可以使用name进行反向解析(由name获取用户请求路劲)

另外,如果path中模式串如果不能满⾜你路由规则,还可以使用re_path对象,re_path对象中模式串是正则表达式,其他三个参数和path对象⼀致

模式

path中的模式串是⼀个普通字符串,用于匹配用户的请求路径。如果这种方式无法满⾜复杂路由请求的话,可以使用re_path完成路由映射,re_path中模式包含了⼀个上尖括号(^)和⼀个美元符号($)。这些都是正则符号,上尖括号表示从字符串开头匹配。美元符号表示匹配字符串的结尾,这两个符号和到⼀起就表示模式必须完全匹配路径,行不是包含⼀部分。⽐如对于模式:

r'^hello/$'如果不包含美元符,也就是r'^hello/',任何以/hello/的url都可以匹配,例如:/hello/world/、/hello/1/2/等,同样如果不以上尖括号开头,则任何以hello/做结尾的url都可以匹配。

Django检查url模式之前会移除模式前的/,所以url模式前⾯的/可以不写,但如果在地址栏⾥请求的时候不带尾斜杠,则会引起重定向,重定向到带尾斜杠的地址, 所以请求的时候要带尾斜杠

模式匹配的时候要注意:

  • Django从上往下进行模式匹配,一旦匹配成功就不会往下继续匹配了
  • ⼀个视图函数可以有多个模式匹配
  • 模式前⾯不需要加/
  • 如果匹配不上,则会引起异常,Django会调用错误处理视图处理(关闭调试模式)

URL配置

在setting中指定根级url配置⽂件,对应的属性ROOT_URLCONF

  1. urlpatterns:⼀个url实例的列表,全在根配置中搞定

  2. 导⼊其它url配置: 在应用中创建urls.py⽂件,编写匹配规则,在⼯程urls.py中进行导⼊包含

    1
    2
    from django.urls import include
    urlpatterns = [ path('xxx/',include('App.urls')) ]

Django的请求处理流程:

  • Django获取用户发出请求

  • Django在⼯程配置⽂件settings.py中通过ROOT URLCOF配置来确定根URLconf

  • Django在URLconf中的所有URL模式中,查找第⼀个配的条⽬。

  • 如果找到匹配,将调用相应的视图函数,并将HttpReuest对象传⼊

  • 视图函数回⼀个HttpResponse响应

动态url

前⾯写的url都是静态的,⽆法给视图函数传递数据,要想给视图函数传参数,url必须是动态,带有参数,但Django主张漂亮简介的url,所以不会使用查询字符串(/hello?name=tom),行是采用通配符:

1
2
3
4
path("hello/<name>/",views.hello)
path("show/<name>/<int:age>",views.show)
re_path(r'^hello/(\w+)/$',views.hello)
#http://localhost:8000/hello/tom/

在path中使用<参数名>表示所传参数,视图函数中的参数名必须和<>中参数名⼀致。参数可以是以下类型:

  • str:如果没有指定参数类型,默认是字符串类型。字符串参数可以匹配除/和空字符外的其他字符串
  • int:匹配0和正整数,视图函数的参数将得到⼀个整型值
  • slug:匹配由数字、字⺟、-和_组成的字符串参数
  • path:匹配任何⾮空字符串,包括/。

re_path中,()部分是正则的组, django在进行url匹配时,就会⾃动把匹配成功的内容,作为参数传递给视图函数

  • 位置参数:url中的正则表达式组,和视图函数中的参数⼀⼀对应,函数中的参数名可以随意指定。

    1
    2
    3
    4
    5
    #位置参数:hello/name/age
    re_path(r'^hello/(\w+)/(\d{1,2})/$',views.hello)
    #视图函数
    def hello(req,value1,value2): #参数名字随意,但从左往右和url中分组对应
    return HttpResponse("哈喽:" + value1 + " age= {}".format(value2))
  • 关键字参数:对正则表达式分组进行命名

    1
    2
    3
    4
    5
    ?P<组名>
    url(r'^hello/(?P<name>\w+)/(?P<age>\d{1,2})/$',views.hello),
    #视图函数
    def hello(req,name,age): #参数名字必须是name和age,和url中命名⼀致,但顺序随意
    return HttpResponse("哈喽:" + name + " age= {}".format(age))

匹配/分组算法:

  • 在⼀个匹配模式中要么使用命名分组,要么使用⽆命名分组,不能同时使用

  • 请求的URL被看做是⼀个普通的Python字符串, URLconf在其上查找并匹配。进行匹配时将不包括GET或POST请求方式的参数以及域名。换句话讲,对同⼀个URL的⽆论是POST请求、GET请求、或HEAD请求方法等等 —— 都将路由到相同的函数。

  • 每个捕获的参数都作为⼀个普通的Python字符串传递给视图,⽆论正则表达

    式使用的是什么匹配方式

  • 每个捕获的参数都作为⼀个普通的Python字符串传递给视图,⽆论正则表达式使用的是什么匹配方式

视图

视图本质上是⼀个函数(类)。这个函数第⼀个参数的类型是HttpReuest;它返回⼀个 HttpResponse实例。为了使⼀个Python的函数成为⼀个Django可识别的视图,它必须 ⾜这两个条件。

作用:接收并处理请求,调用模型和模板,响应请求(返回HttpResponse或其子类)

  • 响应模板
  • 重定向
  • 直接响应字符串
  • 响应错误模板
  • json数据

HttpRequest

HttpRequest是从web服务器传递过来的请求对象,经过Django框架封装产生的,封装了原始的Http请求

  • 服务器接收到http请求后,django框架会⾃动根据服务器传递的环境变量创建HttpRequest对象
  • 视图的第⼀个参数必须是HttpRequest类型的对象
  • 在django.http模块中定义了HttpRequest对象的API
  • 使用HttpRequest对象的不同属性值,可以获取请求中多种信息
属性 说明
content-type 请求的mime类型
GET ⼀个类似于字典的QueryDict对象,包含get请求⽅式的所有参 数,也就是“?”后⾯的内容
POST ⼀个类似于字典的QueryDict对象,包含post请求方式的所有参 数
COOKIES ⼀个标准的Python字典,包含所有的cookie,键和值都为字符串
SESSION ⼀个类似于字典的对象,表示当前的会话,只有当Django启⽤会 话的⽀持时才可⽤
PATH ⼀个字符串,表示请求的⻚⾯的完整路径,不包含域名
method ⼀个字符串,表示请求使⽤的HTTP⽅法,常⽤值包括:GET、 POST,
FILES ⼀个类似于字典的QueryDict对象,包含所有的上传⽂件
META 请求的请求头的源信息(请求头中的键值对)
encoding 字符编码
scheme 协议

META中常用的键:

说明
HTTP_REFERER 来源⻚⾯
REMOTE_ADDR 客户端ip
REMOTE_HOST 客户端主机

下⾯是常用的方法

方法名 说明
get_host() 获取主机名+端⼝
get_full_path() 获取请求路径+查询字符串
is_ajax() 如果是ajax请求返回True
build_absolute_uri() 完整的url

QueryDict

QueryDict是Dict的子类,和Dict最⼤的不同是,值都是列表。用于存储从请求中传递过来的参数,例如对于表单中select、checkbox等多值参数,QueryDict⾮常合适。get、post、files请求都对应⼀个QueryDict

  • HttpRequest中QueryDict是不可变的,只能获取值,不能修改
  • QueryDict键值对都是字符串
  • QueryDict中⼀个键可以对应多个值
1
2
#QueryDict
{'hobby':['打篮球','玩游戏','k歌'],'name':['tom'],'age':'21'}

常用操作

  1. 判断get或post请求中包含指定键

    1
    2
    if 'name' in req.GET:
    pass
  2. 获取get或post请求中指定键对应的单⼀值

    1
    2
    3
    print(req.GET.get('name','⽆名'))
    print(req.GET['name'])
    # 两种方式的区别,如果用下标,键不存在,则包keyerror,get方法则会得到默认值,如果没有默认值则返回None
  3. 获取指定键对应的多个值(列表)

    1
    print(req.GET.getlist('name'))
  4. 获取所有键值对

    1
    2
    for x in req.GET.lists():
    print(x)

HttpResponse

每⼀个视图函数必须返回⼀个响应对象,HttResponse对象由程序员创建并返回。

属性 说明
content 字节字符串
charset 字符编码
status_code http状态码
content_type 指定输出的MIME类型

不调用模板,直接返回内容

1
2
3
4
5
6
7
8
def hello(req):
return HttpResponse("hello world")
def goodbye(req):
res = HttpResponse()
res.content = b'good bye'
res.charset = "utf-8"
res.content_type = 'text/html'
return res

调用模板返回

⼀般用render函数返回,render只是HttpResponse的包装,还是会返回⼀个HttpResponse对象

1
def render_to_response(template_name, context=None, content_type=None,status=None, using=None):
  • template_name : 模板名称。
  • context: ⼀组字典的值添加到模板中。默认情况下,这是⼀个空的字典。
  • content_type :MIME类型用于生成⽂档
  • status :为响应状态代码。默认值为200
1
2
3
4
5
def studentlist(req):
for key in req.GET.lists():
print(key)
allstudent = Student.objects.all()
return render(req,'studentlist.html',context={'data':allstudent})

常用方法:

  • write(content) 设置内容 == obj.content
  • set_cookie() 设置cookie
  • delete_cookie() 删除cookie

JsonResponse

JsonResponse 是HttpResponse的子类,用于向客户端返回json数据。⼀般用于ajax请求。它的content-type缺省值(默认值)为:application/json

1
2
from django.http import JsonResponse #导包
class JsonResponse(data, encoder=DjangoJSONEncoder, safe=True,json_dumps_params=None, **kwargs)

参数:

  • data:可以是字典(或者内置对象),如果safe设置为False,也可以是json序列化对象
  • encoder:编码格式,缺省是django.core.serializers.json.DjangoJSONEncoder
  • safe:默认是True,如果传递非字典给data,则报TypeError

系统模块⾃带的json和JsonResponse

1
2
3
4
5
6
7
from django.http import JsonResponse
import json as myJson
#返回json数据
def json(req):
jsonStr = JsonResponse({'name':'zhangsan','age':18})
jsonStr = myJson.dumps({'name':'zhangsan','age':18})
return HttpResponse(jsonStr)

json.dumps给ajax返回数据以后 js代码中需要将json数据使用Json.parse()进行解析成js对象 而JsonResponse会⾃动转换为js对象(少了异步Json.parse()的转换)

QuerySet转Json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def courselist(req):
courses = Course.objects.all() #QuerySet
data = {'course':list(courses.values())} #查询结果集QuerySet转字典
return JsonResponse(data) #传字典给JsonResponse

# 返回数据格式
{'course': [{'cname': '计算机组成原理', 'cid': 1}, {'cname': '英语','cid': 2}, {'cname': '数学', 'cidame': 'c语⾔程序设计', 'cid': 4},{'cname': 'python从⼊⻔到精通', 'cid': 5}]}

def courselist(req):
courses = Course.objects.all()
data = json.dumps(list(courses.values())) #查询结果集QuerySet转Json字符串,序列化
return JsonResponse(data,safe=False) #safe必须设置为False

# 返回的数据格式
[{"cname": "\u8ba1\u7b97\u673a\u7ec4\u6210\u539f\u7406", "cid": 1},{"cname": "\u82f1\u8bed", "cid": 2}, {"cname": "\u6570\u5b66", "cid":3},{"cname":"c\u8bed\u8a00\u7a0b\u5e8f\u8bbe\u8ba1", "cid": 4},{"cname": "python\u4ece\u5165\u95e8\u5230\u7cbe\u901a", "cid": 5}]

模型转Json

1
2
3
4
5
6
7
8
9
10
11
class Course(models.Model):
cname = models.CharField(max_length=50)
cid = models.AutoField(primary_key=True)
# 给模型添加to_dict方法
def to_dict(self):
return dict([(key, self.__dict__[key]) for key in self.__dict__.keys() if key !="_state"])

# 视图函数
def courseshow(req):
course = Course.objects.get(pk=1) # 返回的是对象,不是QuerySet
return JsonResponse(course.to_dict())

重定向

当浏览器向server发送⼀个请求,要求获取⼀个资源时,在server接收到这个请求后发现请求的这个资源实际存放在另⼀个位置,于是server在返回的response中写⼊那个请求资源的正确的URL,并设置reponse的状态码为301(表示这是⼀个要求浏览器重定向的response),当client接受到这个response后就会根据新的URL重新发起请求。重定向有⼀个典型的特症,即:当⼀个请求被重定向以后,最终浏览器上显示的URL往往不再是开始时请求的那个URL了。这就是重定向的由来。

HttpResponseRedirect是HttpResponse的子类,可以完成⻚⾯的重定向,当执行完特定动作或出现错误时,我们会希望执行的⻚⾯,如果判定用户没有登录则转到登录⻚⾯。重定向可以使用

  • HttpResponseRedirect
  • redirect(是HttpResponseRedirect的简写)

HttpResponseRedirect只⽀持硬编码url,不能直接使用命名url,在使用URL命名时,我们需要先通过URL反向解析方法reverse先对命名URL进行解析,然后再使用HttpReponseRedirect定向。

不带参数重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#urls.py
urlpatterns = [
path('',views.index),
re_path(r'^list/$',views.list_user),
#redirect 重定向路由地址
url(r'^redirect/$',views.Redirect)
]

#views.py
def index(req):
return render(req,'index.html')
def list_user():
return HttpResponse("list user")
def Redirect(req):
# return redirect('http://localhost:9000/')
# return redirect('/')
return redirect("list/")

带参数重定向

1
2
3
4
5
6
7
8
9
10
11
#urls.py
path('repath/<int:num>/', views.repath),
re_path(r'^parameter/(?P<num1>\d+)/(?P<num2>\d+)/$',views.parameter),

#views.py
def repath(req,num):
return redirect("parameter/3/5")
def index(request):
return HttpResponse("⾸⻚")
def parameter(req,num1, num2):
return HttpResponse("num1={},num2={}".format(num1,num2)

反向解析

根据namespace 和 name 查找真实路径:

  • namespace 在根url 包含子url时指定
  • name 在具体的函数后 指定,子url指定
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
# urls.py中
app_name = 'App' # namespace 命名空间
urlpatterns = [
path('', views.index),
path('para/<int:num1>/<int:num2>/'),
]


#python代码中写法
def repath(req):
#res = reverse("App:index")
# print(res,type(res))
#return redirect(res)
# path中参数或re_path中的位置参数,args可以是列表或元组
# return redirect(reverse("App:para",args=(1,2)))
# re_path中命名组,带关键字参数
return redirect(reverse("App:para",kwargs={'num1':1,'num2':3}))


# 模板中的写法:
<h2><a href="/form/">展示表单</a></h2>
<h2><a href="/index/">死链接⽆参的跳转</a></h2>
<h2><a href="/args/1/2/">死链接带参的跳转</a></h2>
<h2><a href="{% url 'App:index' %}">动态生成路由地址不带参的跳转</a></h2>
<h2><a href="{% url 'App:args1' 1 2 %}">动态生成路由地址带参的跳转</a></h2>
<h2><a href="{% url 'App:args1' num1=1 num2=2 %}">re_path中命名组,动态生成路由地址带关键字参数的跳转</a></h2>

错误视图

Django内置了处理HTTP错误的视图(在django.views.defaults包下),主要错误及视图包括:

  • 403错误:permission_denied (权限拒绝)
  • 404错误:page_not_found (找不到指定⽂件)
  • 500错误:server_error (服务器内部错误)

404错误及视图

url匹配失败后,django会调用内置的 django.views.defaults.page_not_found()函数,该视图函数会调用 404.html的模板进行显示。开发阶段可以开启调试模式,但产品上线后,要关闭调试模式。关闭调试模式后,会显示⼀个标准的错误⻚⾯。

1
2
3
# 在项⽬的settings配置⽂件中设置
# 关闭调试模式(开发模式)
DEBUG = False

404错误界⾯可以⾃定义: 在项⽬templates⽬录⾯创建404.html,django找不到界⾯时,就会显示该界⾯了。缺省会传递参数request_path,就是出错的url

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404 Not Found</title>
</head>
<body>
<h2>对不起 您所访问的地址被外星⼈抓⾛了....</h2>
<h4>请求的地址为 {{ request_path }}</h4>
</body>
</html>

500错误及视图

若是在执行视图函数时出现运行时错误,Django会默认会调用django.views.defaults.server_error 视图,加载并显示 500.html 模板,可以在项⽬的templates⽬录下,创建500.html来⾃定义该界面

Django分页

Paginator 分页器

Paginator用于分页,但Paginator并不具体管理具体的页的处理,而是使用Page对象管理具体页⾯

  1. 创建分页器对象

    格式: Paginator(<query_set查询集>,每也显示数据的条数)

  2. 对象的属性

    • count 分页对象的个数

    • num_pages 总页数

    • page_range 页码的列表

    • per_page每页显示的条数

  3. 方法

    • page(num)返回page对象 如果给定的页码不存在 则抛出异常

page 对象

page对象具体负责每页的处理,包括每页的数据,当前页的页码,是否有上⼀页或下⼀页等

类别 名称 说明
属性 object_list 当前页面上的所有数据
属性 number 当前页码值
属性 paginator 返回Paginator对象
方法 has_next 是否有下一页
方法 has_previous 是否有上一页
方法 has_other_pages 是否有上一页或下一页
方法 next_page_number 返回下一页的页码
方法 previous_page_number 返回上⼀页的页码
方法 len 返回当前页数据的个数

实例:

步骤1. models.py中添加模型

1
2
3
4
5
6
7
8
9
class User(models.Model):
uid = models.AutoField(primary_key=True)
username = models.CharField(unique=True, max_length=30)
password = models.CharField(max_length=128)
regtime = models.DateTimeField()
sex = models.IntegerField(blank=True, null=True)

class Meta:
db_table = 'user'

步骤2. 在应用App中urls.py中添加路由

1
2
3
4
5
6
7
8
from App02 import views
app_name = 'App02'
urlpatterns = [
# 分页
path('page/',views.fenye, name='page'),
path('page/<int:page>/',views.fenye, name='page'),

]

步骤3. 在views.py中添加视图

1
2
3
4
5
6
7
8
9
10
def fenye(request,page=1):
users = User.objects.all()
# 产生分页器 参数(<query_set查询集>,每⻚显示数据的条数)
paginator = Paginator(users,3)
# 分页对象
# page表示当前页
pager = paginator.page(page)
for user in paginator.object_list:
print(user,type(user))
return render(request,"userlist.html",locals())

步骤4. 在userlist.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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1" cellspacing="0" width="80%">
<tr>
<td>用户名</td>
<td>密码</td>
</tr>
{% for user in pager.object_list %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.password }}</td>
</tr>
{% endfor %}
</table>
<div>
{# paginator.page_range 页码列表 #}
{% for page in paginator.page_range %}
<a href="{% url 'App02:page' page=page %}">{{ page }}</a>
{% endfor %}
</div>
</body>
</html>

Django中Cookie和Session

HTTP被设计为”无状态”,也就是俗称“脸盲”。 这⼀次请求和下⼀次请求之间没有任何状态保持,我们无法根据请求的任何方面(IP地址,⽤户代理等)来识别来⾃同一人的连续请求。实现状态保持的方式:在客户端或服务器端存储与会话有关的数据 (客户端与服务器端的⼀次通信,就是⼀次会话)

  • cookie
  • session

不同的请求者之间不会共享这些数据,cookie和session与请求者一一对应

cookies 是浏览器为 Web 服务器存的一小段信息。 每次浏览器从某个服务器请求页面时,都会自动带上以前收到的cookie。cookie保存在客户端,安全性较差,注意不要保存敏感信息。典型应用:

  • 网站登录
  • 购物车

设置cookie

1
HttpResponse.set_cookie(key, value='', max_age=None, expires=None,path='/', domain=None, secure=None, httponly=False)

参数:

  • key: cookie的名称(*)
  • value: cookie的值,默认是空字符
  • max_age:cookies的持续有效时间(以秒计),如果设置为 None,cookies 在浏览器关闭的时候就失效了。
  • expires:cookies的过期时间,格式:”Wdy, DD-Mth-YY HH:MM:SS GMT” 如果设置这个参数,它将覆盖max_age。
  • path: cookie⽣效的路径前缀,浏览器只会把cookie回传给带有该路径的页面,这样你可以避免将cookie传给站点中的其他的应⽤。/ 表示根路径,特殊的:根路径的cookie可以被任何url的⻚⾯访问
  • domain: cookie⽣效的站点。你可⽤这个参数来构造⼀个跨站cookie。如,domain=”.example.com” 所构造的cookie对下⾯这些站点都是可读的: www.example.com 、www2.example.com。如果该参数设置为None,cookie只能由设置它的站点读取。
  • secure: 如果设置为 True ,浏览器将通过HTTPS来回传cookie。
  • httponly: 仅http传输 不能使⽤js获取cookie

set_cookie,不同点在于设置salt,即加盐,加密存储cookie数据

HttpResponse.set_signed_cookie(key, value, salt=’’, max_age=None,expires=None, path=’/‘, domain=None, secure=None, httponly=False)

获取cookie

1
2
3
HttpRequest.COOKIES.get(key)
# 获取加“盐”的cookie
HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='',max_age=None)

删除cookie

1
HttpResponse.delete_cookie(key, path='/', domain=None)

Session

cookie看似解决了HTTP(短连接、⽆状态)的会话保持问题,但把全部⽤户数据保存在客户端,存在安全隐患,于是session出现了。我们可以把关于用户的数据保存在服务端,在客户端cookie里加⼀个sessionID(随机字符串)。其⼯作流程:

  1. 当用户来访问服务端时,服务端会生成⼀个随机字符串;

  2. 当用户登录成功后 把 {sessionID :随机字符串} 组织成键值对加到cookie里发送给用户;

  3. 服务器以发送给客户端 cookie中的随机字符串做键,⽤户信息做值,保存用户信息

  4. 再访问服务器时客户端会带上sessionid,服务器根据sessionid来确认用户是否访问过网站

session配置

步骤1. 首先在settings.py中有如下配置(系统默认已经设置了

1
2
3
4
5
6
INSTALLED_APPS = [
'django.contrib.sessions',
]
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
]

步骤2. 进行数据迁移,⽣成session使⽤的数据库表

session操作

session设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def doregister(request):
username = request.POST.get('username')
password = request.POST.get('password')
email = request.POST.get('email')
user = User()
user.username = username
user.password = md5(password.encode('utf8')).hexdigest()
user.email = email
user.save()
# 设置session
request.session['username'] = username

return render(request,"common/notice.html",context={
'code':1,
'msg':'注册成功',
'url':'three:index',
'wait':3
})

session获取

1
2
3
4
def index(request):
# session获取
username = request.session.get('username')
return render(request,'three/index.html',context={'username':username})

session删除

1
2
3
def logout(request):
request.session.flush()
return redirect(reverse("three:index"))
  • clear() 清空所有session 但是不会将session表中的数据删除

  • flush() 清空所有 并删除表中的数据

  • logout() 退出登录 清除所有 并删除表中的数据

  • del req.session[‘key’] 删除某⼀个session的值

session过期时间

1
req.session.set_expiry(5)

cookie和session的区别与联系

区别:

  • session将数据存储与服务器端 cookie存储在客户端
  • cookie 存储在客户端,不安全,session存储在服务器端,客户端只存sesseionid,安全
  • cookie在客户端存储值有⼤⼩的限制,⼤约⼏kb。session没有限制

联系:

  • session 基于cookie

Django 使用阿里云短信服务

短信服务的使用场景:

  • APP、网站注册账号,向手机下发验证码;
  • 登录账户、异地登录时的安全提醒;
  • 找回密码时的安全验证;
  • ⽀付认证、身份校验、⼿机绑定等。

步骤1. 安装阿里云SDK核心库

1
pip install aliyun-python-sdk-core-v3

发送步骤:

  1. 创建Client实例。在创建Client实例时,您需要获取Region ID、AccessKey ID和AccessKey Secret

  2. 创建API请求并设置参数

  3. 发起请求并处理应答或异常

工具类tool.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
from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest

ACCESS_KEY_ID = "LTAIDHOYSjYcvyVt" #用户AccessKey 需要根据自己的账户修改
ACCESS_KEY_SECRET = "qrEgykmXX4e6GUMFOqzuiLZ5gsUxSC" #Access Key Secret 需要根据自己的账户修改

class SMS:
def __init__(self,signName,templateCode):
self.signName = signName #签名
self.templateCode = templateCode #模板code
self.client = client = AcsClient(ACCESS_KEY_ID, ACCESS_KEY_SECRET, 'cn-hangzhou')

def send(self,phone_numbers,template_param):
request = CommonRequest()
request.set_accept_format('json')
request.set_domain('dysmsapi.aliyuncs.com')
request.set_method('POST')
request.set_protocol_type('https') # https | http
request.set_version('2017-05-25')
request.set_action_name('SendSms')

request.add_query_param('RegionId', "cn-hangzhou")
request.add_query_param('PhoneNumbers', phone_numbers)
request.add_query_param('SignName', self.signName)
request.add_query_param('TemplateCode', self.templateCode)
request.add_query_param('TemplateParam', template_param)
response = self.client.do_action_with_exception(request)
return response
# 短语发送对象 需要根据自己的签名和模板代码
sms = SMS("成少雷","SMS_102315005")

views.py视图中调用

1
2
3
4
5
6
7
8
9
def send_sms(request):
from App02.SMS import sms
# 模板参数一定要是这个格式
# 一定要注意模板变量number
para = "{'number':%d}"%(randint(1000,100000))
print(para)
res = sms.send('15116905290',para)
print(res.decode("utf-8"))
return HttpResponse("ok")

Django From表单验证

概要

通常情况下,我们需要真的手动在HTML页面中,编写form标签和其内的其它元素。但这费时费⼒,行且有可能写得不太恰当,数据验证也⽐较麻烦。有鉴于此, Django在内部集成了⼀个表单模块,专门帮助我们快速处理表单相关的内容。Django的表单模块给我们提供了下面三个主要功能:

  • 准备和重构数据⽤于⻚⾯渲染
  • 为数据创建HTML表单元素
  • 接收和处理用户从表单发送过来的数据

Form相关的对象包括

  • Widget:⽤来渲染成HTML元素的部件,如:forms.Textarea对应HTML中的 <textarea> 标签

  • Field:Form对象中的⼀个字段,如:EmailField表示email字段,如果这个字段不是有效的Email地址格式,就会产⽣错误。

  • Form:⼀系列Field对象的集合,负责验证和显示HTML元素

  • Form Media:⽤来渲染表单的CSS和JavaScript资源。

基本使用

Form对象封装了⼀系列Field和验证规则,Form类都必须直接或间接继承⾃django.forms.Form,定义Form有两种方式:

  1. 直接继承Form

    1
    2
    class XXXForm(forms.Form):
    pass
  2. 结合Model,继承django.forms.ModelForm

    1
    2
    3
    4
    5
    6
    7
    class XXX(models.Model):
    字段 = models.CharField(max_length=30)
    字段 = models.CharField(max_length=20)
    class XXXForm(ModelForm):
    class Meta:
    model = XXX
    field = ('字段', '字段') # 只显示model中指定的字段,显示所有是⽤'__all__'

示例代码:

需求: 注册验证

步骤1. models

1
2
3
4
5
6
7
8
9
class User(models.Model):
uid = models.AutoField(primary_key=True)
username = models.CharField(unique=True, max_length=30)
password = models.CharField(max_length=128)
regtime = models.DateTimeField()
sex = models.IntegerField(blank=True, null=True)

class Meta:
db_table = 'user'

步骤2. RegisterForm

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
class RegisterForm(forms.Form):

username = forms.CharField(min_length=3,required=True,error_messages={
'required':'用户名必须输入',
'min_length':'用户名至少3个字符'
})
password = forms.CharField(min_length=3,required=True,error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
confirm = forms.CharField(min_length=3,required=True,error_messages={
'required': '密码名必须输入',
'min_length': '密码至少3个字符'
})
regtime = forms.DateTimeField(required=False,error_messages={
'invalid':'日期格式错误',
})
sex = forms.BooleanField(required=False)

# 单个字段验证: clean_xxxx 密码不能是纯数字
def clean_password(self):
password = self.cleaned_data.get('password')
if password and password.isdigit():
raise ValidationError("密码不能是纯数字")
return password


# 全局验证 两次密码输入必须一致
def clean(self):
password = self.cleaned_data.get('password',None)
confirm = self.cleaned_data.get('confirm','')
print(password,confirm)
if password != confirm:
raise ValidationError({'confirm':"两次密码输入不一致"})
return self.cleaned_data

步骤3. views.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
# From表单模块
def register(request):
if request.method == "POST":
#1.用提交的数据生成表单
form =RegisterForm(request.POST)
#2.能通过验证,返回True,否则返回False
if form.is_valid():
#3.进行业务处理
data = form.cleaned_data # 结果是一个字典
# 如果forms中表单的字段名和models模型的字段名不一致,可以把不一致的属性去除,在使用 # User.objects.create添加到数据库
data.pop("confirm")
#4.获取指定字段
# username = form.cleaned_data.get('username','')
# print(data,type(data))
# 如果forms中表单的字段名和models模型的字段名一致
res = User.objects.create(**data)
# 如果forms中表单的字段名和models模型的字段名不一致
# res = User.objects.create(username=username,password=form.cleaned_data.get('password'))
if res:
return HttpResponse("注册成功")
else:
print(form.__dict__)
# 验证不成功,把错误信息渲染到前端页面
return render(request, "register.html",{'form':form})
return render(request,"register.html")

步骤4. 在模板templatesregister.html使用

建议:尽量使用自己定义的文本框信息,等于Form表单对象只渲染错误信息,不使用渲染的表单信息

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>注册</title>
</head>
<body>
<form action="{% url 'App02:user_register' %}" method="post">
{% csrf_token %}
用户名:<input type="text" name="username">
{% for error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %}
<br>
密码:<input type="password" name="password">
{{ form.password.errors }}
<br>
确认密码:<input type="password" name="confirm">
{{ form.confirm.errors }}
<br>
注册时间:<input type="text" name="regtime">
{{ form.regtime.errors }}
<br>
性别:<input type="radio" name="sex" value="0">
<input type="radio" name="sex" value="1">
{{ form.sex.errors }}
<br>
<input type="submit" value="注册">
</form>
</body>
</html><form action="{% url 'add' %}" method="post">
{% csrf_token %}
{{ shop_form }}
<input type="submit" value="提交"/>
</form>

常用的field类

核心字段参数

参数名 说明
require 给字段添加必填属性,不能空着,若要表示⼀个字段不是必需的,设置required=False
label label参数⽤来给字段添加‘⼈类友好’的提示信息。如果没有设置这个参数,那么就⽤字段的⾸字⺟⼤写名字
label_suffix Django默认为上⾯的label参数后⾯加个冒号后缀,如果想⾃定义,可以使⽤ label_suffix 参数
initial 为HTML⻚⾯中表单元素定义初始值。也就是input元素的value参数的值
widget 指定渲染Widget时使⽤的widget类,也就是这个form字段在HTML⻚⾯中是显示为⽂本输⼊框?密码输⼊框?单选按钮?多选框?还是别的
help_text 该参数⽤于设置字段的辅助描述文本
error_messages 该参数允许你覆盖字段引发异常时的默认信息。 传递的是⼀个字典,其值为你想覆盖的错误信息
validators 指定⼀个列表,其中包含了为字段进⾏验证的函数
localize localize参数帮助实现表单数据输⼊的本地化。
disabled 设置有该属性的字段在前端⻚⾯中将显示为不可编辑状态

核心字段

BooleanField

1
2
3
4
默认的Widget:CheckboxInput
空值:False
规范:Python 的True 或 False。
错误信息的键:required

IntegerField

1
2
3
4
5
6
默认的Widget:当Field.localize是False时为NumberInput,否则
为TextInput。
空值:None
规范化为:Python 整数或⻓整数。
验证给定值是⼀个整数。 允许前导和尾随空格,类似Python的int()函数。
错误信息的键:max_value, invalid, required, min_value

CharField

1
2
3
4
5
默认的Widget:TextInput 
空值:''(⼀个空字符串)
规范化为:⼀个Unicode 对象。 如果提供,验证max_length 或
min_length。 否则,所有的输⼊都是合法的。
错误信息的键:required, max_length, min_length

ChoiceField

1
2
3
4
5
6
7
默认的Widget:Select
空值:' '(⼀个空字符串)
规范化为:⼀个Unicode 对象。 验证给定的值在选项列表中存在。
错误信息的键:required, invalid_choice
invalid_choice:错误消息可能包含%(value)s,它将被选择的选项
替换掉。接收⼀个额外的必选参数:choices⽤来作为该字段选项的
⼀个⼆元组组成的可迭代对象(例如,列表或元组)或者⼀个可调⽤对

DateField

1
2
3
4
5
6
7
8
默认的Widget:DateInput 
空值:None
规范化为:⼀个Python datetime.date 对象。
错误信息的键:required, invalid
input_formats:⼀个格式的列表,验证给出的值是⼀个
datetime.date、datetime.datetime 或指定⽇期格式的字符串。如
果不提供,默认的⽇期格式:['%Y-%m-
%d','%m/%d/%Y','%m/%d/%y']

DateTimeField

1
2
3
4
5
6
7
8
默认的Widget: DateInput 
空值:None
规范化为:⼀个Python datetime.datetime对象。
错误信息的键:required, invalid
input_formats:默认的格式['%Y-%m-%d %H:%M:%S', '%Y-%m-
%d %H:%M', '%Y-%m-%d', '%m/%d/%Y %H:%M:%S','%m/%d/%Y
%H:%M','%m/%d/%Y', '%m/%d/%y %H:%M:%S', '%m/%d/%y
%H:%M', '%m/%d/%y']

DecimalField

1
2
3
4
5
6
7
8
9
10
默认的Widget:当Field.localize是False时为NumberInput,否则
为TextInput。
空值:None
规范化为:Python decimal对象。
错误信息的键: max_whole_digits , max_digits ,
max_decimal_places , max_value , invalid, required, min_value
可选参数:max_value,min_value:允许的值的范围,需要赋值
decimal.Decimal对象,不能直接给个整数类型。
max_digits:值允许的最⼤位数(⼩数点之前和之后的数字总共的位数,前导的零将被删除)。
decimal_places:允许的最⼤⼩数位。

FloatField

1
2
3
4
5
6
7
默认的Widget:当Field.localize是False时为NumberInput,否则
为TextInput。
空值:None
规范化为:Float 对象。
验证给定的值是⼀个浮点数。
错误信息的键:max_value, invalid, required, min_value
可选的参数:max_value和min_value

EmailField

1
2
3
4
5
6
默认的Widget:EmailInput 
空值:''(⼀个空字符串)
规范化为:Unicode 对象。 使⽤正则表达式验证给出的值是⼀个合
法的邮件地址。
错误信息的键:required, invalid
两个可选的参数⽤于验证,max_length 和min_length。

ImageField

1
2
3
4
5
6
7
默认Widget: ClearableFileInput 
空值: None
规范化为: UploadedFile 将⽂件内容和⽂件名称封装到单个对象
中的对象。
验证⽂件数据是否已绑定到表单,并且该⽂件是Pillow可以理解的
图像格式。
错误信息键: required , invalid , missing , empty ,invalid_image

FileField

1
2
3
4
5
6
7
默认Widget: ClearableFileInput 
空值: None
规范化为: UploadedFile 将⽂件内容和⽂件名称封装到单个对象
中的对象。
可以验证⾮空⽂件数据已被绑定到表单。
错误信息键: required , invalid , missing , empty ,
max_length

ModelChoiceField

1
2
3
4
5
6
7
8
9
默认的Widget:Select 
空值:None
规范化为:⼀个模型实例。
验证给定的id存在于查询集中。
错误信息的键:required, invalid_choice 可以选择⼀个单独的模型
对像,适⽤于表示⼀个外键字段。 ModelChoiceField默认widet不
适⽤选择数量很⼤的情况,在⼤于100项时应该避免使⽤它。
可选参数:
empty_label:默认情况下,ModelChoiceField使⽤的

Form常用的属性和方法

名称 说明 示例
cleaned_data(字典) 表单中验证通过的⼲净数据 form.cleaned_data form.cleaned_data.get(‘username’)
changed_data 有变化的字段的列表 form.changed_data
fields(字典) 表单中的字段属性 form.fiedls[‘username’]
is_bound 表单是否绑定数据 form.is_bound
errors(字典) 错误信息 form.errors
is_valid() 表单中数据是否验证通过,通过返回True,否则返回False form.is_valid()
has_changed() 检查表单数据是否已从初始数据更改 form.has_changed()
errors.as_json(escape_html=False) 返回JSON序列化后的错误信息字典 form.errors.as_json()

表单渲染的选项

对于

1
2
3
1. {{ form.as_table }} 以表格的形式将它们渲染在 <tr> 标签中
2. {{ form.as_p }} 将它们渲染在 <p> 标签中
3. {{ form.as_ul }} 将它们渲染在 <li> 标签中

注意,你必须自己提供

    元素。

    常⽤渲染项:

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
有⽤的属性包括:{{ field }}
{{ field.label }}
该领域的标签,例如。Email address
{{ field.label_tag }}
字段的标签包含在适当的HTML <label>标记中。这包括表格label_suffix。例
如,默认label_suffix值为冒号:
<label for="id_email">Email address:</label>
{{ field.id_for_label }}
将⽤于此字段的ID(id_email在上⾯的示例中)。如果您⼿动构建标签,则可能
需要使⽤此代替label_tag。例如,如果你有⼀些内联JavaScript并且想要避免
硬编码字段的ID,它也很有⽤。
{{ field.value }}
该字段的值。例如someone@example.com。
{{ field.html_name }}
将在输⼊元素的名称字段中使⽤的字段的名称。这会将表单前缀考虑在内,如果已
设置的话。
{{ field.help_text }}
与该字段关联的任何帮助⽂本。
{{ field.errors }}
输出包含与此字段对应的任何验证错误的a 。您可以使⽤循环⾃定义错误的表示。
在这种情况下,循环中的每个对象都是包含错误消息的简单字符串。<ul
class="errorlist">{% for error in field.errors %}
{{ field.is_hidden }}
True如果表单字段是隐藏字段, False则此属性。它作为模板变量并不是特别有
⽤,但在条件测试中可能很有⽤,例如:
{% if field.is_hidden %}
{# Do something special #}
{% endif %}
{{ field.field }}
Field来⾃此BoundField包装的表单类的实例。您可以使⽤它来访问 Field属
性,例如 。{{ char_field.field.max_length }}

form.as_ul

1
2
3
4
5
6
7
<form action="/add/" method="post">
{% csrf_token %}
<ul>
{{ form.as_ul }}
</ul>
<input type="submit" value="注册⼀个学⽣">
</form>

手动渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<form action="/add/" method="post">
{% csrf_token %}
<div>
<label for="{{ form.name.id_for_label }}">姓名:</label>
{{ form.name }}
{{ form.name.errors }}
</div>
<div>
<label for="{{ form.sex.id_for_label }}">性别:</label>
{{ form.sex }}
{{ form.sex.errors }}
</div>
<div>
<label for="{{ form.age.id_for_label }}">年龄:</label>
{{ form.age }}
{{ form.age.errors }}
</div>
<input type="submit">
</form>

循环渲染

1
2
3
4
5
6
7
8
9
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}

循环隐藏和可⻅字段

1
2
3
4
5
6
7
8
9
10
11
{# Include the hidden fields #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{# Include the visible fields #}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}

可重⽤的表单模板

1
2
3
4
5
6
7
8
9
# In your form template:
{% include "form_snippet.html" %}
# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}

Django 用户认证

概要

auth模块是Django提供的标准权限管理系统,可以提供用户身份认证, 用户组和权限管理。auth可以和admin模块配合使用, 快速建立网站的管理系统。在INSTALLED_APPS中添加’django.contrib.auth’使⽤该APP, auth模块默认启⽤。主要的操作包括:

  • create_user 创建用户
  • authenticate 验证登录
  • login 记住⽤户的登录状态
  • logout退出登录
  • is_authenticated 判断⽤户是否登录
  • @login_required 判断⽤户是否登录的装饰器

前期配置

说明

Django 在新建⼯程时已经为使⽤⽤户认证系统做好了全部必要的配置。不过有可能你并⾮使⽤ django-admin 命令新建的⼯程,或者你使⽤的是⼀个正在开发中的项⽬,因此最好再检查⼀下 settings.py ⽂件中是否已经做好了全部必要配置。

配置

  1. 在setting.py的INSTALLED_APPS

    1
    2
    3
    4
    5
    INSTALLED_APPS = [
    'django.contrib.auth',
    # 用户权限处理部分依赖的应⽤
    'django.contrib.contenttypes',
    ]
  2. 在setting.py的MIDDLEWARE

    1
    2
    3
    4
    5
    6
    MIDDLEWARE = [
    # 会话⽀持中间件
    'django.contrib.sessions.middleware.SessionMiddleware',
    # 认证⽀持中间件
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ]

user对象

属性

属性 说明 备注
username 小于等于30个字符。 ⽤户名可以包含字⺟、 数字、_、@、+、.和- 字符 必选
password 密码的哈希及元数据。(Django 不保存原始密码)。原始密码可以⽆限⻓⽽且可以包含任意字符。参⻅密码相关的⽂档 必选
is_active 布尔值。指示⽤户的账号是否激活,缺省值为True 必选
first_name 少于等于30个字符 可选
last_name 少于30个字符 可选
email 邮箱地址 可选
groups 与Group 之间的多对多关系 可选
user_permissions 与Permission之间的多对多关系 可选
is_staff 布尔值,指示⽤户是否可以访问Admin 站点 可选
is_superuser 布尔值。只是这个⽤户拥有所有的权限⽽不需要给他们分配明确的权限。 可选
last_login ⽤户最后⼀次登录的时间 默认值
date_joined 账户创建的时间。当账号创建时,默认设置为当前的date/time 默认值

说明

  • User 对象属性:username, password(必填项)password用哈希算法保存到数据库
  • is_staff: 用户是否拥有网站的管理权限.
  • is_active : 是否允许用户登录, 设置为 False ,可以不用删除⽤户来禁止用户登录

拓展 User 模型

说明

用户可能还包含有头像、昵称、介绍等等其它属性,因此仅仅使⽤ Django 内置的 User 模型是不够。所有有些时候我们必须使⽤在系统的User上进⾏拓展

继承AbstractUser

说明

推荐方式、django.contrib.auth.models.User 也是继承⾃ AbstractUser 抽象基类,而且仅仅就是继承了 AbstractUser ,没有对 AbstractUser 做任何的拓展

在app的models.py中

1
2
3
4
class User(AbstractUser):
phone = models.CharField(max_length=12,null=True,verbose_name="⼿机号")
class Meta(AbstractUser.Meta):
db_table='user'

注意:

为了让 Django ⽤户认证系统使⽤我们自定义的用户模型,必须在 settings.py里通过 AUTH_USER_MODEL 指定⾃定义用户模型所在的位置

1
AUTH_USER_MODEL = 'app名字.User'

迁移

1
2
python manage.py makemigrations
python manage.py migrate

常用操作

验证登录

当用户登录的时候用 authenticate(username=username,password=password) 验证⽤户是否登录,如果数据库中存在用户输⼊的账号和密码,返回⼀个user对象,否则返回None。底层将password⽤hash算法加密后和数据库中password进⾏对比

示例代码

1
2
3
4
5
6
7
8
9
10
11
def myauthenticate(request):
pwd = request.POST.get("pwd", "")
u_name = request.POST.get("u_name", "")
if len(pwd) <= 0 or len(u_name) <= 0:
return HttpResponse("账号或密码不能为空")
else:
user = authenticate(username=u_name, password=pwd)
if user:
return HttpResponse("验证成功")
else:
return HttpResponse("账号或密码错误")

注册操作

当⽤户注册的时候⽤ create_user(username,password,email) 默认情况下is_active=True,is_staff=False,is_superuser=False 。底层将password⽤hash算法加密之后存储到数据库中

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def register_view(request):
if request.method == 'POST':
try:
username = request.POST.get('username')
password = request.POST.get('password')
phone = request.POST.get('phone')
email = request.POST.get('email')
# 验证⽤户是否存在
user = User.objects.filter(username=username).first()
if user:
# ⽤户已经存在
return render(request, 'register.html', {'msg': '⽤户名已存在'})
else:
# 保存⽤户
User.objects.create_user
(username=username,password=password,phone=phone,email=email)
except Exception as e:
return render(request, 'register.html', {'msg': '注册失败'})
else:
return render(request, 'register.html')

登录操作

当⽤户登录的时候⽤ login(request,user) 来记住⽤户的登录状态该函数接受⼀个HttpRequest对象,以及⼀个认证了的User对象此函数使⽤django的session框架给某个已认证的⽤户附加上session id等信息

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
def login_view(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 验证⽤户是否存在
user = authenticate(request, username=username,password=password)
if user:
login(request,user)
return redirect('/')
else:
return render(request, 'test/login.html', {'msg': '⽤户密码错误'})
else:
return render(request, 'login.html')

登出操作

当⽤户注销的时候⽤ logout(request) ,只需要⼀个参数request

示例代码

1
2
3
from django.contrib.auth import logout
def logout_view(request):
logout(request)

修改密码

第一种: 自动签名,使用set_password修改,底层最终还是使用make_password

1
2
3
4
user = auth.authenticate(username=username,password=old_password)
if user:
user.set_password(new_password)
user.save()

第二种: 手动签名,使用make_password

1
2
3
4
5
6
7
def test_pass(request):
password = make_password("123")
print(password)
# 加密后的password: pbkdf2_sha256$150000$3ZMHSagCWJLt$89MAwmlUIE8S+zi+Cpv13/ny/AyzI1nLTkfES6mGqhY=
# 比较明文密码和加密后的密码比较
print(check_password('123',password))
return HttpResponse("ok")

如果报错需要在setting.py中添加密码配置

1
2
3
4
5
6
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

路由保护

@login_required 修饰器修饰的view函数会先通过session key检查是否登录,已登录⽤户可以正常的执⾏操作, 未登录⽤户将被重定向到login_url指定的位置. 若未指定login_url参数, 则重定向到settings.LOGIN_URL

示例代码

1
2
3
4
5
6
7
8
9
10
# settings 配置
LOGIN_URL = '/day05/login/'
# views
@login_required
def find_user_info(request):
pass

@login_required(login_url='/day05/phone/login')
def find_user_info(request):
pass

业务中修改密码

1
2
3
4
5
6
def change_password(request):
# 修改密码
user = User.objects.get(pk=1)
user.set_password('123')
user.save()
return HttpResponse("修改密码")

前端验证登录

如果是真正的 User 对象,返回值恒为 True 。 ⽤于检查⽤户是否已经通过了认证。 通过认证并不意味着⽤户拥有任何权限,甚⾄也不检查该⽤户是否处于激活状态,这只是表明⽤户成功的通过了认证。 这个方法很重要, 在后台⽤request.user.is_authenticated()判断⽤户是否已经登录,如果true则可以向前台展示request.user.name

示例代码

1
2
3
4
# 在后台的视图函数⾥可以⽤request.user.is_authenticated()判断⽤户是否登录在前端页面中可以用
{% if user.is_authenticated %}
{%endif%}
# 判断用户是否登录

Django 图形验证码

安装django-simple-captcha库

在⽹站开发的登录⻚⾯中,经常会需要使⽤到图形验证码来验证。在Django中,django-simple-captcha库包提供了图形验证码的使⽤。

1
2
3
pip install django-simple-captcha
# 如果安装有依赖库问题,请执⾏下⾯的安装
apt-get -y install libz-dev libjpeg-dev libfreetype6-dev python-dev

setting配置

步骤一. settings.py中安装应用,在INSTALL_APPS 添加如下代码

1
2
3
INSTALLED_APPS = [
'captcha' # 安装应用
]

步骤二. settings.py中设置验证码样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 图形验证码配置
# 证码设置
CAPTCHA_IMAGESIZE = (8,45) # 设置captcha图片大小

CAPTCHA_LENGTH =4 #字符个数
CAPTCHA_TIMEOUT =1 #超时(minutes)*

# 输出格式:输入框验证码图片隐藏域•
# '%(image)s %(hidden_field)s %(text_field)s'
CAPTCHA_OUTPUT_FORMAT ='%(text_field)s %(image)s %(hidden_field)s'
CAPTCHA_NOISE_FUNCTIONS =(
'captcha.helpers.noise_null',
'captcha.helpers.noise_arcs',
'captcha.helpers.noise_dots',
)
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge'

步骤三. urls.py中设置路由

1
2
3
urlpatterns = [
path('captcha/',include("captcha.urls"))
]

步骤四. 迁移数据库

1
python manage.py migrate

建立表单

1
2
3
4
5
6
7
# forms.py
from django import forms
from captcha.fields import CaptchaField
class LoginForm(forms.Form):
username = forms.CharField(max_length=20,min_length=3)
password =forms.CharField(max_length=128,widget=forms.PasswordInput())
captcha = CaptchaField() # 验证码字段

##实现

步骤一. 在应用的urls.py中添加路由

1
2
3
4
5
# 应⽤的urls.py
urlpatterns = [
.....
path('yzm/',views.user_login,name='yzm'),
]

步骤二. 前端页面

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
# 前端⻚⾯
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<div>{{ msg }}</div>
<form action="{% url 'App03:yzm' %}" method="post">
{% csrf_token %}
⽤户:{{ form.username }} <span>{{ form.username.errors.0 }}</span> <br>
密码:{{ form.password }} <span>{{ form.password.errors.0 }}</span><br>
验证码:{{ form.captcha }} <span>{{ form.captcha.errors.0 }}</span><br>
<input type="submit">
</form>
</body>
</html>
<script src="https://cdn.bootcss.com/jquery/1.12.3/jquery.min.js">
</script>
<script>
//点击刷新验证码
$(function () {
$('.captcha').css({
'cursor': 'pointer'
});
// ajax刷新
$('.captcha').click(function () {
console.log('click');
$.get("/app3/refresh/",function (result) {
$('.captcha').attr('src', result['image_url']);
$('#id_captcha_0').val(result['key'])
});
});
})
</script>

步骤三. views.py视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
from captcha.helpers import captcha_image_url
from captcha.models import CaptchaStore
from django.contrib.auth import authenticate
import django.contrib.auth as auth
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
def user_login(request):
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user =authenticate(request,username=username,password=password)
if user:
auth.login(request,user)
return redirect(reverse("App03:home"))
else:
form = LoginForm()
# 跳转登录⻚⾯
return render(request,'App03/login.html',context={'form':form})

Django 发送邮件

setting配置

网易邮箱发送配置

1
2
3
4
5
6
7
8
9
10
11
# smtp服务的邮箱服务器
EMAIL_HOST = 'smtp.126.com'
# smtp服务固定的端口是25
EMAIL_PORT = 25

#发送邮件的邮箱
EMAIL_HOST_USER = 'zysheep@126.com'
#在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'TLNOFJSYQPYWISOA'
#收件人看到的发件人 <此处要和发送邮件的邮箱相同>
EMAIL_FROM = '小满<zysheep@126.com>'

QQ邮箱发送配置:

1
2
3
4
5
6
7
8
9
10
11
12
# smtp服务的邮箱服务器
EMAIL_HOST = 'smtp.qq.com'
# smtp服务固定的端口是25 如果不好使,就换成465
EMAIL_PORT = 25

#发送邮件的邮箱
EMAIL_HOST_USER = 'xxx@qq.com'
#在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'TLNOFJSYQPYWISOA'
EMAIL_USE_TLS = True # 必须设置为True,否则发送不成功
#收件人看到的发件人 <此处要和发送邮件的邮箱相同>
EMAIL_FROM = 'xxx@qq.com'

发送邮件

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
def mail_send(request):
#1.发送一封邮件
'''
subject: 主题
message: 内容
from_email: 收件人看到的发件人 <此处要和发送邮件的邮箱相同> 如 EMAIL_FROM = '小满<zysheep@126.com>'
recipient_list: 收件人名单可以发送个多个
'''
res = send_mail('习近平回信勉励北京大学援鄂医疗队全体','习近平指出,青年一代有理想、有本领、有担当,国家就有前途',EMAIL_FROM,['ivan15096000421@163.com','15096000421@qq.com'])

#2.发送多封邮件

# 发送多封邮件
message1 = ('习近平回信勉励北京大学', '<b>近平指出,青年一代有理想</b>', EMAIL_FROM, ['ivan15096000421@163.com','15096000421@qq.com'])
message2 = ('习近平回信勉励北京大学', '<b>近平指出,青年一代有理想</b>', EMAIL_FROM, ['ivan15096000421@163.com','15096000421@qq.com'])
send_mass_mail((message1, message2), fail_silently=False)

#3.html格式邮件
html_content = loader.get_template('active.html').render({'username': '小花猫'})
msg = EmailMultiAlternatives('小满博客温馨提示!', from_email=EMAIL_FROM, to=['15096000421@qq.com'])
msg.attach_alternative(html_content, "text/html")
msg.send()
# 3.1 html格式邮件,第二种格式
# 加载模板 并渲染模板
html = loader.get_template('active.html').render({'url':url})
print(url)
send_mail("账号激活",'',EMAIL_FROM,['15096000421@qq.com'],html_message=html)
return HttpResponse("邮件发送")

邮箱验证激活

说明

  1. 处理用户注册数据,存入数据库,is_active字段设置为False,用户认证之前不允许登录
  2. 产⽣token,⽣成验证连接URL
  3. 发送验证邮箱
  4. 用户通过认证邮箱点击验证链接,设置is_active字段为True,可以登录
  5. 若验证链接过期,删除用户在数据库中的注册信息,允许用户重新注册(username,email字段具有唯一性)

验证邮箱链接

  • 产生token,发送邮件
  • 处理验证链接,这里采用base64加密,及itsdangerous第三方库序列化(自带时间戳)
    • itsdangerous需要下载:pip install itsdangerous
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from itsdangerous import URLSafeTimedSerializer as utsr
import base64
from django.conf import settings as django_settings

class Token:
def __init__(self, security_key):
self.security_key = security_key
self.salt = base64.encodebytes(security_key.encode('utf8'))
# 生成token (自带时间戳)
def generate_validate_token(self, username):
serializer = utsr(self.security_key)
return serializer.dumps(username, self.salt)
# 验证token
def confirm_validate_token(self, token, expiration=3600):
serializer = utsr(self.security_key)
return serializer.loads(token, salt=self.salt, max_age=expiration)
# 移除token
def remove_validate_token(self, token):
serializer = utsr(self.security_key)
print(serializer.loads(token, salt=self.salt))
return serializer.loads(token, salt=self.salt)


token_confirm = Token(django_settings.SECRET_KEY) # 定义为全局变量

注册发送邮箱

模型:models.py

1
2
3
4
5
6
7
8
9
10
class User(models.Model):
uid = models.AutoField(primary_key=True)
username = models.CharField(unique=True, max_length=30)
password = models.CharField(max_length=128)
regtime = models.DateTimeField(auto_now=True)
sex = models.IntegerField(blank=True, null=True)
is_active = models.IntegerField(default=1)

class Meta:
db_table = 'user'

路由:urls.py

1
2
3
4
5
6
app_name='App'
urlpatterns = [
# 邮件验证
path('checkuser/',views.check_user,name='checkuser'),
path('active/<token>/',views.active_user,name='activeuser'),
]

模板: register.html

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>
</head>
<body>

<form action="{% url 'App:checkuser' %}" method="post">
{% csrf_token %}
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"> <br>
<input type="submit">

</form>

</body>
</html>

active.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>账户激活</title>
</head>
<body>
<p>亲爱的用户:</p>
<h2>请点击链接 <a href="{{ url }}">激活 </a> 账号</h2>
</body>
</html>

视图: views.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
def check_user(request):
if request.method =='POST':
username = request.POST.get('username')
password = request.POST.get('password')

# 检测用户是否存在
user = User.objects.filter(username=username,password=password).first()
if user:
return HttpResponse("用户已经存在")
# 保存用户信息
# 刚注册的用户是未激活
user = User.objects.create(username=username,password=password,is_active=0)

# 发送邮件,确认激活
token = token_confirm.generate_validate_token(user.uid)
print(token)
# 构造验证url
url = "http://"+request.get_host()+reverse("App:activeuser",kwargs={'token':token})
# 加载模板 并渲染模板
html = loader.get_template('active.html').render({'url':url})
print(url)
send_mail("账号激活",'',EMAIL_FROM,['15096000421@qq.com'],html_message=html)
return HttpResponse("激活邮件已经发送,请登录邮箱确认激活")
return render(request,'register.html')

激活用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def active_user(request,token):
# 激活用户
try:
uid = token_confirm.confirm_validate_token(token)
except Exception as e:
print(e)
try:
uid = token_confirm.remove_validate_token(token)
user = User.objects.get(pk=uid)
user.delete()
except:
pass
return HttpResponse("激活失败,请重新注册")
try:
user = User.objects.get(pk=uid)
except User.DoesNotExist:
return HttpResponse("你激活的用户不存在,请重新注册")
user.is_active = 1 # 激活用户
user.save()

return HttpResponse("用户已激活,请登录系统")

Django 富文本编辑器

⼀般⽤于写⽂章 编辑内容⾃带样式

安装

1
pip install django-tinymce

配置settings⽂件

在INSTALL_APPS 添加如下代码

1
2
3
4
INSTALLED_APPS = [
...
'tinymce',
]

在settings.py下添加如下代码

1
2
3
4
5
6
#富⽂本编辑器的配置
TINYMCE_DEFAULT_CONFIG = {
'theme':'advanced',
'width':600,
'height':400
}

添加视图函数

1
2
3
4
5
6
7
def index(req):
if req.method == 'GET':
return render(req,'index.html')
if req.method == 'POST':
# print(req.POST)
Posts(title=req.POST.get('title'),content=req.POST.get('content')).save()
return HttpResponse('index')

前台模板的展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/static/tiny_mce/tiny_mce.js"></script>
<script>
tinyMCE.init({
'mode':'textareas',
'width':800,
'height':600,
})
</script>
</head>
<body>
<form action="/" method="POST">
{% csrf_token %}
<p>标题 <input type="text" name="title" placeholder="请输⼊标题"maxlength="20" required></p>
<textarea name="content" id="" cols="30" rows="10"></textarea>
<input type="submit">
</form>
</body>
</html>

Django 文件上传

表单注意

  • 表单的enctype的值需要设置为:enctype=”multipart/form-data

  • 表单提交类型为POST

    储存路径

在settings.py⽂件下添加如下代码

1
2
#设置上传⽂件路径
MDEIA_ROOT = os.path.join(BASE_DIR,'static/upload')

文件上传对象的属性和方法

名称 说明
file.name 获取上传的名称
file.size 获取上传⽂件的大小(字节)
file.read() 读取全部(适用于小文件)
file.chunks() 按块来返回文件 通过for循环进⾏迭代,可以将大⽂件按照块来写入到服务器
file.multiple_chunks() 判断文件是否大于2.5M 返回True或者False

创建上传文件的表单

前端模板

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/doUpload/" method="post" enctype="multipart/formdata">
{% csrf_token %}
<p>⽂件 <input type="file" name="file"></p>
<p><input type="submit" value="上传"></p>
</form>
</body>
</html>

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.conf import settings
import os
#⽂件上传处理
def doUpload(req):
file = req.FILES.get('file')
# print(file.name)
# print(file.size)
savePath = os.path.join(settings.MDEIA_ROOT,file.name)
# print(savePath)
with open(savePath,'wb') as f:
# f.write(file.read())
if file.multiple_chunks():
for myf in file.chunks():
f.write(myf)
print('⼤于2.5')
else:
print('⼩于2.5')
f.write(file.read())
return HttpResponse('文件上传')

封装文件上传的类

可以⾃定义⼀个类实现⽂件上传,⽂件上传类可以:

  • 检查文件类型
  • 检查文件大小
  • 是否生成随机文件名
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class FileUpload:
def __init__(self, file, exts=('png','jpg','jpeg'), size=1024*1024,is_randomname=False):
"""
:param file: 文件上传对象
:param exts: 文件类型
:param size: 文件大小,默认1M
:param is_randomname: 是否是随机文件名,默认是否
"""
self.file = file
self.exts = exts
self.size = size
self.is_randomname = is_randomname

#文件上传
def upload(self,dest):
"""

:param dest: 文件上传的目标目录
:return:
"""
#1 判断文件类型是否匹配
if not self.check_type():
return -1
#2 判断文件大小是否符合要求
if not self.check_size():
return -2
#3 如果是随机文件名,要生成随机文件名
if self.is_randomname:
self.file_name = self.random_filename()
else:
self.file_name = self.file.name
#4 拼接目标文件路径
path = os.path.join(dest,self.file_name)
#5 保存文件
self.write_file(path)
return 1

def check_type(self):
ext = os.path.splitext(self.file.name)
if len(ext) > 1:
ext = ext[1].lstrip('.')
if ext in self.exts:
return True
return False

def check_size(self):
if self.size < 0:
return False
#如果文件大小于给定大小,返回True,否则返回False
return self.file.size <= self.size

def random_filename(self):
filename = datetime.now().strftime("%Y%m%d%H%M%S")+str(randint(1,10000))
ext = os.path.splitext(self.file.name)
#获取文件后缀
ext = ext[1] if len(ext)>1 else ''
filename += ext
return filename

def write_file(self,path):
with open(path,'wb') as fp:
if self.file.multiple_chunks():
for chunk in self.file.chunks():
fp.write(chunk)
else:
fp.write(self.file.read())

Django 站点管理

配置admin应用

默认django添加这个应用

1
2
3
INSTALLED_APPS = [
django.contrib.admin
]

创建管理员用户

1
python manage.py createsuperuser

依次输入用户名->邮箱->密码->确认密码

汉化

1
2
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'

在App/admin.py 里面注册自己的模型类

1
2
3
4
from .models import Grade,Students
#注册模型类 在后台展示
admin.site.register(Grade)
admin.site.register(Students)

配置后台页面和添加数据的展示

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
#配置数据的展示
class GradeAdmin(admin.ModelAdmin):
#设置显示哪些字段
list_display = ['pk','gname','gboynum','ggirlnum']
#添加搜索字段
search_fields = ['gname']
# 分⻚
list_per_page = 5
# 过滤字段‘
list_filter = ['gname']
class StudentsAdmin(admin.ModelAdmin):
list_display = ['pk','sname','ssex','sage','grade']
search_fields = ['sname']
#分⻚
list_per_page = 5
#过滤字段‘
list_filter = ['sname']
#更改添加 修改的字段属性的位置
# fields = ['sage','ssex','sname','grade','info']
fieldsets = [
("基本信息",{"fields":['sname','sage','ssex']}),
("其它信息",{'fields':['info','grade']}),
]
#字段顺序和字段分组不能同时使⽤
#注册模型类 在后台展示
admin.site.register(Grade,GradeAdmin)
admin.site.register(Students,StudentsAdmin)

关联对象

1
2
3
4
5
6
7
8
9
10
#TabularInline 横着展示添加学⽣的布局
#StackedInline 竖着展示添加学⽣的布局
# class AddStudents(admin.TabularInline):
#class AddStudents(admin.StackedInline):
class AddStudents(admin.TabularInline):
model = Students #关联的模型名称
extra = 2 #添加学⽣的个数
#配置数据的展示
class GradeAdmin(admin.ModelAdmin):
inlines = [AddStudents]

bool值的显示男女

models.py

1
2
3
4
5
def ssex(self):
if self.sex:
return '男'
else:
return '⼥'

在admin.py中

1
list_display = ['pk','sname',ssex,'sage','grade']

Django 中间件

中间件其实就是⼀个类,是介于request与response处理之间的⼀道处理过程(类似装饰器),相对比较轻量级,每个中间件都会负责⼀个功能,例如,AuthenticationMiddleware,与sessions处理相关,中间件,在请求到来和结束后,django会根据⾃⼰的规则在合适的时机执行中间件中相应的方法并且在全局上改变django的输⼊与输出。因为改变的是全局,所以需要谨慎使用,用不好会影响到性能

Django 中间件作用和可实现功能

作用

  • 修改请求,即传送到 view 中的 HttpRequest 对象。
  • 修改响应,即 view 返回的 HttpResponse 对象。

可实现功能

  • 统计
  • 黑名单
  • 白名单
  • 界面友好化(捕获异常)

Django 默认的中间件配置:

1
2
3
4
5
6
7
8
9
10
# 中间件重上往下执行
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',
]

中间件执行过程

用户请求到达中间件之后,先按照正序执行每个注册中间件,的process_request方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法,将HttpResponse对象返回给浏览器。(下图红色路线进行)也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了⼀个HttpResponse对象,那么第4,5,6中间件的process_requestprocess_response方法都不执行,顺序执行3,2,1中间件的process_response方法。

process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回 None,继续按顺序执行,所有process_view方法执行完后执行视图函数。假如中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后⼀个中间件,也就是中间件6的process_response方法开始倒序执行。

Django中的中间件方法

process_request

在执行路由前被调⽤,每个请求上都会调⽤,不主动进行返回或返回HttpResponse对象

1
process_request(self, request)

参数:

  • request,是⼀个HttpRequest请求对象

返回值:

返回None会继续调用下⼀个中间件的process_request方法,返回HttpResponse,则执行自己process_response

process_view

调用视图之前执行,每个请求都会调用,不主动进行返回或返回HttpResponse对象

1
process_view(self,request,view_func,view_args,view_kwargs)

参数:

  • request:HttpRequest对象
  • view_func:是⼀个即将调⽤的视图函数,不是字符串函数名
  • view_args:传递给视图函数的位置参数
  • view_kwargs:传递给视图函数的关键字参数

返回值:

如果返回None,会继续执行处理此请求,然后调⽤下⼀个中间件的process_view,直⾄执行视图函数;如果返回HttpResponse,则直接执行最后⼀个中间件的process_response

process_template_response

在视图刚好执⾏完后进⾏调⽤,只要视图返回⼀个render⽅法返回的对象,就会调⽤process_template_response,不主动进⾏返回或返回HttpResponse对象

1
process_template_response(self, request, response)

参数:

  • request HttpRequest对象
  • response 是⼀个由Django view或者中间件返回的
  • TemplateResponse 对象

返回值:

必须返回⼀个render⽅法执⾏后的response对象,它可以修改view中返回的 response.template_name 和 response.context_data,或者为view返回的模板增加⼀个商标等等。你不需要明确的渲染响应,当所有的template响应中间件处理完成后会被⾃动渲染

process_response

所有响应返回浏览器之前调用,每个请求都会调用,返回HttpResponse对象

1
process_response(self,request,response)

参数:

  • request :HttpRequest对象
  • response : HttpResponse对象

返回值:必须是HttpResponse对象

process_exception

当视图抛出异常时调⽤,返回None或返回HttpResponse对象

1
process_exception(self,request,exception)

参数:

  • request: HttpRequest 对象
  • exception:view函数中raise的Exception对象,当view 函数raise⼀个exception的时候调⽤process_exception

自定义中间件

App下Mymiddleware.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
from django.utils.deprecation import MiddlewareMixin
# 自定义中间件必须继承MiddlewareMixin类
class MyMiddleware(MiddlewareMixin):
def process_request(self,request):
print("=====1. process_request====",request.method)
# 全局路由保护
#print(request.path)
username = request.session.get('username')
if username:
return None
elif request.path not in ['/login/']: # 请求的不是登录界面
return redirect("/login/")

def process_view(self,request,view_func,view_args,view_kwargs):
print("=====2. process_view====","先执行process_view视图函数在执行自定义的视图函数")
return None

def process_response(self,request,response):
print("=====3. process_response====","响应返回浏览器之前调用")
return response # 必须返回相应对象

# 异常处理
def process_exception(self, request, exception):
print("exception")
# 对管理员展示错误页面,一般用户只能看到404,500等页面
ip = request.META.get('REMOTE_ADDR')
if ip == '127.0.0.9':
return technical_500_response(request, *sys.exc_info())
return redirect(reverse("App02:index"))

启用中间件

在settings中进⾏配置,MIDDLEWARE中添加:模块名.Mymiddleware.类名

1
2
3
4
MIDDLEWARE = [
.......
'App.Mymiddleware.Mymiddleware',
]

Django 缓存

缓存是一类可以更快的读取数据的介质统称,也指其它可以加快数据读取的存储方式。

在Django中,当用户请求到达视图后,视图会先从数据库提取数据放到模板中进行动态渲染,渲染后的结果就是用户看到的网页。如果用户每次请求都从数据库提取数据并渲染,将极大降低性能,不仅服务器压力大,而且客户端也无法即时获得响应。如果能将渲染后的结果放到速度更快的缓存中,每次有请求过来,先检查缓存中是否有对应的资源,如果有,直接从缓存中取出来返回响应,节省取数据和渲染的时间,不仅能大大提高系统性能,还能提高用户体验。

缓存使用场景:缓存主要适用于对⻚面实时性要求不高的⻚面。存放在缓存的数据,通常是频繁访问的,而不会经常修改的数据。

缓存方式:

  • 数据库
  • 文件
  • 内存
  • redis等

缓存配置

数据库缓存

settings.py

1
2
3
4
5
CACHES = {
'default':{
'BACKEND':'django.core.cache.backends.db.DatabaseCache', 'LOCATION':'my_cache_table',
}
}

生成缓存表

1
python manage.py createcachetable

文件缓存

1
2
3
4
5
6
7
8
9
10
11
12
CACHES = {
'default': {
'BACKEND':
'django.core.cache.backends.filebased.FileBasedCache', #指定缓存使⽤的引擎
'LOCATION': '/var/tmp/django_cache', #指定缓存的路径
'TIMEOUT':300, #缓存超时时间(默认为300秒,None表示永不过期)
'OPTIONS':{
'MAX_ENTRIES': 300, # 最⼤缓存记录的数量(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最⼤个数之后,剔除缓存个数的⽐例,即:1/CULL_FREQUENCY(默认3)
}
}
}

redis缓存

需要安装:pip install django-redis

1
2
3
4
5
6
7
CACHES = {
'default':{
'BACKEND':'django_redis.cache.RedisCache',#指定缓存类型redis缓存
'LOCATION':'redis://:123@127.0.0.1:6379/1', #缓存地址,@前面是reids的密码,如果没有则去掉
# 'LOCATION':'redis://127.0.0.1:6379/1', # 没密码
}
}

缓存使用

视图函数

1
2
3
4
from django.views.decorators.cache import cache_page 
@cache_page(60 * 0.5) #缓存过期时间
def dbcache(request):
return render(request,'index.html',context={'content':77665})

模板局部缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% cache 30 'content' %}
{{ content }}
{% endcache %}
</body>
</html>

全站缓存

1
2
3
4
5
6
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # 必须是第一个中间件
.....
'django.middleware.cache.FetchFromCacheMiddleware', # 必须是最后一个中间件
]
CACHE_MIDDLEWARE_SECONDS = 20 #设置超时时间 20秒

手动设置缓存

  • 设置缓存:cache.set(key,value,timeout)
  • 获取缓存:cache.get(key)
1
2
3
4
5
6
7
8
9
10
def cache_data1(request):
# 首先判断数据是否在缓存中,如果在直接获取
users = cache.get('all_users')
if not users: # 如果不在缓存,查询数据库,将结果写入缓存
users = User.objects.all()
# cache可以直接把查询结果序列化
cache.set('all_users',users)
print("数据库")
print(users)
return render(request,'index1.html',locals())

Django 异步任务队列

Celery简介

Celery 是一个基于python开发的异步任务队列/基于分布式消息传递的作业队列, 通过它可以轻松的实现任务的异步处理。它侧重于实时操作,但对调度支持也很好。Celery用于生产系统每天处理数以百万计的任务。Celery是用Python编写的, 但该协议可以在任何语言实现。它也可以与其他语言通过webhooks实现。Celery建议的消息队列是RabbitMQ,但提供支持Redis, Beanstalk, MongoDB, CouchDB, 和数据库(使用SQLAlchemy的或Django的 ORM) 。Celery是易于集成Django, Pylons 和 Flask,使用 django-celery, celery-pylons and Flask-Celery 附加包即可。它的特点:

  • 方便查看定时任务的执行情况, 如 是否成功, 当前状态, 执行任务花费的时间等.

  • 使用功能齐备的管理后台或命令行添加,更新,删除任务

  • 方便把任务和配置管理相关联

  • 可选多进程, Eventlet 和 Gevent 三种模型并发执行

  • 提供错误处理机制.

  • 提供多种任务原语, 方便实现任务分组,拆分,和调用链

  • 支持多种消息代理和存储后端.

  • Celery 是语言无关的,它提供了python 等常⻅语言的接口支持

Celery的相关概念

celery架构图

  • task 就是任务,包括异步任务和定时任务
  • 中间人,接收生产者发来的消息即Task,将任务存入队列。任务的消费者是Worker。Celery本身不提供队列服务,推荐用RedisRabbitMQ实现 队列服务。
  • worker 执行任务的单元,它实时监控消息队列,如果有任务就获取任务并执行它
  • backend 用于存储任务的执行结果。Celery支持以不同方式存储任务的结果, 包括AMQP, redis,memcached, mongodb,SQLAlchemy, Django ORM, Apache Cassandra, IronCache 等。
  • beat 定时任务调度器,根据配置定时将任务发送给Broler。

应用场景

  • 异步调用:那些用户不关心的但是又存在在我们API里面的操作 我们就可以用异步调用的方式来优化(发送邮件或者上传头像)
  • 定时任务:定期去统计日志,数据备份,或者其他的统计任务

Celery的安装

安装

1
2
3
4
pip install celery
pip install celery-with-redis
#django-celery-results库基于 Django ORM实现了结果存储后端
pip install django-celery-results

配置

在 settings.py文件中设置

1
2
3
4
5
6
7
8
9
10
11
12
INSTALLED_APPS = (
...
'celery',
'django_celery_results', #把 django_celery_results 加到INSTALLED_APPS 中
'自己的APP'
}

BROKER_URL='redis://localhost:6379/5'
# 任务序列化和反序列化使用json
CELERY_RESULT_BACKEND = 'django-db'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json' # 结果序列化为json

创建celery实例

在settings.py的同级目录下新建celery.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from __future__ import absolute_import #绝对路径导入
from celery import Celery
from django.conf import settings
import os

# 设置工程配置文件 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "工程名.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day08.settings")


# 实例话celery对象
# 第一个参数是应用名称,必须给
app = Celery('mycelery')

# 设置时区
app.conf.timezone = "Asia/Shanghai"

# 读取配置文件
app.config_from_object("django.conf:settings")

# 让celery 自动去发现我们的任务(task)
app.autodiscover_tasks() #你需要在app目录下 新建一个叫tasks.py(一定不要写错)文件

在settings.py同级目录下的init.py加入

1
2
from future import absolute_import
from .celery import app as celery_app

Celery的使用

创建任务

在需要使用异步任务的APP目录下新建tasks.py

1
2
3
4
5
6
7
8
9
# 异步发送邮件
@shared_task
def mail_send(mail):
"""
subject, message, from_email, recipient_list
:param mail: 字典 {'subject':'hello'}
:return:
"""
send_mail(**mail,from_email=EMAIL_FROM)

调用

在views.py内的调用

1
2
3
4
5
6
7
# 任务函数名.delay(参数,,,,)
# 异步发送邮件
mail_send.delay({
'subject':'no zuo no die',
'message':'不作不死',
'recipient_list':['15096000421@qq.com']
})

生成数据库表

1
python manage.py migrate django_celery_results

启动worker

1
celery -A 你的工程名 worker -l info

注意:修改tasks.py的内容后 要重启celery的服务

获取任务执行结果

异步任务执行完毕后,会自动触发信号:

  • before_task_publish
  • after_task_publish
  • task_prerun
  • task_postrun
  • task_success
  • task_failure
  • task_revoked
1
2
3
4
from celery.signals import task_success 
@task_success.connect(sender=add)
def task_done_handler(sender=None, result=None):
print(result)

定时任务和计划任务

定时任务

1
celery -A 你的工程名称 beat -l info

在settings.py文件添加

1
2
3
4
5
6
7
8
# 定时任务
CELERYBEAT_SCHEDULE = {
'schedule-test': {
'task': 'App.tasks.hello_world',
'schedule': timedelta(seconds=10),
'args': (6,)
},
}

计划任务时间

1
2
3
4
5
6
7
8
from celery.schedules import crontab
CELERYBEAT_SCHEDULE = {
"every-ten-second-run-my_task": {
"task": "App.tasks.do",
"schedule": crontab(minute="05", hour="16"),
# "args": (2,)
}
}

注意: 我们启动定时任务服务时 也要先开启worker。如果只开启定时服务 没有开启worker服务 那么定时任务会被放入任务队列,但是由于没有干活儿的worker 那么任务是不会被执行,当worker服务被启动后 会立刻去任务队列领任务并执行

你的任务一定要确保是可以正常执行的

其它

查看异步任务情况

Celery提供了一个工具flower,将各个任务的执行情况、各个worker的健康 状态进行监控并以可视化的方式展现

  1. 安装flower

    1
    pip install flower
  2. 启动flower(默认会启动一个webserver,端口为5555):

    1
    celery flower --broker=redis://localhost:6379/5
  3. 即可查看

    1
    http://localhost:5555

内存泄漏

说明:⻓时间运行Celery有可能发生内存泄露,可以像下面这样设置

CELERYD_MAX_TASKS_PER_CHILD = 1000 # 每个worker执行了多少任务就会死掉

常用配置清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#from kombu import Queue, Exchange
#设置Broker和backend
BROKER_URL = 'redis://127.0.0.1:6379/0'
# 将数据存放到redis1数据库,redis默认有16个数据库
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1'

CELERY_TASK_SERIALIZER = 'json' # 任务序列化和反序列化使用json
CELERY_RESULT_SERIALIZER = 'json' #结果序列化为json
CELERY_ACCEPT_CONTENT = ['json'] #分布式接受数据的类型为json
CELERY_TIMEZONE = 'Asia/Shanghai' #使用中国上海时区
CELERY_ENABLE_UTC = True

CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 #后端存储任务超过一天,则自动清理数据,单位为秒
CELERYD_MAX_TASKS_PER_CHILD = 1000 #每个worker最多执行1000个任务就会被销毁,可防止内存泄露

Django LOG日志

Log简介

logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比print,具备如下优点:

通过log的分析,可以⽅便⽤户了解系统或软件、应⽤的运⾏情况;如果你的应⽤log⾜够丰富,也可以分析以往⽤户的操作⾏为、类型喜好、地域分布或其他更多信息;如果⼀个应⽤的log同时也分了多个级别,那么可以很轻易地分析得到该应⽤的健康状况,及时发现问题并快速定位、解决问题,补救损失

Log的用途

不管是使⽤何种编程语⾔,⽇志输出⼏乎⽆处不再。总结起来,⽇志⼤致有以下⼏种⽤途:

  • 问题追踪:通过⽇志不仅仅包括我们程序的⼀些bug,也可以在安装配置时, 通过⽇志可以发现问题

  • 状态监控:通过实时分析⽇志,可以监控系统的运⾏状态,做到早发现问题、 早处理问题

  • 安全审计:审计主要体现在安全上,通过对⽇志进⾏分析,可以发现是否存在非授权的操作

Log的等级

  • DEBUG最详细的⽇志信息,典型应⽤场景是 问题诊断

  • INFO信息详细程度仅次于DEBUG,通常只记录关键节点信息,⽤于确认⼀切都是按照我们预期的那样进⾏⼯作

  • WARNING当某些不期望的事情发⽣时记录的信息(如,磁盘可⽤空间较低),但是此时应⽤程序还是正常运⾏的

  • ERROR由于⼀个更严重的问题导致某些功能不能正常运⾏时记录的信息 如IO操作失败或者连接问题

  • CRITICAL当发⽣严重错误,导致应⽤程序不能继续运⾏时记录的信息

Log的四大组件

Loggers

提供应⽤程序代码直接使⽤的接⼝

Handlers

⽤于将⽇志记录发送到指定的⽬的位置

1
2
3
4
FileHandler:logging.FileHandler;⽇志输出到⽂件
RotatingHandler:logging.handlers.RotatingHandler;⽇志回滚⽅式,⽀持⽇志⽂件最⼤数量和⽇志⽂件回滚
SMTPHandler:logging.handlers.SMTPHandler;远程输出⽇志到邮件地址
HTTPHandler:logging.handlers.HTTPHandler;通过"GET"或者"POST"远程输出到HTTP服务器

Filters

提供更细粒度的⽇志过滤功能,⽤于决定哪些⽇志记录将会被输出(其它的⽇志记录将会被忽略)

Formatters

⽤于控制⽇志信息的最终输出格式

1
2
3
4
5
6
7
8
9
10
11
%(levelno)s:打印⽇志级别的数值
%(levelname)s:打印⽇志级别的名称
%(pathname)s:打印当前执⾏程序的路径,其实就是sys.argv[0]
%(filename)s:打印当前执⾏程序名
%(funcName)s:打印⽇志的当前函数
%(lineno)d:打印⽇志的当前⾏号
%(asctime)s:打印⽇志的时间
%(thread)d:打印线程ID
%(threadName)s:打印线程名称
%(process)d:打印进程ID
%(message)s:打印⽇志信息

示例

1
2
3
4
5
6
7
8
9
10
11
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler(“log.txt”)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")

Django中的配置

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 日志配置
ADMINS = (
('tom', '15096000421@qq.com'),
)
# 配置邮件
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
SERVER_EMAIL = EMAIL_HOST_USER


LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'standard': {
'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'}
},
'filters': { # 过滤条件
# 要求debug是False才记录
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
}
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'mail_admins': { # 一旦线上代码报错 邮件提示
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'filters': ['require_debug_false'],
},
'debug': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, "log", 'debug.log'), # 文件路径
'maxBytes': 1024 * 1024 * 5, # 5兆的数据
'backupCount': 5, # 允许有5这样的文件
'formatter': 'standard', # 格式
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'standard',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False
},
'django.request': {
'handlers': ['debug', 'mail_admins'],
'level': 'ERROR',
'propagate': True, # 是否继承父类的log信息
},
# 对于不在 ALLOWED_HOSTS 中的请求不发送报错邮件
'django.security.DisallowedHost': {
'handlers': ['null'],
'propagate': False,
},
}
}

基于类的视图

视图是可调用的,它接收请求并返回响应。这可能不仅仅是一个函数,Django提供了一些可用作视图的类的示

例。这些允许您通过利用继承和mixin来构建视图并重用代码

基于类的视图(Class-based views)提供了另一种将视图实现为Python对象而不是函数的方法。它们不替换基于

函数的视图,但与基于函数的视图相比具有一定的差异和优势:

  • 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承)
  • 可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性

Function Based View FBV

1
2
3
4
5
6
7
8
9
10
# Create your views here.
# Function Based View FBV
# 简单,易懂
# 缺点:不能继承,不容易代码复用

def index(request):
if request.method=="POST":
return redirect(reverse("App:register"))
else:
return HttpResponse("首页")

CBV Class Based View 基于类的视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# CBV Class Based View 基于类的视图
# 优点:有继承,代码可复用、可维护性更强;一个请求对应一个方法,无需判断
# 缺点:比较抽象,不易懂
class RegisterView(View):
a = 10
def get(self,request):
print(self.a)
return HttpResponse("GET")

def post(self,request):
return HttpResponse("POST")

def put(self,request):
return HttpResponse("PUT")

def delete(self,request):
return HttpResponse("DELETE")

内建的基于类的视图的层次结构:

  • 基本视图:view 、TemplateView、RedirectView
  • 通用显示视图:DetailView、ListView
  • 通用编辑视图:FormView、CreateView、 UpdateView、DeleteView
  • 通用日期视图: ArchiveIndexView、YearArchiveView、 MonthArchiveView、 WeekArchiveView、DayArchiveView、TodayArchiveView、DateDetailView
  • 基于类的视图mixins
    • 简单的mixins:ContextMixin、TemplateResponseMixin
    • 单个对象mixins:SingleObjectMixin、SingleObjectTemplateResponseMixin
    • 多个对象混合:MultipleObjectMixin、MultipleObjectTemplateResponseMixin

类视图的基本使用

所有类视图都继承自Django提供的父类View,可以使用from django.views import View或from django.views.generic import View来导入父类View。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#views.py
from django.urls import reverse
from django.views import View
from django.http import HttpResponse
from django.shortcuts import render, redirect
class Register(View):
# 处理GET请求
def get(self,request, *args, **kwargs):
return render(request,"App/register.html")
# 处理POST请求
def post(self, request, *args, **kwargs):
# 注册业务处理
...
return redirect(reverse("App:login"))

路由注册:

1
2
3
4
5
6
urlpatterns = [
# 函数注册
path("register/",views.register,name='register')
# 类视图注册
path(r'register/',views.RegisterView.as_view(),name='register')
]

基本视图

根视图View类

提供适合各种应用程序的基本视图类。所有视图都继承自 View 该类,该类处理将视图链接到URL,HTTP方法调度 和其他简单功能。

View类核心代码在as_viewdispatch方法中,其中as_view是类方法(@classonlymethod),只能通过类调用,不能通过对象调用,它是类视图的入口点。注意这里调用的时候是通过类名.as_view()调用的。 其中,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
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
# 参数检查
for key in initkwargs:
if key in cls.http_method_names: # 参数名不能是指定http的方法
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key): # 参数名不能是已有类的属性
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))

# 视图处理函数
def view(request, *args, **kwargs):
self = cls(**initkwargs) # 实例化当前类的对象
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 方法派发
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs

# take name and docstring from class
update_wrapper(view, cls, updated=())

# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view # 返回视图函数

整个as_view方法是一个装饰器方法,它返回内部函数view,所以as_view()执行其实就是内部函数view执行。内部 函数view主要逻辑就是:as_view()=>view()=>dispatch()=>相应的http方法

  • 实例化本类对象
  • 接受请求对象和参数(setup)
  • 调用dispatch方法进行派发

调用as_view方法可以传递参数,但要注意:

  • 不能使用请求方法的名字作为参数的名字
  • 只能接受视图类已经存在的属性对应的参数

dispatch方法是实例方法,它的主要代码:

1
2
3
4
5
6
7
8
9
def dispatch(self, request, *args, **kwargs):
#检查请求方法是不是在http_method_names中包含
#http_method_names包括八种方法:['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
if request.method.lower() in self.http_method_names:
# 判断传入的对象是否有改方法,有则返回,没有返回默认值
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)

dispatch主要完成http请求方法的派发,调用视图类对应实例方法处理用户请求,所有用户需要定义和http请求方 法同名的实例方法完成功能,所以一般CBV的模块写法是:

1
2
3
4
5
6
7
8
9
10
from django.views import View
class IndexView(View):
def get(self,request):
return HttpResponse("get")
def post(self,request):
return HttpResponse("post")
def put(self,request):
return HttpResponse("put")
def delete(self,request):
return HttpResponse("delete")

TemplateView

TemplateView可以根据上下文渲染指定模板,返回响应对象。它继承了ContentMixin、View、TemplateResponseMix

  • ContentMixin用于获取渲染模板的变量。你可以重写get_context_data方法返回模板渲染的参数
  • TemplateResponseMixin 用于渲染模板
    • template_name模板文名(必须设置)
    • template_engine模板引擎(有默认值)
    • response_class模板渲染类,默认是TemplateResponse
    • content_type内容类型,默认是text/html
    • get_template_names你可以重写这个方法返回模板名称
1
2
3
4
5
6
7
8
9
10
#路由
urlpatterns = [
url(r'^hello/(\w+)/$',views.HelloView.as_view(template_name='hello.html'),name='hello'),
]
#views.py
class HelloView(TemplateView):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context['name'] = args[0]
return self.render_to_response(context)

注意as_view方法参数只能是template_name、template_engine、response_class、content_type

RedirectView

重定向的指定url

get_redirect_url用于构造重定向的目标URL,可以重写。默认实现url用作起始字符串,并%使用URL中捕获的命名组在该字符串中执行命名参数的扩展。如果url未设置,则get_redirect_url()尝试反转 pattern_name使用URL中捕获的内容(使用已命名和未命名的组)。如果请求query_string,它还会将查询字符串附加到生成的URL。子类可以实现他们希望的任何行为,只要该方法 返回可重定向的URL字符串即可

通用显示视图

本类视图主要用户数据展示,包括ListView显示对象列表信息和DetailView显示对象详细信息

ListView

  • MultipleObjectTemplateResponseMixin

    • 提供了模板文件名
    • 如果没有指定模板文件名,则默认模板文件名规则是:应用名/模型名_list.html
  • MultipleObjectMixin

    核心类,提供了渲染模板所有需要的模型或查询结果集(不一定是QuerySet,可以是对象列表),分页。

    • queryset属性用于渲染模板所需对象列表,也可以重写get_queryset方法获取

    • model,如果没指定queryset,则根据指定model获取对象列表

    • context_object_name模板中对象列表的名称,如果不指定,则根据model获取对象列表名称:model_list

    • paginate_by指定分页每页的记录个数,默认是None,不分页

    • page_kwarg指定分页请求路径中命名分组名或get传参中键的名称,默认是page

    • paginate_orphans是指分页最后一页如果记录不满一页的处理方式,默认是0,和前一页合并显示,如果不为0,则单独显示一页

    • 分页具体实现:

      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
      def get_context_data(self, **kwargs):
      """
      Get the context for this view.
      """
      queryset = kwargs.pop('object_list', self.object_list)
      page_size = self.get_paginate_by(queryset)
      context_object_name = self.get_context_object_name(queryset)
      if page_size:
      paginator, page, queryset, is_paginated = self.paginate_queryset(queryset,
      page_size)
      context = {
      'paginator': paginator, #在模板中可以使用分页器
      'page_obj': page, #分页对象
      'is_paginated': is_paginated,
      'object_list': queryset #当前页数据
      }
      else:
      context = {
      'paginator': None,
      'page_obj': None,
      'is_paginated': False,
      'object_list': queryset
      }
      if context_object_name is not None: #如果context_object_name不为空
      context[context_object_name] = queryset
      context.update(kwargs)
      return super(MultipleObjectMixin, self).get_context_data(**context)
    • BaseListView默认实现get请求

DetailView

显示对象的详细信息

  • SingleObjectMixin
    • pk_url_kwarg 默认值pk,从请求路径中获取主键的值,请求路径中参数必须是命名组,组名必须和 pk_url_kwarg的值一样
    • slug_url_kwarg默认值是slug,从请求路径中获取查询参数sug的值,请求路径中参数必须是命名组,组名必须和slug_url_kwarg的值一样,如果参数中有pk_url_kwarg的值,则slug_url_kwarg不起作用
    • slug_fifield查询字段的名称
    • context_object_name模板中引用对象的名称,默认模板中对象名称是object

通用编辑视图

本类视图主要完成对象的增删改。包括FormView、CreateView、 UpdateView、DeleteView

FormView

显示表单的视图。出错时,重新显示带有验证错误的表单; 成功时,重定向到新的URL。

  • FormMixin
    • form_class 表单类名
    • success_url表单验证成功后调整的url
    • form_valid() 验证数据成功后的处理
    • form_invalid() 验证不成功的处理
  • ProcessFormView
    • get渲染表单
    • post表单提交
    • put创建或修改对象

CreateView

显示用于创建对象的表单的视图,使用验证错误(如果有)重新显示表单并保存对象。

重要属性:

  • template_name 模板文件名
  • fields指定的字段列表
  • model关联模型名
  • form_class表单类,如果没有设置会默认是模型名

UpdateView

UpdateView的用法和CreateView基本一样

DeleteView

删除指定对象的视图

类视图使用装饰器

为类视图添加装饰器,可以使用两种方法。

为了理解方便,我们先来定义一个为函数视图准备的装饰器(在设计装饰器时基本都以函数视图作为考虑的被装饰对象),及一个要被装饰的类视图

1
2
3
4
5
6
7
8
9
10
11
12
13
def my_decorator(func):
def wrapper(request, *args, **kwargs):
print('自定义装饰器被调用了')
print('请求路径%s' % request.path)
return func(request, *args, **kwargs)
return wrapper
class DemoView(View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')

在类视图中使用为函数视图准备的装饰器时,不能直接添加装饰器,需要使用method_decorator将其转换为适用 于类视图方法的装饰器

method_decorator装饰器使用name参数指明被装饰的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 为全部请求方法添加装饰器
@method_decorator(my_decorator, name='dispatch')
class DemoView(View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')
# 为特定请求方法添加装饰器
@method_decorator(my_decorator, name='get')
class DemoView(View):
def get(self, request):
print('get方法')
return HttpResponse('ok')
def post(self, request):
print('post方法')
return HttpResponse('ok')

如果需要为类视图的多个方法添加装饰器,但又不是所有的方法(为所有方法添加装饰器参考上面例子),可以直 接在需要添加装饰器的方法上使用method_decorator,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.utils.decorators import method_decorator
# 为特定请求方法添加装饰器
class DemoView(View):
@method_decorator(my_decorator) # 为get方法添加了装饰器
def get(self, request):
print('get方法')
return HttpResponse('ok')
@method_decorator(my_decorator) # 为post方法添加了装饰器
def post(self, request):
print('post方法')
return HttpResponse('ok')
def put(self, request): # 没有为put方法添加装饰器
print('put方法')
return HttpResponse('ok')

Django-rest-framework

什么是RESTful

  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
  • REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
  • 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
  • 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)

RESTful API设计

  • API与用户的通信协议,总是使用HTTPs协议

  • 域名

  • 版本

  • 路径,视网络上任何东西都是资源,均使用名词表示(可复数)

  • method

    • GET :从服务器取出资源(一项或多项)
    • POST :在服务器新建一个资源
    • PUT :在服务器更新资源(客户端提供改变后的完整资源)
    • PATCH :在服务器更新资源(客户端提供改变的属性)
    • DELETE :从服务器删除资源
  • 过滤,通过在url上传参的形式传递搜索条件

  • 状态码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功。
    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
  • 错误处理,状态码是4xx时,应返回错误信息,error当做key。

    1
    2
    3
    {
    error: "Invalid API key"
    }
  • 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。

    1
    2
    3
    4
    5
    6
    GET /collection:返回资源对象的列表(数组)
    GET /collection/resource:返回单个资源对象
    POST /collection:返回新生成的资源对象
    PUT /collection/resource:返回完整的资源对象
    PATCH /collection/resource:返回完整的资源对象
    DELETE /collection/resource:返回一个空文档
  • Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

    1
    2
    3
    4
    5
    6
    {"link": {
    "rel": "collection https://www.example.com/zoos",
    "href": "https://api.example.com/zoos",
    "title": "List of zoos",
    "type": "application/vnd.yourformat+json"
    }}

基于Django实现

路由系统:

1
2
3
urlpatterns = [
url(r'^users', Users.as_view()),
]

CBV视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.views import View
from django.http import JsonResponse

class Users(View):
def get(self, request, *args, **kwargs):
result = {
'status': True,
'data': 'response data'
}
return JsonResponse(result, status=200)

def post(self, request, *args, **kwargs):
result = {
'status': True,
'data': 'response data'
}
return JsonResponse(result, status=200)

基于Django Rest Framework框架实现

Django Rest Framework(DRF)是一个强大且灵活的工具包,用以构建Web API。DRF可以在Django的基础上迅速实现API,并且自身带有WEB的测试页面,可以方便的测试自己的API

安装:

1
pip install djangorestframework

url.py

1
2
3
4
5
6
from django.conf.urls import url, include
from web.views.s1_api import TestView

urlpatterns = [
url(r'^test/', TestView.as_view()),
]

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from rest_framework.views import APIView
from rest_framework.response import Response


class TestView(APIView):
def dispatch(self, request, *args, **kwargs):
"""
请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法

注意:APIView中的dispatch方法有好多好多的功能
"""
return super().dispatch(request, *args, **kwargs)

def get(self, request, *args, **kwargs):
return Response('GET请求,响应内容')

def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')

def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')

Django上线部署

安装

  1. 在线上服务器安装虚拟开发环境

  2. 安装nginx

  3. 安装mysql

  4. 创建虚拟环境

  5. 在虚拟开发环境中安装django、pymysql、pillow

  6. 安装uwsgi

    1
    pip install uwsgi
  7. 上传项⽬

  8. 在项⽬中根⽬录下创建uconfifig.ini的⽂件 代码在下⽅

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [uwsgi]
    # 外部访问地址,可以指定多种协议,⽤socket #
    socket = 0.0.0.0:8000 # uwsgi的监听端⼝
    # 指向项⽬根⽬录
    chdir = /var/www/online
    # wsgi.py所在位置
    wsgi-file = day09/wsgi.py
    module = day09.wsgi
    # 虚拟开发环境位置
    virtualenv = /home/python/.pyenv/versions/env3.6.6
    #plugins = python
    master = true
    # 处理器数
    processes = 1
    # 线程数
    threads = 2
  9. 更改nginx的default⽂件代码在下方

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    server {
    listen 80;
    server_name www.blog.com;
    location / {
    # 转发端⼝必须和uconfig.ini中socket端⼝⼀致
    uwsgi_pass 127.0.0.1:8000;
    include uwsgi_params;
    uwsgi_param UWSGI_SCRIPT online.wsgi;
    # 项⽬的根⽬录
    uwsgi_param UWSGI_CHDIR /var/www/online;
    }
    # 静态资源所在位置
    location /static {
    alias /var/www/online/static/;
    }
    }
  10. 重启nginx

  11. 回到虚拟环境⽬录启动 uwsgi

    1
    uwsgi uconfig
  12. 关闭uwsgi

    1
    uwsgi —stop uconfig.ini
  13. 如果关闭不掉杀死进程

    1
    2
    ps -ef | grep uwsgi
    Sudo kill 进程号

问题描述:

django admin没有样式 admin管理⻚⾯找不到base.css,dashboard.css⽂件

解决办法:

在settings⽂件中设置STATIC_ROOT⽬录,该⽬录不能在STATICFILES_DIRS中. 然后, 执⾏命令

1
python manage.py collectstatic

执⾏后,django会将STATICFILES_DIRS下的所有⽂件以及admin所需要⽤到的js,css,image⽂件全都放到STATIC_ROOT⽬录下.例如, 像下⾯这样写:

1
2
3
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'collectstatic')
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]

简单描述⼀下这几个变量的意思 STATIC_URL: 当访问什么样的⽹址时, 按照访问静态⽂件的⽅式去查找⽂件. STATICFILES_DIRS: 当访问静态⽂件是, 会在每个app中的static⽬录中查找, 然后再从STATICFILES_DIRS设置的路径列表中逐⼀查找. STATIC_ROOT: 当执⾏ python manage.py collectstatic 时, 收集的静态⽂件放在该⽬录下.

此刻项⽬下就会多出⼀个collectstatic的静态资源⽂件⽬录

default代码更改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server{
listen 80; # 服务器监听端⼝
server_name 192.168.1.132; # 这⾥写你的域名或者公⽹IP
location / {
uwsgi_pass 127.0.0.1:8000; # 转发端⼝,需要和uwsgi
配置当中的监听端⼝⼀致
include uwsgi_params; # 导⼊uwsgi配置
uwsgi_param UWSGI_PYTHON /home/xlg/axf/venv; #Python
解释器所在的路径(这⾥为虚拟环境)
uwsgi_param UWSGI_CHDIR /home/xlg/axf/;# ⾃⼰创建的⽬
录 项⽬根⽬录
}
location /static{
alias /home/xlg/axf/collectstatic/;
}
}

访问可能会出现403没有权限的问题

解决办法:

找到nginx.conf⽂件的位置,将第⼀⾏的代码进⾏修改

1
2
3
#user www-data;
user root;
或者将 www-data更改权限

此刻就可以访问了

10.0.11.11/static/img/home.img