Django

绪论

Django 3 官方参考文档

点击进入

Django 2.2 官方参考文档

点击进入

Django 2.2 其他参考文档

点击进入

Django 1.11 官方参考文档

点击进入(英文)

Django 1.11 中文参考文档

点击进入

Django 2 及后续版本不再支持 Python 2;

Django 3 及后续版本不再支持 Python 3.5 及以下版本。

Django 1 与 2 的区别

主要区别如下:

  • url 用法:Django 1 主要使用 url 来配置,参数部分使用()做匹配;Django 2 使用 path 来配置,参数部使用<>做匹配,不支持传统的正则表达式。这里Django 2 兼容 Django 1 ,可以用re_path来做Django 1中url的操作。
  • 路由分发 include。
  • ORM 外键:Django 2 的外键必须加on_delete属性

参考文章一
参考文章二

Django 3.0 新特性(2019年12月 推出)

  • 仅支持 Python 3.6以上版本。
  • 支持使用 MariaDB 10.1 或更高版本的数据库。
  • 开始将新增对 ASGI 的支持。这意味着 Django 3 可以支持异步操作,消除阻塞操作对程序的影响。
  • 新增枚举类型 TextChoices 和 IntegerChoices 类。

枚举示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
GRADUATE = 'GR'
YEAR_IN_SCHOOL_CHOICES = [
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
(GRADUATE, 'Graduate'),
]

year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)

Django 基本操作

django-admin 基本命令

1
2
3
4
5
6
7
8
9
10
django-admin startproject     # 创建Django项目
django-admin startapp # 创建Django应用
django-admin check # 检查项目完整性
django-admin test # 执行单元测试
django-admin runserver # 启动服务器
django-admin shell # 进入Django Shell
django-admin makemigrations # 创建数据库迁移文件
django-admin migrate # 执行迁移文件
django-admin dumpdata # 导出数据库数据
django-admin loaddata # 导入数据库数据

目录结构

项目结构 A (本次使用)

  • Project # 项目目录
    • manage.py # 项目管理文件
    • project_name # 项目配置目录
      • asgi.py # Django 3.0 新增文件
      • settings.py # 项目配置
      • urls.py # 项目路由
      • wsgi.py # Web 与 Django 交互入口
    • my_app # 创建的Django应用目录
      • migrations # 数据库迁移文件目录
      • static # 静态文件目录
      • templates # 模板目录
        • index.html
      • templatetags # 自定义标签过滤器目录
        • __init__.py
      • urls.py # 应用路由
      • apps.py # 应用声明
      • models.py # 应用模型
      • test.py # 单元测试
      • admin.py # Admin模块
      • views.py # 应用视图

创建过程如下:

1
2
3
4
5
6
7
django-admin startproject project_name
cd project_name
django-admin startapp my_app
cd my_app
mkdir templates
mkdir templatetags
mkdir static

项目结构 B

部分Django项目也会用到这种结构,即将模板,过滤器,静态文件等目录放在应用的外部。

  • Project
    • manage.py
    • project_name
      • settings.py
      • urls.py
      • wsgi.py ( Web 与 Django 交互入口)
    • templates
      • my_app_templates
        • index.html
    • templatetags # 自定义标签过滤器
      • __init__.py
    • my_app
      • migrations
      • urls.py
      • apps.py
      • models.py
      • test.py
      • admin.py
      • 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 每创建一个应用,都要在项目setting.py中声明该应用
INSTALLED_APPS=[
...
'my_app',
# 或写成
'my_app.apps.MyAppConfig' # 具体可以到应用的apps.py文件中查看命名
]

# 配置项目的数据库,可以保持默认SQLite
DATABASES=[
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
]

# 如果要改为MySQL,配置如下。注意:首次运行可能需要安装所提示的Python扩展包。
DATABASES=[
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '数据库名称', # 必须手动创建
'USER': 'root',
'PASSWORD': '123456',
'HOST': 'localhost',
'PORT': 3306,
}
]

# 如果使用项目结构 B,则做下面的配置,配置要渲染的模板的目录
TEMPLATES = [
...
'DIR' = [os.path.join(BASE_DIR, 'templates')]
...
]

# 修改默认语言和时区
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'

# 如果项目完成后,在交付阶段,要将DEBUG关闭,并配置允许访问的主机
DEBUG = False
ALLOWED_HOSTS = ['*']

配置完成后,在应用my_app下的views.py中添加一个简易的视图:

1
2
3
4
5
6
from django.shortcuts import render
from django.http import HttpResponse


def hello(request):
return HttpResponse("Hello World")

同时应该配置路由文件,在my_app中创建并配置urls.py。

1
2
3
4
5
6
7
# Django 2.0 以上推荐此写法,后续写法均为 2.0 写法。
from django.urls import path, re_path, include
import my_app.views

urlpatterns = [
path('hello/', my_app.views.hello),
]
1
2
3
4
5
6
7
8
9
10
# Django 1.11 以及之前的写法。
from django.conf.urls import url, include
import my_app.views

