Django框架
Django是Python Web应用框架, 基于Python的WSGI(Web Service Gateway Interface)Web服务网关接口, Django从3.0开始运行 ASGI (异步服务网关接口)。
Django三大版本: Django 1.x、Django 2.x, Django 3.x(性能最优, 比较FastAPI/Tornado)。
一、 Django入门
1.1 基本概念
1.2 创建环境与app项目
安装依赖包
pip install django==2.2 -i https://mirrors.aliyun.com/pypi/simple
【注意】如果Python版本(3.7.4+)很高时,SQLite3版本同样很高,则django版本建议使用django==2.1.5+;因为,admin.site 站点管理时,会报auth_user_old表不存在的错误。
进入"终端" CMD命令, 通过 django-admin 命令创建django项目。
django-admin startproject helloDjango
通过django-admin命令创建app应用
django-admin startapp mainapp
在一个Django项目中,存在很多的app应用(模块), 创建好的app需要注册到主工程中(settings.py)
其它命令,可以通过django-admin help 命令查看:
[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 help runserver
查看相关的命令: python manage.py help
Available subcommands:
[auth]
changepassword
createsuperuser
[contenttypes]
remove_stale_contenttypes
[django]
check
compilemessages
createcachetable
dbshell
diffsettings
dumpdata
flush
inspectdb
loaddata
makemessages
makemigrations
migrate
sendtestemail
shell
showmigrations
sqlflush
sqlmigrate
sqlsequencereset
squashmigrations
startapp
startproject
test
testserver
[sessions]
clearsessions
[staticfiles]
collectstatic
findstatic
runserver
1.3 django项目结构
项目结构如下:
helloDjango
|--- helloDjango 主工程目录
|---- settings.py # 设置文件, 数据库连接、app注册、中间件及模板配置
|---- urls.py # 总路由
|---- wsgi.py # Django实现wsgi的脚本
|---- __init__.py
|--- mainapp 应用模块(主)
|---- __init__.py
|---- admin.py # 后台管理配置脚本
|---- models.py # 数据模型类声明所在脚本
|---- views.py # 声明当前应用的视图处理函数或类
|---- urls.py # 自已增加的当前应用模块的子路由
|---- tests.py # 当前应用模块的单元测试类
|---- apps.py # 声明当前应用的基本信息
|--- manage.py WEB应用的启动脚本, 项目工程的入口
启动项目的命令:python manage.py runserver
1.4 Django请求流程
1. 到urls分发器 (主路由urls.py -> 子路由 urls.py)
2. urls分发器根据路由规则(正则)分发到views
3. views去调用Model,交互数据
4. views将数据渲染(解析模板标签)到模板中,获取渲染之后HTML文本信息
5. 模板页面呈现给用户(封装HttpResponse对象)
http://127.0.0.1:8000/m/add/?csrfmiddlewaretoken=GQg9ySsT2KKOg2mzFosONCE3WqxaEnqK7UFkwbsGmwo9QLYbajI9PRfAgfHi0pgi&sn=111&name=%E7%8B%84%E5%A4%A7%E5%93%A5
1.5 请求和响应
请求对象: django.http.HttpRequest
响应对象: django.http.HttpResponse | JsonResponse
快捷函数: django.shortcuts.render 渲染 | redirect 重定向 , 快速生成响应的对象
Request结构:
Request结构主要由以下部分组成:
URL字段
Header字段
Body字段 该字段是一个io.ReadCloser接口
Form字段、PostForm字段和MultipartForm字段
请求对象的属性:
request.method 请求方法,请求方法有:GET、POST、PUT/PATCH、DELETE、OPTIONS
request.GET QueryDict 字典类型, get请求的查询参数
request.POST QueryDict 字典类型, post请求的表单form参数
request.META QueryDict 字典类型, 存放客户端环境相关参数, 如REMOTE_ADDR 客户端的IP地址
render函数:
render(request, '模板html文件', {}) 渲染模板,第三个参数是dict类型,可以在渲染模板时,替换{
{ }}或{% %}表达式。
二、 数据库连接与ORM模型
ORM(Object Relationship Mapping 对象关系映射): 将类和表进行映射, 针对类的实例操作时,即对表的行数据进行操作。在Python中,使用元类和相关的自省函数(hasattr、getattr、setattr、isinstance)实现的。
2.1 数据库连接配置
默认是sqlite3数据库, 在使用ORM模型之前,需要先生成迁移文件,再执行迁移命令,在数据库中生成这些模型对应的表。
- 先生成迁移文件
python manage.py makemigrations
- 开始迁移(生成表、 修改表、删除表)
python manage.py migrate
注意: 一旦生成了迁移文件并且迁移成功之后,不要删除迁移文件。
sqlite3数据库的文件访问方式可以通过python的内置模块sqlite3(微型关系数据库, 不强调数据类型),还可以是pycharm的数据面板打开。
以下是python的sqlite3模块打开sqlite.db数据库文件的方式:
>>> import sqlite3
>>> db = sqlite3.connect('/Users/apple/PycharmProjects/xpy201/hidjango/db.sqlite3')
>>> cursor = db.cursor()
>>> sql = "select name from sqlite_master where type='table'"
>>> cursor.execute(sql)
<sqlite3.Cursor object at 0x104c80420>
>>> cursor.fetchall()
[('django_migrations',), ('sqlite_sequence',), ('auth_group_permissions',), ('auth_user_groups',), ('auth_user_user_permissions',), ('django_admin_log',), ('django_content_type',), ('auth_permission',), ('auth_user',), ('auth_group',), ('django_session',)]
2.2 初步使用ORM模型
在app模块中的models.py 定义一个用户(客户)模型
from django.db import models
class UserEntity(models.Model):
# 默认情况下会自动创建id主键
name = models.CharField(max_length=20)
age = models.IntegerField(default=0)
phone = models.CharField(max_lengt=11)
class Meta:
# 指定当前模型类映射成哪一个表
db_table = 'app_user'
模型创建完成后,先后执行生成迁移文件和迁移。
2.3 CURD
查询
UserEntity.objects.all() # 查询所有, list
UserEngtity.objects.get(pk=id) # 根据主键值查询一个实体对象
X.objects 对象在X模型类定义时,由它的父类的元类动态添加的cls.add_to_class('objects', manager)
。此对象主要用于对模型的查询操作(基于QuerySet类构建)。
增加
u = UserEntity()
u.name = 'disen'
u.age = 20
u.phone = '177'
# 保存模型对象
u.save()
删除
u = UserEntity.objects.get(1)
u.delete() # 删除
更新
u = UserEntity.objects.get(3)
u.name = '李成'
u.save()
django作业: 完成以上的orm模型练习。
from django.db import models
# Create your models here.
# 声明学生模型类
class Student(models.Model):
# 如果声明的字段不存在主键primary key ,默认增加id 主键字段
sn = models.IntegerField(primary_key=True, db_column='sn')
name = models.CharField(max_length=20, null=False)
age = models.CharField(max_length=30)
sex = models.CharField(max_length=2, default='男')
def __str__(self):
return f'{self.sn}, {self.name}, {self.age}, {self.sex}'
class Meta:
db_table = 'tb_student'
# ordering = ('-age', )
Python Console中的命令:
>>> from mainapp.models import Student
>>> Student.objects.all()
<QuerySet [<Student: 1, disen, 1991-10-12, 女>, <Student: 2, jack, 1992-10-12, 男>]>
>>> Student.objects.get(pk=2)
<Student: 2, jack, 1992-10-12, 男>
>>> s2 = Student.objects.get(pk=2)
>>> s2
<Student: 2, jack, 1992-10-12, 男>
>>> s2.delete()
(1, {
'mainapp.Student': 1})
>>> Student.objects.all()
<QuerySet [<Student: 1, disen, 1991-10-12, 女>]>
>>> s1 = Student.objects.get(pk=1)
>>>s1.name = '李阳'
>>>s1.save()
>>>s2.save()
除了Python Console之外,也可以在Terminal中通过Python交互环境进入,但需要将项目的path目录复制,如项目的位置为/Users/apple/PycharmProjects/xpy201/hidjango
,则如下操作:
(xpy201) hidjango # python
>>> import os, sys
>>> import django
>>> sys.path.insert(0, '/Users/apple/PycharmProjects/xpy201/hidjango')
>>> os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hidjango.settings')
'hidjango.settings'
>>> django.setup()
>>> from mainapp.models import Student
>>> Student.objects.all()
<QuerySet [<Student: 1, 李阳, 1991-10-12, 女>, <Student: 2, jack, 1992-10-12, 男>]>
模型类的objects是Model的元类动态添加的: cls.add_to_class('objects', manager)
base.py 360行,它的类型是django.db.models.Manager
类(QuerySet查询结果集类)。
2.4 ORM应用于View
ORM定义的模型类,在urls路由对应的处理view函数中使用。根据客户端请求,要么查询数据,要么修改或保存数据,根据相关的业务数据要求,通过ORM实现数据的查询、修改、存储和删除。
2.4.1 表单数据保存
将POST请求的表单数据进行存储。首先,在view函数所在的脚本中,导入模型类,如Student
from .models import Student
# path: /m/add/
def add_stu(request: HttpRequest):
if request.method == 'POST':
form = request.POST
s = Student()
s.sn = form.get('sn', None)
s.name = form.get('name', None)
s.age = form.get('age', None)
s.sex = form.get('sex', '男')
s.save()
return redirect('/m/list/')
return render(request, 'add_stu.html', locals())
Window 操作系统如果出现debug下的gbk
编码问题,则修改django/views/debug.py文件第331和338两行的文件编码为UTF-8
,如:
def get_traceback_html(self):
"""Return HTML version of debug 500 HTTP error page."""
with Path(CURRENT_DIR, 'templates', 'technical_500.html').open(encoding='utf-8') as fh:
t = DEBUG_ENGINE.from_string(fh.read())
c = Context(self.get_traceback_data(), use_l10n=False)
return t.render(c)
def get_traceback_text(self):
"""Return plain text version of debug 500 HTTP error page."""
with Path(CURRENT_DIR, 'templates', 'technical_500.txt').open(encoding='utf-8') as fh:
t = DEBUG_ENGINE.from_string(fh.read())
c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
return t.render(c)
修改完成后,确认保存。或者降低django的版本为pip install django==2.0.1
。
2.4.2 查询数据渲染
在新的view函数中,查询所有的数据:
# 处理客户端发送url为 /m/list/ 的请求
def list_stu(request):
datas = Student.objects.all()
return render(request, 'list_stu.html',
context={
'datas': datas, 'title': '列出所有学生'})
将数据渲染到html中,html文件内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>学生列表</title>
</head>
<body>
<h1>{
{ title }}</h1>
<table border="1" cellspacing="0" cellpadding="5" width="80%">
<thead>
<th>学号</th>
<th>姓名</th>
<th>生日</th>
<th>性别</th>
<th width="200px">操作</th>
</thead>
{% for stu in datas %}
<tr>
<td>{
{ stu.sn }}</td>
<td>{
{ stu.name }}</td>
<td>{
{ stu.age }}</td>
<td>{
{ stu.sex }}</td>
<td>
<button onclick="">删除</button>
<button onclick="">编辑</button>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>
2.4.3 API接口数据
API接口: 前端页面的数据接口(URL),通过ajax(XMLHttpRequest)或 fetch()异步请求的方式获取API接口数据。API接口的数据,一般使用是json格式的文本字符串,在View函数中,对响应的json数据通过JsonResponse进行封装。
API接口说明文档:
base_url = ‘http://localhost:8000’
- 删除学生接口
-
url :
/m/del/
-
请求方法: GET
-
请求参数(查询参数):
sn 表示学号
-
响应的数据
-
正确(成功)
{ "data": "haha", "code": 0}
-
失败
{ "code": 1, "msg": "必须指定sn查询参数"}
-
测试接口的前端js代码:
...
<button onclick="del_stu('{
{ stu.sn }}')">删除</button>
...
<script>
function del_stu(sn) {
if(confirm('确认是否删除学号为: '+sn + " 的学生?")){
// alert('正在删除学号为: '+sn)
// url = 'http://locahost:8000/m/del/?sn=123'
url = location.origin+'/m/del/?sn='+sn
fetch(url).then(resp=>resp.json()).then(data=>{
if (data.code == 0){
// 刷新当前页面的数据
open('/m/list/', target='_self')
}else{
alert('操作失败')
}
})
}
}
</script>
view函数的写法:
def del_stu(request):
# 获取查询参数中的sn
sn = request.GET.get('sn', None)
if not sn:
return JsonResponse({
'code': 1, 'msg': '必须指定sn查询参数'})
s = Student.objects.get(pk=sn) # pk表示主键的列名
s.delete()
return JsonResponse({
'data': 'OK', 'code': 0})
扩展:增加列表页面的搜索功能
views.py脚本的内容:
def list_stu(request):
wd = None
if request.method == 'POST':
# wd 可能是学号,可能是姓名
# django.db.models.Q 可以多条件查询
wd = request.POST.get('wd', '')
if wd: # '' 为 False, None为False
if wd.isdigit(): # 判断搜索的内容是否为数字
datas = Student.objects.filter(sn=wd).all()
else:
datas = Student.objects.filter(name__contains=wd).all()
else:
datas = Student.objects.all()
return render(request, 'list_stu.html',
context={
'datas': datas, 'title': '列出所有学生'})
list_stu.html模板内容:
<h1>{
{ title }}</h1>
<form method="post">
{% csrf_token %}
<div style="text-align: center">
<input type="text" placeholder="输入学号或姓名" name="wd" size="30"> <button>搜索</button>
</div>
</form>
...
作业: 完成修改功能。
2.4.4 跨域请求配置
2.4.4.1 自定义中间件方式
在项目创建middles.py脚本,并定义CorsMiddleware类
ALLOWED_ORIGINS = '*'
ALLOWED_METHODS = ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE']
# ALLOWED_METHODS = ['POST', 'GET']
ALLOWED_HEADERS = ['Content-Type', '*']
ALLOWED_CREDENTIALS = 'true'
class CorsMiddleware(object):
"""
This middleware allows cross-domain XHR using the html4/5 post data API. Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE
"""
def process_request(self, request):
if 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META:
response = http.HttpResponse()
response['Access-Control-Allow-Origin'] = ALLOWED_ORIGINS
response['Access-Control-Allow-Methods'] = ",".join(ALLOWED_METHODS )
response['Access-Control-Allow-Headers'] = ",".join(ALLOWED_HEADERS )
response['Access-Control-Allow-Credentials'] = ALLOWED_CREDENTIALS
return response
return None
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = ALLOWED_ORIGINS
response['Access-Control-Allow-Methods'] = ",".join( ALLOWED_METHODS )
response['Access-Control-Allow-Headers'] = ",".join( ALLOWED_HEADERS )
response['Access-Control-Allow-Credentials'] = ALLOWED_CREDENTIALS
return response
并配置到settings文件的中间件的位置:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'middles.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
2.4.4.2 第三方应用方式
安装第三方的跨域请求的应用:django-cors-headers。
参考文档: https://pypi.org/project/django-cors-headers/
pip install django-cors-headers
安装成功之后, 添加已安装应用列表:
INSTALLED_APPS = [
...
'corsheaders',
...
]
再配置中间件:
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
最后配置应用中使用的常量:
CORS_ORIGIN_ALLOW_ALL = True # 默认为False
CORS_ALLOW_CREDENTIALS = True
# CORS_ORIGIN_WHITELIST = [] # 白名单, 只有CORS_ORIGIN_ALLOW_ALL为False时有效
CORS_ALLOW_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
CORS_ALLOW_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
]
三、 综合案例-水果管理
3.1 模型设计
# Fruit 水果模型类(name 名称, price 价格, source 产地, content 描述, cate_type_id 类型ID)
# FruitImage 水果图片模型类(fruit_id 水果ID, url 图片的路径, width 宽度, height 高度, name 标题)
# CateType 水果分类(name 名称, order_num 排序号)
# Store 水果商店(name 商店名称,boss_name 店主姓名, phone 联系电话, address 详细地址, city 城市, lat 纬度, lon 经度)
# 经纬度: 百度地图拾取坐标系统
# StoreDetail 水果商店的详情, 广告、精选水果、特价水果
模型和模型之间的关系:
- 一对一的关系 models.OneToOneField()
- 一对多的关系 models.Foreignkey()
- 多对一的关系
- 多对多的关系 models.ManyToManyField()
【注】在一端模类中,访问多端模型的对象时: obj.小写的多端类名_set, 或在多端类中定义关系时指定了反向引用的属性名。
from django.db import models
# Create your models here.
class FruitCategory(models.Model):
# 如果不存在主键字段时,默认新增一个id字段
name = models.CharField(verbose_name='分类名', max_length=20, unique=True)
num = models.IntegerField(verbose_name='序号')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = verbose_name = '水果分类'
db_table = 'tb_category'
ordering = ('-num', )
class Fruit(models.Model):
name = models.CharField(max_length=20, verbose_name='水果名')
price = models.FloatField(verbose_name='价格', default=0)
source = models.CharField(verbose_name='源产地', max_length=50)
# blank和verbose_name两个参数描述字段在admin站点显示的信息和必填验证
content = models.TextField(verbose_name='描述', null=True, blank=True)
# ForeignKey属性的字段(关联主表模型类的实例),自动增加`字段名_id` 外键字段
# related_name 指定关联父模型引用当前类的名称,默认为`小写的当前类名_set`
category = models.ForeignKey('FruitCategory',
on_delete=models.SET_NULL, # 级联删除时设置为NUll
null=True, blank=True, verbose_name='所属分类',
related_name='fruits')
def __str__(self):
return self.name
class Meta:
db_table = 'tb_fruit'
verbose_name_plural = verbose_name = '水果信息'
ordering = ('source', 'price')
c1 = FruitCategory(name='水果',num=100)
c1.save()
f1 = Fruit(name='红富士', price=4.5, source='延安', category_id=c1.id)
f1.save()
f2 = Fruit(name='小青', price=3.5, source='山东', category_id=c1.id)
f2.save()
f1.category.name # 查看水果的分类名称
c1.fruits.all() # 查看当前分类下的所有水果
设计水果图片模型类:
class FruitImage(models.Model):
fruit = models.OneToOneField('Fruit',
on_delete=models.CASCADE,
verbose_name='水果')
title = models.CharField(max_length=50, verbose_name='标题')
# 使用ImageField时,需要安装pillow库
# 配置静态资源 static和媒体资源 media
# upload_to 是相对于settings.MEDIA_ROOT 路径
img = models.ImageField(upload_to='fruits',
width_field='width',
height_field='height')
width = models.IntegerField(verbose_name='宽度')
height = models.IntegerField(verbose_name='高度')
安装pillow库:
pip install pillow
设置静态资源的目录:
# 静态资源文件: css/js/image/fonts等
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
# 媒体资源文件
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media')
将媒体资源访问的URL添加到主路由中:
from fruitpro import settings
from django.conf.urls.static