urlpatterns = [
# 写法 1:这里的'hello'是按照字符串进行匹配,如果用户写作helloabcd,也会命中该记录,如果想修正这种错误,可以写作'hello/'。
url('hello', my_app.views.hello),
# 写法 2:使用正则表达式,r表示该串为正则表达式,^与$分别标记了正则表达式匹配的开始和结尾,这样,用户写helloabcd就不会命中了。使用正则表达式的好处,例如匹配GET参数等,都会使操作变得很方便。
url(r'^hello/$', my_app.views.hello),
]

之后再配置项目路由器:

1
2
3
4
5
6
7
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('my_app/', include('my_app.urls'))
]

配置到这里就可以运行项目看效果了。

1
python manage.py runserver 8080

进入如下地址,出现Hello World就算配置成功了。
http://127.0.0.1:8080/my_app/hello/

Django 模型

模型(Models)是用于对接数据库的接口,在所有的MVC应用中都是如此。Django的模型的定义如下:

数据类型

  • 整型:IntegerField
  • 定长文本:CharField
  • 不定长文本:TextField
  • 日期:DataField
  • 时间:TimeField
  • 日期时间:DateTimeField
  • 自增ID:AutoField (可以不定义,自动生成)
  • 布尔:BooleanField
  • Null型布尔:NullBooleanField
  • 十进制浮点数:DecimalField (精度更高,例如钱数)
  • 浮点型:FloatField
  • 文件:FileField
  • 图片:ImageField

主要属性

  • 主键:primary_key
  • 长度:max_length
  • 默认值:default
  • 唯一性:unique(不允许重复出现)
  • 索引:db_index
  • 自定义字段名称:db_column
  • 是否允许为空:null
  • 是否允许空白:blank
  • 十进制浮点数:
    • 数字数:max_digits
    • 小数点数:decimal_places
  • 日期(二者只能用一个)
    • 更新时间:auto_now
    • 创建时间:auto_now_add
  • 外键:’其他表’ (赋值时直接写该对象,2.0以上版本还要求on_delete属性)

创建模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.db import models

class MyBookModel(models.Model):
# 注意:属性名不能出现两个连续的下划线,
# title
title = models.CharField(max_length=20)
# publish_date
publish_date = models.DateTimeField(auto_now=True)
# author
author = models.ForeignKey('AuthorModel', on_delete=models.CASCADE)
# Django 1 版本中不要求on_delete

def __str__(self):
return self.title

class AuthorModel(models.Model):
# name
name = models.CharField(max_length=20)
# age
age = models.IntegerField(default=0)

def __str__(self):
return self.name

完成后,迁移数据库到sqlite:

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

使用模型

简单查询操作

  • get:返回模型对象;查到多条或是未查到都会抛出异常。
  • all:返回查询集;返回所有数据
  • filter:返回查询集;返回满足条件的数据
  • exclude:返回查询集;返回不满足条件的数据
  • order_by:返回查询集;对查询结果排序

对于 get,filter,exclude 这三个查询操作,可以填写查询条件,例如:

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
# 精确查询
MyBookModel.objects.get(id=1)
MyBookModel.objects.get(title__exact="First Book") # 同 title="First Book"
# 模糊查询
MyBookModel.objects.filter(title__contains="First")
MyBookModel.objects.filter(title__startswith="First")
MyBookModel.objects.filter(title__endswith="Book")
# 空查询
MyBookModel.objects.filter(title__isnull=False)
# 范围查询
MyBookModel.objects.filter(id__in=[1, 3, 5])
# 比较查询
MyBookModel.objects.filter(id__gt=2)
MyBookModel.objects.filter(id__lt=2)
MyBookModel.objects.filter(id__gte=2)
MyBookModel.objects.filter(id__lte=2)
# 日期查询
MyBookModel.objects.filter(publish_date__year=1990)
MyBookModel.objects.filter(publish_date__month=2)
MyBookModel.objects.filter(publish_date__day=2)
MyBookModel.objects.filter(publish_date__gt=date(1980,3,2))
# 排序
MyBookModel.objects.all().order_by('id', 'title') # 升序
MyBookModel.objects.all().order_by('-id') # 降序,前面加一个 减号
# 查看是否有数据
m = MyBookModel.objects.filter(id=1)
m.exists()

也就是说,其参数格式为模型属性名__条件名=值。(注意是双下划线)

高级查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.db.models import F, Q

# 直接书写,默认为“且”的关系
MyBookModel.objects.filter(id__gt=2, title__contains="First")

# Q 对象,实现“与或非”的关系
MyBookModel.objects.filter(Q(id__gt=2) & Q(title__contains="First")) # 且的关系
MyBookModel.objects.filter(Q(id__gt=2) | Q(title__contains="First")) # 或的关系
MyBookModel.objects.filter(~Q(id=2)) # id 不为 2

# F 对象,实现“属性(字段)”之间的比较
MyBookModel.objects.filter(publish_date=F('publish_date')) # 查询出版日期与出版日期相等的对象
MyBookModel.objects.filter(publish_date=F('publish_date') / 3) # 查询出版日期与出版日期除以三相等的对象

# 聚合函数 sum count avg max min

from django.db.models import Sum, Count, Avg, Max, Min
MyBookModel.objects.all().aggregate(Count('id')) # 返回字典 {'id_count': 2}
MyBookModel.objects.aggregate(Count('id')) # 返回字典 {'id_count': 2},与前者功能一致
MyBookModel.objects.filter(id__gt=2).count() # id 大于 2 的记录数目

查询集:

  • 惰性查询:只有需要具体数据的时候才发生查询。
  • 缓存:第一次查询到的查询集数据会缓存下来,第二次再访问这个查询集的时候就会使用缓存的内容。
  • 切片:对一个查询集切片会产生新的查询集,且切片参数不可为负数。

模型关系

模型(数据表)关系分为:

  • 一对一
  • 一对多
  • 多对多

关系属性:

  • 多对多:ManyToManyField(‘表名’),可以任意定义到其中一个模型中。
  • 一对一:OneToOneField(‘表名’),可以任意定义到其中一个模型中。
  • 多对一:ForeignKey(‘表名’),定义在多的模型中。

关联查询(一对多)

1
2
3
4
5
6
7
8
9
10
11
# 查询一表
一表.objects.filter(多表__属性__条件='...') # 这里的多表要小写
AuthorModel.objects.filter(mybookmodel__title__contains='First')
# 查询多表
多表.objects.filter(外键__属性__条件='...')
MyBookModel.objects.filter(author__name__contains='W')
# 查询多表
查询集x = 一表.objects.get(id=1)
查询集y = 查询集.多表_set.all() # 多表也要小写
x = AuthorModel.objects.get(id=1)
y = x.mybookmodel_set.all()

插入,更新与删除

使用举例:

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
from my_app.models import AuthorModel, MyBookModel
from datetime import date

# 创建一条作者记录
a = AuthorModel()
a.name = "Wang"
a.age = 80
a.save()

# 创建一条作者记录
a = AuthorModel.objects.create(name='Li', age=10)

# 创建一条书籍记录
m = MyBookModel()
m.title = "First Book"
m.date = date(1999,1,1)
m.author = a
m.save()

# 修改
m = MyBookModel.objects.get(id=1)
m.title = "Second Book"
m.save()
# 删除
m = MyBookModel.objects.get(id=1)
m.delete()

自关联

自关联是一种特殊的一对多关系,例如“省->市->县”的关系。
设计这种关系,一种方法是设计三张表,利用外键关联三个表;另外一种方法是将他们设计到一张表中,利用一个字段指向其父级ID,这样就形成了自关联的关系。

1
2
3
4
5
6
class AreaModel(models.Model):
title = models.CharField(max_length=100)
parent = models.ForeignKey('self', null=True, blank=True, on_delete=False)

def __str__(self):
return self.title

on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五个可选择的值。

CASCADE:此值设置,是级联删除。
PROTECT:此值设置,是会报完整性错误。
SET_NULL:此值设置,会把外键设置为null,前提是允许为null。
SET_DEFAULT:此值设置,会把设置为外键的默认值。
SET():此值设置,会调用外面的值,可以是一个函数。

管理器 objects

就是MyBookModel.objectsobjects。自制管理器的优势有:

  • 改变查询结果
  • 添加个性化方法

自制管理器的方式如下:

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
class AuthorModelManager(models.Manager):
def all(self):
a = super().all()
return a.filter(age__gt=10)

# 此方法与下面的create任选一个:
def create_author(self, name, age):
a = self.model() # a = AuthorModel()
a.name = name
a.age = age
a.save()
return a

class AuthorModel(models.Model):
...
# 管理器
objects = AuthorModelManager()
...

@classmethod
def create(cls, name, age):
a = cls()
a.name = name
a.age = age
a.save()
return a

一旦自制了管理器,原来的管理器就自动失效了(就算名字不是objects,原objects也会失效)。

元选项

在数据库中,数据表的命名是项目名_模型名,但是一旦项目名发生变化,所有的表的命名都会受到影响。如果消除这种影响,可以在模型类里面定义一个元类:

1
2
3
4
5
class AuthorModel(models.Model):
...
class Meta:
# 指定表名
db_table = 'author_table'

在某些版本的SQLite中不支持数据表改名,所以这一步操作要注意。

导入导出数据

1
2
python manage.py dumpdata > data.json
python manage.py loaddata data.json

Django 视图

静态视图

在my_app下创建templates文件夹,在该文件夹下创建index.html,编辑index.html。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello</h1>
</body>
</html>

编辑视图函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.http import HttpResponse
from django.shortcuts import render


# 第一种方式,直接返回纯文本
def hello(request):
return HttpResponse('Hello')


# 第二种方式,返回模板
def index(request):
return render(request, 'index.html')

修改后记着修改对应的urls:

1
2
3
4
5
6
7
8
from django.urls import path, include
import my_app.views


urlpatterns = [
path('hello', my_app.views.hello),
path('index', my_app.views.index)
]

动态视图

模板系统基本语法:

1
2
3
变量标签:{{ 变量 }}
for循环标签:{% for x in list %}, {% endfor %}
if-else标签:{% if %}, {% else %}, {% endif %}

首先编辑好前端页面,label_list是要输出的标签。这里我们创建books.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>
{% for x in label_list %}
<h1>{{ x.title }}</h1>
{% endfor %}
</body>
</html>

模板渲染:
后台给要输出的标签赋值:

1
2
3
4
5
def get_them(request):
get_all = MyBookModel.objects.all()
return render(request, 'books.html', {
'label_list': get_all
})

修改后,别忘了修改urls.py文件。

1
2
3
4
urlpatterns = [
path('index/', my_app.views.index),
path('get_books/', my_app.views.get_books),
]

如果想自己做一个渲染器,可以这样做:

1
2
3
4
5
6
7
from django.template import loader, RequestContext

def my_render(request, template, args):
temp = loader.get_template(template)
context = RequestContext(request, args)
res_html = temp.render(context)
return HttpResponse(res_html)

http://127.0.0.1:8080/my_app/index/
http://127.0.0.1:8080/my_app/get_books/

依据ID进行路由跳转

首先建立模板one_book.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>
<h1>{{ item.title }}</h1>
<h2>{{ item.author }}</h2>

</body>
</html>

在my_app的urls里面修改:

1
path('get_the_book/<int:bid>', my_app.views.get_the_book)

相应修改views.py:

1
2
3
4
5
6
def get_the_book(request, bid):
bid = int(bid)
get_one = MyBookModel.objects.get(id=bid)
return render(request, 'one_book.html', {
'item': get_one
})

页面重定向跳转方法:

1
2
3
4
5
6
7
8
9
def create(request):
a = AuthorModel.objects.get(id=1)

m = MyBookModel()
m.title = "Second Book"
m.author = a
m.save()

return HttpResponseRedirect('/my_app/index')

自定义 404 等错误页面

在项目urls.py中配置:

1
2
3
4
import django.conf.urls

django.conf.urls.handler404 = 'my_app.views.error404'
django.conf.urls.handler500 = 'my_app.views.error500'

同时设计相应的错误页面。要查看效果,就要关闭DEBUG模式才可以。

1
2
3
4
5
6
def error404(request):
return HttpResponse('Error handler content', status=404)
# return render(request, 'error404.html', status=404)

def error500(request):
return HttpResponse('Error handler content', status=500)

管理静态文件

通常一些静态文件,如网站logo等资源需要单独存放到一个固定的位置,一般是存放到静态文件目录下。
配置静态文件目录过程为:

  1. 确保 INSTALLED_APPS 包含了 django.contrib.staticfiles。
  2. 在配置文件中,定义 STATIC_URL,例子:
    STATIC_URL = '/static/'
  3. 在模板中,用 static 模板标签基于配置 STATICFILES_STORAGE 位给定的相对路径构建 URL。
    1
    2
    {% load static %}
    <img src="{% static 'image/example.jpg' %}" alt="My image">
  4. 将你的静态文件保存至程序中名为 static 的目录中。例如 my_app/static/image/example.jpg。

表单

表单的提交常见有两种方式:GET与POST。
在Request中包含了浏览器的请求信息,Request的属性包括:

  • POST:POST请求参数,查询字典(QueryDict)类型
  • GET:GET请求参数,查询字典(QueryDict)类型
  • FILES:上传的文件,类似于字典的对象
  • COOKIES:客户端的cookies,一个Python字典
  • path:表示请求路径,不包括域名与参数
  • method:表示请求方式
  • encoding:提交数据的编码,默认utf8
  • session:服务端session

用法如下

1
2
3
4
5
6
7
8
9
def set_author(request):
args = request.POST
# 写法 1:如果没有该参数则返回None
name = args.get('name')
name = args.get('name', "Anonymous") # 第二个参数表示默认值,即没有参数的时候返回该默认值
name = args.getlist('name') # 返回name参数的多个值,因为允许一个参数含有多个值
# 写法 2:如果没有该参数则抛出异常KeyError
name = args['name']
return HttpResponseRedirect('/my_app/index')

Ajax 请求

1
2
3
4
5
6
7
8
9
from django.http import JsonResponse

def get_author(request):
a = AuthorModel.objects.get(id=1)
j = {
'name': 'Wang'
'age': 20
}
return JsonResponse(j)

Cookie:保存在客户端,由服务器生成,客户端访问服务器时会附带Cookies。另外Cookies是会过期的,如果不指定,则有效期为关闭浏览器时。
Session:保存在服务端,也是由服务器生成,依赖于Cookie,因为客户标识码SessionID存储在Cookie里面。Session存储位置在数据库中。

Cookie

1
2
3
4
5
6
7
8
9
10
# 设置 Cookies
response = HttpResponse('')
response.set_cookie('num', 1, max_age=7*24*3600) # 从现在开始计算过期时间,单位:秒
response.set_cookie('num', 1, expires=timedelta(days=7)+datetime.now()) # 从指定时间计算,单位:秒
return response
# 读取 Cookies
if 'num' in request.COOKIES['num']:
num = request.COOKIES['num']
else:
num = 0

Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 设置 Session
request.session['num'] = 1
return HttpResponse('...')
# 读取 Session
if 'num' in request.session['num']:
num = request.session['num']
else:
num = 0
# 或
num = request.session.get('num', '0') # 也可以设置默认值
# 清除 Session 的值
request.session.clear()
# 删除 Session 记录
request.session.flush()
# 删除 某一个键
del request.session['key']
# 设置会话超时时间,单位:秒。默认两周;为0,则关闭浏览器过期
request.session.set_expiry(24*3600)

设计分页

我们也可以使用index?page=1的方式传递GET参数,分页也一般采用这种方式查看当前访问的是第几页。通过GET参数获取请求的分页,为了获取GET参数,可以使用如下:

1
2
3
4
5
6
page = request.GET.get('page')  # 字符串,可能没有这个参数
if page:
page = int(page)
else:
page = 1

Django自带了分页组件。分页组件及其常用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.core.paginator improt Paginator

p = Paginator(one_list, 3) # one_list列表, 每页3个记录
p.num_pages # 分了几页
p.page_range # 总记录数

page = p.page(1) # 获取第一页
page.number # 当前页页码
page.object_list # 第一页的查询集
page.paginator # 对应的分页器
page.has_next() # 是否有下一页
page.has_previous() # 是否有上一页
page.previous_page_number # 前一页页码
page.next_page_number # 后一页页码

Django 路由

Path 语法

Django Path默认支持五个转化器:

1
2
3
4
5
str:匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int:匹配正整数,包含0。
slug:匹配字母、数字以及横杠、下划线组成的字符串。
uuid:匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path:匹配任何非空字符串,包含了路径分隔符

具体用法例如:

1
2
3
4
5
6
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<slug>/', views.article_detail),
]

用户也可以自定义转化器,自定义的转化器需要使用实现转化器接口。实现方法如下:

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
# 例 1
class IntConverter:
# 正则表达式
regex = '[0-9]+'
# value是匹配到的字符串,返回Python变量
def to_python(self, value):
return int(value)
# value是Python变量,返回字符串,用于url反向引用
def to_url(self, value):
return str(value)

# 例 2
class StringConverter:
regex = '[^/]+'

def to_python(self, value):
return value

def to_url(self, value):
return value

# 例 3:匹配4位整数
class FourDigitYearConverter:
regex = '[0-9]{4}'

def to_python(self, value):
return int(value)

def to_url(self, value):
return '%04d' % value

定义完成后,将其注册到配置中。在需要的urls.py中添加:

1
2
3
4
5
6
7
8
from django.urls import register_converter
from . import converters # 自制转化器,不嫌乱也可以把自制转化器放到urls.py中。

register_converter(converters.FourDigitYearConverter, 'yyyy')

urlpatterns = [
path('articles/<yyyy:year>/', views.year_archive),
]

如果嫌自制转化器太繁琐,可以使用兼容Django 1中的正则表达式的方式直接匹配。

1
2
3
4
5
6
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path('articles/(?P<year>[0-9]{4})/', views.year_archive),
re_path('articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/', views.month_archive),
re_path('articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[^/]+)/', views.article_detail),
]

Django 模板

模板加载顺序

加载一个模板,首先是查找配置的模板目录,如果找不到,再去INSTALLED_APPS下的templates查找。这一过程是Django自动的。

模板变量

模板变量,不能以下划线开头。

1
{{ num }}

下面两种情况,有两种解析顺序。

1
{{ author.name }}

其解析顺序为:

  • 作为字典,取键值
  • 作为对象,取属性
  • 作为对象,当作对象的方法
  • 都无法匹配,则替换为空字符串
1
{{ author_list.0 }}

其解析顺序为:

  • 作为字典,取键值
  • 作为列表,取下标
  • 都无法匹配,则替换为空字符串

模板标签

后端给标签变量赋值可以使用render函数。

1
2
3
render(request, 'index.html', {
'author_list': AuthorModel.objects.all()
})

前端的模板标签主要如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for循环标签
{% for x in author_list %}
列表不为空时
{{ forloop.counter }} 记录循环第几次
{% empty %}
列表为空时
{% endfor %}

if标签
{% if 条件 %}
操作符旁边必须有空格
{% elif %}
{% else %}
{% endif %}

注释
{# 单行注释 #}
{% comment %}
多行注释
{% endcomment %}

模板过滤器

过滤器是用在前端的标签函数,用于对模板变量做操作。

1
2
3
4
5
6
7
8
9
10
过滤器格式为
{{ 变量|过滤器:参数 }}
改变日期的显示格式
{{ book.publish_date|date:"Y年-m月-d日" }}
求长度
{{ book.title|length }}
设置默认值
{{ book.title|default:"No Title" }}
自定义过滤器(是否是奇数)
{{ author.age|mod:1 }}

自定义过滤器的定义应在my_app目录下的templatetags下。templatetags应该有一个__init__.py文件,保证该目录可以被Python识别。

这里创建一个filters.py文件用于开发自定义过滤器。自定义标签也可以写到这里。
filters.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django import template
register = template.Library()


# value:被判断的变量;arg:传入的参数
def mod(value, arg):
return value % arg

# 完成后,注册过滤器
register.filter('mod', mod)

# 另外,只有一个参数的过滤器如下,外加另外一种注册方式
@register.filter(name='lower')
def lower(value): # Only one argument.
return value.lower()

在需要使用的模板上加载过滤器。

1
{% load filters %}

模板继承

网页往往会有很多重复的内容,因此我们可以制作一个父页面,子页面继承主页面显示以减少重复代码。

父页面base.html

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>
<h1>Hello</h1>
{% block topics %}
默认显示内容
{% endblock topics %}
</body>
</html>

子页面sub_page.html

1
2
3
4
5
6
7
8
9
{% extends 'base.html' %}

{% block topics %}
新内容

获取父模板的内容
{{ block.super }}

{% endblock topics %}

模板转义

默认情况下,模板上下文(由后端传递过来)中的html标记会被转义显示,即模板中的<>会被转化为&lt;&gt。因此要关闭转义显示,可以使用标签

1
2
3
4
5
6
7
方式 1
{{ 变量|safe }}

方式 2
{% autoescape off %}
模板语言代码
{% endautoescape %}

Django 用户登录

登录装饰器

有些页面是用户登录之后才可以访问的,例如修改密码,修改昵称等,也就是这些页面首先要进行用户登录的判断,否则让用户跳转回登录页面。

我们可以通过函数装饰器的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义一个闭包函数
def login_required(view_func):
def wrapper(request, *view_args, **view_kwargs)
# 判断用户是否登录
if true:
return view_func(request, *view_args, **view_kwargs)
else:
return redirect('/login')
pass
return wrapper

# 使用函数装饰器,使用该函数会先调用login_required,相当于
# login_required(change_pwd)(request, *view_args, **view_kwargs)
@login_required
def change_pwd(request):
return HttpResponse('Change Password')

CSRF 攻击

CSRF 攻击即跨站请求伪造攻击。我们在访问某一网站时,例如银行网站,在访问的结束后再去访问其他的网站,就会致使我们所有保存在浏览器上的数据包都会暴露给第三方网站,如果第三方网站上有某些攻击脚本,例如在用户不知情的情况下,再次利用刚才的数据包(SessionID)访问银行网站进行一些危险的操作,我们的数据就会产生泄露甚至丢失的危险。

由于伪造的网站与真实的网站的IP或主机名是不一样的,所以根据这一特性,我们可以也防止这种跨站请求伪造攻击。

Django 默认是启用这种 CSRF 攻击保护的(只针对POST),但同时也带来了不便,因为我们有时自己的网站也会被防护,导致自己的网站都无法正常浏览。

解决这一问题,可以在模板中的表单里添加如下内容即可。

1
{% csrf_token %}

内置表单

Django中内置了表单。用户可以通过Django内置的表单生成器自动生成表单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django import forms

class LoginForm(forms.Form):
username = forms.TextField()
password = forms.TextField(widget=forms.PasswordInput)

def login_handler(request):
if request.method == "POST":
login_form = LoginForm(request.POST)
if login_form.is_valid():
user = login_form.cleaned_data['username']
pswd = login_form.cleaned_data['password']
if user:
# 验证用户名,密码
return HttpResponse('成功登录')
else:
return HttpResponse('登录失败')
else:
return HttpResponse("输入不合法")

def login(request):
login_form = LoginForm()
return render(request,'login.html', {"forms":login_form})
1
2
3
4
5
<form action="." method="post">
{% csrf_token %}
{{ forms }}
<input type="submit" value="Login">
</form>

验证码

django-simple-captcha 官方文档
首先按照验证码库:

1
pip install django-simple-captcha

在项目settings.py中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
INSTALLED_APPS = [
...
"captcha",
]

# Captcha 二者选其一
# 字母验证码
CAPTCHA_IMAGE_SIZE = (80, 45) # 设置 captcha 图片大小
CAPTCHA_LENGTH = 4 # 字符个数
CAPTCHA_TIMEOUT = 1 # 超时(minutes)


# 加减乘除验证码
CAPTCHA_OUTPUT_FORMAT = '%(image)s %(text_field)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'
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge'
CAPTCHA_TIMEOUT = 1

在项目urls.py中配置:

1
path('captcha/', include('captcha.urls'))

完成后迁移数据库

1
2
python manage.py makemigration
python manage.py migrate

创建登录表单,验证码在登录时由表单自动完成验证。

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 import forms
from captcha.fields import CaptchaField

class LoginForm(forms.Form):
username = forms.TextField()
password = forms.TextField(widget=forms.PasswordInput)
captcha = CaptchaField()

def login_handler(request):
if request.method == "POST":
login_form = LoginForm(request.POST)
if login_form.is_valid():
user = login_form.username
if user:
'''用户登陆后,Django会自动调用默认的session应用,将用户的id存至session中'''
return HttpResponse('成功登录')
else:
return HttpResponse('登录失败')
else:
return HttpResponse("输入不合法")

def login(request):
login_form = LoginForm()
return render(request,'login.html', {"forms":login_form})

如果想要点击验证码实现验证码更新,则可以使用如下操作(需要jQuery)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from captcha.helpers import captcha_image_url
from captcha.models import CaptchaStore
import json


def captcha_refresh(request):
""" Return json with new captcha for ajax refresh request """
if not request.is_ajax(): # 只接受ajax提交
raise Http404
new_key = CaptchaStore.generate_key()
to_json_response = {
'key': new_key,
'image_url': captcha_image_url(new_key),
}
return HttpResponse(json.dumps(to_json_response), content_type='application/json')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script>
$(function(){
# 改变鼠标箭头
$('.captcha').css({
'cursor': 'pointer'
})
# ajax 刷新
$('.captcha').click(function(){
console.log('click');
$.getJSON("/captcha/refresh/",
function(result){
$('.captcha').attr('src', result['image_url']);
$('#id_captcha_0').val(result['key'])
});});
# ajax动态验证
$('#id_captcha_1').blur(function(){ // #id_captcha_1为输入框的id,当该输入框失去焦点是触发函数
json_data={
'response':$('#id_captcha_1').val(), // 获取输入框和隐藏字段id_captcha_0的数值
'hashkey':$('#id_captcha_0').val()
}
$.getJSON('/ajax_val', json_data, function(data){ //ajax发送 $('#captcha_status').remove()
if(data['status']){ //status返回1为验证码正确, status返回0为验证码错误, 在输入框的后面写入提示信息
$('#id_captcha_1').after('<span id="captcha_status" >*验证码正确</span>')
}else{
$('#id_captcha_1').after('<span id="captcha_status" >*验证码错误</span>')
}
});
});
})
</script>

当然,高级玩家可以自己画验证码。
下面自制验证码:

安装Pillow包

1
pip install Pillow

定义一个验证码生成函数

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
from PIL import Image, ImageDraw, ImageFont
from django.utils.six import BytesIO

def verify_code(request):
import random
# 背景色,宽,高
bgcolor = (random.randrange(20, 100), random.randrange(20, 100), 255)
width = 100
height = 25
# 创建画面
img = Image.new('RGB', (width, height), bgcolor)
draw = ImageDraw.Draw(img)

# 绘制噪点
for i in range(0, 100):
xy = (random.randrange(0, width), random.randrange(0, height))
fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
draw.point(xy, fill=fill)

# 准备字符串
str_back = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
rand_str = ''
for i in range(0, 4):
rand_str += str_back[random.randrange(0, len(str_back))]

# 绘制字符串
font = ImageFont.truetype('FreeMono.ttf', 23)
fontcolor = (255, random.randrange(0, 255), random.randrange(0,255))
for i in range(0, 4):
draw.text((5 + 24*i, 2), rand_str[i], font=font, fill=fontcolor)

# 释放画笔
del draw

# 存储验证码到后端
request.session['verify_code'] = rand_str

# 保存到内存文件
buf = BytesIO()
im.save(buf, 'png')

# 返回验证码
return HttpResponse(buf.getvalue(), 'image/png')

URL 反向解析

在模板里面,可以将链接到其他页面的超链接写成动态的,这样可以保证修改链接后自动修改所有链接到某页的路径。

在项目urls.py中,添加namespace属性:

1
2
3
4
url_patterns = [
...
path('my_app/', include('my_app.urls', namespace='my_app'))
]

在应用urls.py配置中,添加name属性:

1
2
3
4
url_patterns = [
...
path('index_renamed/', views.index, name='index')
]

在模板中:

1
2
3
4
5

用法:url 'namespace:name' 参数
<a href="{% url 'my_app:index' %}">首页</a>
<a href="{% url 'my_app:index' arg1 arg2 %}">带位置参数的首页</a>
<a href="{% url 'my_app:index' a=arg1 b=arg2 %}">带关键字参数的首页</a>

在视图中使用反向解析:

1
2
3
4
5
6
7
from django.core.urlresolvers import reverse

def test_redirect(request):
# 'namespace:name'
url = reverse('my_app:index', args=('arg1', 'arg2'))
url = reverse('my_app:index', kwargs={'a'='arg1', 'b'='arg2'})
return redirect(url)

中间件

是Django预留的函数接口,允许我们干预请求和应答。例如对客户端进行过滤,防止DDoS攻击等。

中间件可以允许我们在执行视图函数之前自动执行中间件。中间件的执行流程如下:

1
2
3
4
5
6
7
8
9
10
11
st=>start: 请求到达服务器
op1=>operation: 产生Request对象
op2=>operation: 调用process_request
op3=>operation: 匹配URL
op4=>operation: 调用process_view
op5=>operation: 调用视图函数
op6=>operation: 调用process_response
op6=>operation: 返回给浏览器
e=>end

st->op1->op2->op3->op4->op5->op6->e

在setting.py中注册中间件,注册顺序与执行顺序相反。

1
2
3
4
MIDDLEWARE = [
...
'my_app.middleware.Block_Middleware'
]

在my_app目录下建立middleware.py文件,编辑此文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 中间件类
class Block_Middleware(object):
# 匹配url之后,在进入视图函数之前调用
def process_view(self, request, view_func, *view_args, **view_kwargs):
# 获取浏览器端的IP地址:
user_ip = request.META('REMOTE_ADDR')
if user_ip in ['127.0.0.1']:
return HttpRequest('Go Back')

# 服务器启动后接受第一个请求的时候调用
def __init__(self):
pass
# 产生request之后,匹配url路由之前调用
def process_request(self, request):
pass
# 调用视图函数之后,返回浏览器之前调用
# view_func 为将要调用的视图函数
def process_response(self, request, response):
return response
# 视图函数异常时候调用
def process_exception(self, request, exception):
pass

注意:如果在中间件的任意一个函数返回response,后续的过程将不会执行,而是直接将结果交给process_response,再返回浏览器。

Django Shell

就是带Django相关功能的Python Shell。可以方便开发者调试代码。
例如,使用Django Shell添加一条数据库的记录。首先进入Shell

1
python manage.py shell

进入后,可以执行如下常用操作:

1
2
3
4
5
from my_app.models import AuthorModel, MyBookModel
from datetime import date

# 获取作者A的所有书 的 第0本,注意要所有字母小写
print(a.mybookmodel_set.all()[0])

Django Admin 模块

Django标配的后台管理工具,使用方便,可以快速编辑很多内容。

首先创建用户:

1
python manage.py createsuperuser

填写用户名与密码,这里可能要求密码长度大于8位且不能为纯数字。

之后运行查看效果:

1
python manage.py runserver 8080

http://127.0.0.1:8080/admin/

进入后台后,可以看到管理页面中出现 Groups 与 Users,编辑这两项添加用户与用户组。
如果想将my_app的模型MyModel也加入其中,可以到my_app下的admin.py中编辑:

1
2
3
from my_app.models import MyBookModel, AuthorModel
admin.site.register(MyBookModel)
admin.site.register(AuthorModel)

也可以使用自定义管理页面:

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
class MyBookModelAdmin(admin.ModelAdmin):
# 浏览页

# 显示的字段,方法
list_display = ['id', 'title', 'title_func']
# 如果想让传过来的方法也可以排序,要做模型里面添加
# title_func.admin_order_field = 'title'
# 如果想改变显示内容
# title_func.short_description = 'T'
# 如果想改变字段的显示内容
# title = models.CharField(verbose_name='T', max_length=20)

# 每一页显示多少条
list_per_page = 10

# 动作
actions_on_bottom = True
actions_on_top = False

# 过滤栏
list_filter = [
'title' # 列表页右侧的过滤栏
]

# 搜索框
search_fields = [
'title' # 列表页上方的搜索框
]

# 编辑页

# 字段显示顺序
fields = [
'title',
'id'
]

# 分组显示
fieldsets = [
('Base', {'fields': ['title', 'id']}),
('Advance', {'fields': []})
]

# 关联对象
inlines = [
AreaStackedInline,
AreaTabularInline
]

# 块状
class AreaStackedInline(admin.StackedInline):
# 写多类的名字
model = AreaInfo
extra = 2 # 额外新建编辑2个子对象

# 表状
class AreaTabularInline(admin.TabularInline):
# 写多类的名字
model = AreaInfo
extra = 2 # 额外新建编辑2个子对象

admin.site.register(MyBookModel, MyBookModelAdmin)
admin.site.register(AuthorModel)

如果要重写模板,可以在templates下建立base_site.html文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends "admin/base.html" %}

{# 标题 #}
{% block title %}
{{ title }} | {{ site_title|default:_('Django site admin') }}
{% endblock %}

{# 展框 #}
{% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}">
{{ site_header|default:_('Django administrator') }}
</a>
</h1>
{% endblock %}

{# 导航栏 #}
{% block nav-global %}

{% endblock %}

Django 上传

配置settings.py文件:

1
2
MEDIA_URL = '/static/media'
MEDIA_ROOT = os.path.join(BASE_DIR, 'my_app/static/media')

模板上,上传图片的表单配置如下:

1
2
3
4
5
<form method="post" action="/my_app/upload_action" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="pic"/><br/>
<input type="submit" value="upload file"/>
</form>

视图中,获取文件并保存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def upload_handle(request):
# 如果是小文件(<2.5MB),则文件存储在内存中;如果是大文件(>2.5MB),则文件存储在临时文件中。
image = request.FILES['pic']
# image.name:文件名
# image.chunks():返回一个列表,里面存储文件的每一个区块
# image.size:文件大小
# image.content_type:文件类型,但是不确定
# 创建一个文件
save_path = '%s/my_book_model/%s'%(settings.MEDIA_ROOT, image.name)
with open(save_path, 'wb') as f:
for chk in image.chunks():
f.write(chk)
# 将路径保存至数据库中
m = MyBookModel.objects.get(id=1)
m.picture = 'my_book_model/%s'%image.name
m.save()

WebSocket

Django-channels

开发流程总结

需求分析

网站设计

数据库设计

URL设计

URL 视图 模板文件
/login login login.html

创建项目

模型编辑

视图编辑

路由配置

其他

虚环境

虚环境的安装

1
2
sudo pip install virtualenv           # 虚环境
sudo pip install virtualenvwrapper # 虚环境扩展

虚环境的常用命令

1
2
3
4
mkvirtualenv -p python3 name  # 创建虚环境
deactivate # 退出虚环境
workon name # 进入虚环境
rmvirtualenv name # 删除虚环境

CMD下进入虚环境:

1
./venv/Scripts/activate.bat

PowerShell下进入虚环境:

1
2
3
4
# 首先开启脚本运行权限(管理员模式)
Set-ExecutionPolicy RemoteSigned
# 开启脚本
./venv/Scripts/activate.psl

查看虚环境下已安装的包

1
2
pip list                      # 列出所有的包
pip freeze > requirements.txt # 输出安装的包(到文件)

MySQL

基本操作

开启日志文件,需要修改mysql.conf文件。

1
2
# 实时查看日志文件
tail -f mysql.log

数据可视化

pyecharts
xlrd Excel 操作